keys/main.go

342 lines
8.7 KiB
Go
Raw Permalink Normal View History

2026-05-10 15:53:01 +08:00
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"apigo.cc/go/encoding"
"apigo.cc/go/keys/lib"
"apigo.cc/go/safe"
"apigo.cc/go/shell"
"golang.org/x/term"
)
func main() {
if len(os.Args) < 2 {
interactiveKeystoreMode()
return
}
arg1 := os.Args[1]
// 1. 特殊全局命令 (以 - 开头)
if strings.HasPrefix(arg1, "-") {
switch arg1 {
case "-h", "--help", "-help":
printUsage()
case "-setpath":
if len(os.Args) < 3 {
fmt.Println(shell.Red("用法: keys -setpath <路径>"))
os.Exit(1)
}
setPath(os.Args[2])
case "-status":
showStatus()
default:
fmt.Printf("%s: %s\n", shell.Red("未知选项"), arg1)
printUsage()
}
return
}
// 2. 密钥管理非交互指令
switch arg1 {
case "list":
handleListKeystores()
return
case "create":
if len(os.Args) < 3 {
fmt.Println(shell.Red("用法: keys create <密钥名>"))
os.Exit(1)
}
handleCreateKeystore(os.Args[2])
return
case "remove":
if len(os.Args) < 3 {
fmt.Println(shell.Red("用法: keys remove <密钥名>"))
os.Exit(1)
}
handleRemoveKeystore(os.Args[2])
return
case "export":
if len(os.Args) < 4 {
fmt.Println(shell.Red("用法: keys export <密钥名> <语言>"))
os.Exit(1)
}
handleExportKeystore(os.Args[2], os.Args[3])
return
}
// 3. 密码管理指令 (keys <keyname> [cmd])
keyName := arg1
// 检查 keyName 是否存在于 keystore
if keyName != "default" {
ksPath := filepath.Join(lib.GetKeystorePath(), keyName)
if _, err := os.Stat(ksPath); err != nil {
fmt.Printf("%s: 密钥 '%s' 不存在。\n", shell.Red("错误"), keyName)
fmt.Println("使用 'keys list' 查看可用密钥。")
os.Exit(1)
}
}
if len(os.Args) == 2 {
// 进入该密钥的交互式密码管理模式
interactivePasswordMode(keyName)
return
}
// 非交互式密码管理
handlePasswordCmd(keyName, os.Args[2:])
}
func setPath(newPath string) {
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Println(shell.Red("无法获取用户主目录: " + err.Error()))
os.Exit(1)
}
err = os.WriteFile(filepath.Join(homeDir, ".keyspath"), []byte(newPath), 0600)
if err != nil {
fmt.Println(shell.Red("保存 .keyspath 失败: " + err.Error()))
os.Exit(1)
}
fmt.Println(shell.Green("存储路径已更新为: " + newPath))
}
func showStatus() {
root, ksCount, pwCount := lib.GetStatus()
fmt.Print(shell.Cyan("Keys 系统状态:\n"))
fmt.Printf(" 存储根目录: %s\n", shell.White(root))
fmt.Printf(" 密钥总数: %d\n", ksCount)
fmt.Printf(" 密码总数: %d\n", pwCount)
}
func handleListKeystores() {
fmt.Println(shell.Cyan("可用密钥列表:"))
fmt.Printf(" - %s %s\n", shell.White("default"), shell.Yellow("(内置默认)"))
files, err := os.ReadDir(lib.GetKeystorePath())
if err != nil {
fmt.Printf("共 1 个密钥\n")
return
}
count := 1
for _, fi := range files {
if !fi.IsDir() && !strings.HasPrefix(fi.Name(), ".") {
fmt.Printf(" - %s\n", shell.White(fi.Name()))
count++
}
}
fmt.Printf("共 %d 个密钥\n", count)
}
func handleCreateKeystore(name string) {
pwd := getMasterPassword()
defer safe.ZeroMemory(pwd)
ks, err := lib.CreateKeystore(name, pwd)
if err != nil {
fmt.Println(shell.Red("创建失败: " + err.Error()))
os.Exit(1)
}
ks.Close()
fmt.Println(shell.Green("密钥 '" + name + "' 创建成功。"))
}
func handleRemoveKeystore(name string) {
path := filepath.Join(lib.GetKeystorePath(), name)
if err := os.Remove(path); err != nil {
fmt.Println(shell.Red("删除失败: " + err.Error()))
os.Exit(1)
}
fmt.Println(shell.Green("密钥 '" + name + "' 已删除。"))
}
func handleExportKeystore(name, lang string) {
var pwd []byte
if name != "default" {
pwd = getMasterPassword()
defer safe.ZeroMemory(pwd)
}
ks, err := lib.LoadKeystore(name, pwd)
if err != nil {
fmt.Println(shell.Red("加载失败: " + err.Error()))
os.Exit(1)
}
defer ks.Close()
key, iv, err := ks.GetRaw()
if err != nil {
fmt.Println(shell.Red("提取失败: " + err.Error()))
os.Exit(1)
}
code, err := lib.MakeCode(lang, key, iv)
if err != nil {
fmt.Println(shell.Red("导出失败: " + err.Error()))
os.Exit(1)
}
fmt.Println(code)
}
func handlePasswordCmd(keyName string, args []string) {
op := args[0]
// 下面这些操作通常需要解密 Key所以统一获取 master password
var pwd []byte
if keyName != "default" {
pwd = getMasterPassword()
defer safe.ZeroMemory(pwd)
}
ks, err := lib.LoadKeystore(keyName, pwd)
if err != nil {
// --- 🛡️ 诱饵模式: 就算 LoadKeystore 返回随机 Key这里也会继续运行 ---
fmt.Println(shell.Red("密钥解锁失败: " + err.Error()))
os.Exit(1)
}
defer ks.Close()
key, iv, err := ks.GetRaw()
if err != nil {
fmt.Println(shell.Red("Key 提取失败: " + err.Error()))
os.Exit(1)
}
algo := "aes-gcm"
target := ""
var pwdVal []byte
// 提取参数中的算法和目标
for _, a := range args[1:] {
if strings.HasPrefix(a, "-") {
algo = a[1:]
} else {
if target == "" {
target = a
} else {
pwdVal = []byte(a)
}
}
}
switch {
case op == "list":
pwds, _ := lib.ListPasswords(keyName)
fmt.Printf("%s 保护的密码项:\n", shell.Cyan("'"+keyName+"'"))
for _, p := range pwds {
fmt.Println(" - " + p)
}
case op == "create":
if target == "" || len(pwdVal) == 0 {
fmt.Println(shell.Red("用法: keys <密钥名> create <路径> <值> [-算法]"))
os.Exit(1)
}
if err := lib.SavePassword(keyName, target, pwdVal, key, iv, algo); err != nil {
fmt.Println(shell.Red("保存失败: " + err.Error()))
os.Exit(1)
}
fmt.Printf("%s (算法: %s)。\n", shell.Green("保存成功"), algo)
case op == "remove":
if target == "" {
fmt.Println(shell.Red("用法: keys <密钥名> remove <路径>"))
os.Exit(1)
}
if err := lib.RemovePassword(keyName, target); err != nil {
fmt.Println(shell.Red("删除失败: " + err.Error()))
os.Exit(1)
}
fmt.Println(shell.Green("删除成功。"))
case op == "encrypt":
if target == "" {
fmt.Println(shell.Red("用法: keys <密钥名> encrypt <路径|原文> [-算法]"))
os.Exit(1)
}
// 尝试作为已有项加载
data, err := lib.LoadPassword(keyName, target, key, iv, "aes-gcm")
if err != nil || len(data) == 0 {
data = []byte(target)
}
sym, err := ks.NewSymmetric(algo)
if err != nil {
fmt.Println(shell.Red("算法初始化失败: " + err.Error()))
os.Exit(1)
}
enc, _ := sym.EncryptBytes(data)
fmt.Println(encoding.UrlBase64ToString(enc))
case op == "decrypt":
if target == "" {
fmt.Println(shell.Red("用法: keys <密钥名> decrypt <路径|密文> [-算法]"))
os.Exit(1)
}
dec, err := lib.LoadPassword(keyName, target, key, iv, algo)
if err != nil || len(dec) == 0 {
// 尝试作为原始密文解密
rawData, err2 := encoding.UnUrlBase64FromString(target)
if err2 == nil {
sym, err3 := ks.NewSymmetric(algo)
if err3 == nil {
dec2, err4 := sym.DecryptBytes(rawData)
if err4 == nil {
fmt.Println(string(dec2))
return
}
}
}
// 🛡️ 诱饵模式: 完全失败打印随机字符
fmt.Println(string(lib.CryptoRandDecoy(12, 24)))
return
}
fmt.Println(string(dec))
default:
fmt.Printf("%s: %s\n", shell.Red("未知密码指令"), op)
}
}
func getMasterPassword() []byte {
if p := os.Getenv("MASTER_PASSWORD"); p != "" {
return []byte(p)
}
fmt.Print("输入主密码: ")
fd := int(os.Stdin.Fd())
password, err := term.ReadPassword(fd)
fmt.Println()
if err != nil || len(password) == 0 {
fmt.Println(shell.Red("必须输入密码"))
os.Exit(1)
}
return password
}
func printUsage() {
fmt.Println(shell.Cyan("Keys CLI - 现代化本地 KMS 与密码管理器"))
fmt.Println(`
用法:
keys 启动交互式密钥管理 (Keystore Mode)
keys <密钥名> 启动该密钥对应的交互式密码管理 (Password Mode)
管理密钥 (非交互):
keys list 列出所有密钥
keys create <> 创建新密钥
keys remove <> 删除密钥
keys export <> <语言> 导出混淆代码 (go, php, java, python, js)
管理密码 (非交互):
keys <密钥名> list 列出该密钥下的所有密码项
keys <密钥名> create <> <> [-算法] 创建/更新项 (gcm/cbc/sm4/sm4-cbc)
keys <密钥名> remove <> 删除密码项
keys <密钥名> encrypt <|原文> [-算法] 加密一段文本
keys <密钥名> decrypt <|密文> [-算法] 解密并显示
系统指令:
keys -status 查看存储路径与统计状态
keys -setpath <路径> 重定向存储根目录 (持久化到 ~/.keyspath)
keys -h, --help 显示此帮助信息
环境变量:
KEYSPATH 临时指定存储路径
MASTER_PASSWORD 自动化脚本中传递主密码`)
}