342 lines
8.7 KiB
Go
342 lines
8.7 KiB
Go
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 自动化脚本中传递主密码`)
|
||
}
|