diff --git a/main.go b/main.go index 2411b26..5992d3a 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,12 @@ import ( "acme-client/src" "bufio" "encoding/json" + "fmt" "github.com/go-acme/lego/v4/log" "io" "net/http" "os" + "strconv" "strings" ) @@ -56,31 +58,114 @@ func onCommand() { switch command { case "help": showHelp() + case "list": + showList() + case "add": + addConf() + case "del": + delConf() + case "edit": + editConf() + case "get": + getCert() case "-s": onServerCommand() default: - log.Fatalf("Unknown command: %s", command) + log.Fatalf("不支持的指令: %s\n您可以使用 help 查看帮助", command) } } +// 帮助 func showHelp() { - log.Printf("help\t查看帮助") - log.Printf("list\t查看配置列表") - log.Printf("add\t添加配置") - log.Printf("del\t删除配置") - log.Printf("edit\t编辑配置") - log.Printf("-s list\t查看服务端已配置的Domain名称") + fmt.Printf("help\t查看帮助\n") + fmt.Printf("list\t查看配置列表\n") + fmt.Printf("add\t添加配置\n") + fmt.Printf("del\t删除配置\n") + fmt.Printf("edit\t修改配置\n") + fmt.Printf("get\t从获取端获取证书,会自动判断有效期\n") + fmt.Printf("\t[-f]\t从获取端获取证书,不判断有效期\n") + fmt.Printf("-s list\t查看服务端已配置的Domain名称\n") } + +// 显示配置列表 func showList() { - for i := range src.GetClientConfig().Domains { - domain := src.GetClientConfig().Domains[i] - log.Printf("- %s", domain.Name) - log.Printf(" - cert: %s", domain.CertFile) - log.Printf(" - key: %s", domain.KeyFile) - log.Println() + domains := src.GetClientConfig().Domains + if len(domains) == 0 { + fmt.Println("暂无配置; 您可以使用 add 命令添加配置") + return + } + for i := range domains { + domain := domains[i] + fmt.Printf("%d. %s\n", i, domain.Name) + fmt.Printf(" - 证书文件: %s\n", domain.CertFile) + fmt.Printf(" - 密钥文件: %s\n", domain.KeyFile) + fmt.Printf(" - 详情文件: %s\n", domain.InfoFile) + fmt.Println() } } +// 添加配置 +func addConf() { + domain := &src.Domain{} + name := scanConf("请输入服务端已配置名称;可以通过 -s list 查看", "名称不能为空") + domain.Name = name + domain.CertFile = scanConf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem", "证书文件路径不能为空") + domain.KeyFile = scanConf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem", "私钥文件路径不能为空") + domain.InfoFile = scanConf("请输入详情文件存放全路径;如: /data/cert/info.json", "详情文件路径不能为空") + config := src.GetClientConfig() + config.Domains = append(config.Domains, *domain) + src.WriteConfig() + fmt.Println("添加成功") + showList() +} + +// 删除配置 +func delConf() { + fmt.Println("当前配置: ") + showList() + indexInt, _ := selectDomain("请输入要删除的序号") + config := src.GetClientConfig() + config.Domains = append(config.Domains[:indexInt], config.Domains[indexInt+1:]...) + src.WriteConfig() + fmt.Println("删除成功") + showList() +} + +// 修改配置 +func editConf() { + fmt.Println("当前配置") + showList() + _, domain := selectDomain("请输入要修改的序号") + msg := fmt.Sprintf("请输入服务端已配置名称;可以通过 -s list 查看\n当前配置: %s\n", domain.Name) + domain.Name = scanConf(msg, "名称不能为空") + certFile := fmt.Sprintf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem\n当前配置: %s\n", domain.CertFile) + domain.CertFile = scanConf(certFile, "证书文件路径不能为空") + msg = fmt.Sprintf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem\n当前配置: %s\n", domain.KeyFile) + domain.KeyFile = scanConf(msg, "私钥文件路径不能为空") + msg = fmt.Sprintf("请输入详情文件存放全路径;如: /data/cert/info.json\n当前配置: %s\n", domain.InfoFile) + domain.InfoFile = scanConf(msg, "详情文件路径不能为空") + src.WriteConfig() + fmt.Println("修改成功") + showList() +} + +func getCert() { + _, domain := selectDomain("请选择要获取证书的配置编号") + args := os.Args + isDoGet := len(args) >= 3 && args[3] == "-f" + if !isDoGet { + infoFile := domain.InfoFile + os.Stat(infoFile) + //info, err := os.ReadFile(infoFile) + //keyFile := domain.KeyFile + } + + //config := src.GetClientConfig() + //domains := config.Domains + //config.FindDomain(args[2]) +} + +// 服务端命令 func onServerCommand() { args := os.Args if len(args) < 3 { @@ -95,13 +180,14 @@ func onServerCommand() { } } +// 从服务端获取域名列表 func showServerList() { server := src.GetClientConfig().Server token, encryptToken := src.GenToken() url := server + "/api/v1/domain/list?token=" + encryptToken resp, err := http.Get(url) if err != nil { - log.Fatal(err) + log.Fatal("获取服务端数据失败, 请检查 server 地址是否正确") } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) @@ -118,5 +204,52 @@ func showServerList() { } data := result.Data text := src.DecryptByToken(token, data) - println(text) + d := &[]src.SDomain{} + err = json.Unmarshal([]byte(text), d) + if err != nil { + log.Fatal(err) + } + for i := range *d { + domain := (*d)[i] + fmt.Printf("- %s\n", domain.Name) + fmt.Printf("\t认证域名: [ ") + for j := range domain.Host { + host := domain.Host[j] + fmt.Printf("%s ", host) + } + fmt.Printf("]\n") + } +} + +// 读取用户输入 +func scanConf(msg string, errMsg string) string { + for { + log.Println(msg) + reader := bufio.NewReader(os.Stdin) + name, err := reader.ReadString('\n') + if err != nil { + fmt.Println("读取失败;", err) + continue + } + name = strings.Trim(name, "\r\n") + if name == "" { + fmt.Println(errMsg) + continue + } + return name + } +} + +// 选择域名 +func selectDomain(msg string) (int, *src.Domain) { + index := scanConf(msg, "序号不能为空") + indexInt, err := strconv.Atoi(index) + if err != nil { + log.Fatal("序号错误") + } + config := src.GetClientConfig() + if indexInt >= len(config.Domains) || indexInt < 0 { + log.Fatal("序号超出范围") + } + return indexInt, &config.Domains[indexInt] } diff --git a/src/config.go b/src/config.go index b46e8a1..c5b0835 100644 --- a/src/config.go +++ b/src/config.go @@ -83,6 +83,7 @@ type Domain struct { Name string CertFile string KeyFile string + InfoFile string } func (conf *ClientConfig) FindDomain(name string) *Domain { @@ -93,14 +94,3 @@ func (conf *ClientConfig) FindDomain(name string) *Domain { } return nil } - -//func (conf *ClientConfig) SetServer(server string) *ClientConfig { -// conf.Server = server -// WriteConfig() -// return conf -//} -//func (conf *ClientConfig) SetPubKey(pubkey string) *ClientConfig { -// conf.RsaPublicKey = pubkey -// WriteConfig() -// return conf -//} diff --git a/src/token.go b/src/token.go index a2224b3..3f0873a 100644 --- a/src/token.go +++ b/src/token.go @@ -2,8 +2,6 @@ package src import ( "acme-client/src/crypto" - "crypto/aes" - "crypto/cipher" "crypto/rand" "encoding/base64" "encoding/hex" @@ -30,47 +28,16 @@ func GenToken() (token string, encryptToken string) { if err != nil { log.Fatal("Error encrypting data:", err) } - //encryptToken = base64.StdEncoding.EncodeToString(tokenBytes) encryptToken = hex.EncodeToString(tokenBytes) - //keyBytes, err := base64.StdEncoding.DecodeString(key) - //if err != nil { - // log.Fatal("Error decoding public key:", err) - //} - //pubKey, err := x509.ParsePKCS1PublicKey(keyBytes) - //if err != nil { - // log.Fatal("Error parsing public key:", err) - //} - // - //enToken, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, []byte(token), nil) - //if err != nil { - // log.Fatal("Error encrypting data:", err) - //} - //encryptToken = base64.StdEncoding.EncodeToString(enToken) return } func DecryptByToken(token string, data string) string { - // 使用 token 进行 DES 解密 - key := []byte(token) - dataBytes, err := base64.StdEncoding.DecodeString(data) + content, err := base64.StdEncoding.DecodeString(data) if err != nil { log.Fatal("Error decoding data:", err) } - - block, err := aes.NewCipher(key) - if err != nil { - log.Fatal("Error creating cipher:", err) - } - - if len(dataBytes) < aes.BlockSize { - log.Fatal("ciphertext too short") - } - - iv := dataBytes[:aes.BlockSize] - dataBytes = dataBytes[aes.BlockSize:] - - mode := cipher.NewCBCDecrypter(block, iv) - mode.CryptBlocks(dataBytes, dataBytes) - return string(dataBytes) + res := crypto.DecryptAES([]byte(token), content) + return string(res) } diff --git a/src/variable.go b/src/variable.go index 5929221..f5dbebf 100644 --- a/src/variable.go +++ b/src/variable.go @@ -15,3 +15,9 @@ type Result struct { Msg string Data string } + +type SDomain struct { + Name string + Email string + Host []string +}