commit 14831e38c8269365ed9b8e32784a824e76d70fea Author: ZhuoQinghui <1302344380@qq.com> Date: Wed Oct 30 18:24:36 2024 +0800 first diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..704924c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea + +log +vendor +/config.yml +acme-mana.exe +acme-mana.pid +acme-mana.sock \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6aedf5e --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module acme-client + +go 1.23.2 + +require ( + github.com/go-acme/lego/v4 v4.19.2 + gopkg.in/yaml.v3 v3.0.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8150f26 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-acme/lego/v4 v4.19.2 h1:Y8hrmMvWETdqzzkRly7m98xtPJJivWFsgWi8fcvZo+Y= +github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6MbYxNtlQ= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2411b26 --- /dev/null +++ b/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "acme-client/src" + "bufio" + "encoding/json" + "github.com/go-acme/lego/v4/log" + "io" + "net/http" + "os" + "strings" +) + +func main() { + // 读取server参数,如果没有提示添加 + config := src.GetClientConfig() + server := config.Server + if server == "" { + log.Println("请输入服务端地址; 如: http://acme.server.com:8080") + reader := bufio.NewReader(os.Stdin) + serverAddr, err := reader.ReadString('\n') + if err != nil { + log.Fatal(err) + } + serverAddr = strings.Trim(serverAddr, "\r\n") + config.Server = serverAddr + src.WriteConfig() + } + + // 读取服务器RSA公钥, 没有提示添加 + rsaPublicKey := config.RsaPublicKey + if rsaPublicKey == "" { + log.Println("请输入服务端下发的RAS公钥;") + reader := bufio.NewReader(os.Stdin) + rsaPublicKeyContent, err := reader.ReadString('\n') + if err != nil { + log.Fatal(err) + } + rsaPublicKeyContent = strings.Trim(rsaPublicKeyContent, "\r\n") + config.RsaPublicKey = rsaPublicKeyContent + src.WriteConfig() + } + + // 获取指令, 如果没有则提示添加 + args := os.Args + if len(args) < 2 { + log.Println("请输入命令; 如: add") + log.Println("或者输入 help 查看帮助") + return + } + onCommand() +} + +func onCommand() { + command := os.Args[1] + switch command { + case "help": + showHelp() + case "-s": + onServerCommand() + default: + log.Fatalf("Unknown command: %s", 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名称") +} +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() + } +} + +func onServerCommand() { + args := os.Args + if len(args) < 3 { + log.Fatal("参数错误, 请检查") + } + command := args[2] + switch command { + case "list": + showServerList() + default: + log.Fatal("参数错误, 请检查") + } +} + +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) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + result := src.Result{} + err = json.Unmarshal(body, &result) + if err != nil { + log.Fatal(err) + } + if result.Code != 200 { + log.Fatal("读取数据出错; ", result.Msg) + } + data := result.Data + text := src.DecryptByToken(token, data) + println(text) +} diff --git a/src/config.go b/src/config.go new file mode 100644 index 0000000..b46e8a1 --- /dev/null +++ b/src/config.go @@ -0,0 +1,106 @@ +package src + +import ( + "github.com/go-acme/lego/v4/log" + "github.com/go-acme/lego/v4/platform/config/env" + "gopkg.in/yaml.v3" + "os" +) + +func ReadConfig() ClientConfig { + InitConfig() + file, err := os.ReadFile(getConfigFile()) + if err != nil { + log.Fatal(err) + } + var conf ClientConfig + err = yaml.Unmarshal(file, &conf) + if err != nil { + log.Fatal(err) + } + return conf +} + +func InitConfig() { + confFile := getConfigFile() + _, err := os.Stat(confFile) + + if os.IsNotExist(err) { + log.Infof("客户端配置文件不存在,自动创建默认配置文件") + conf := defaultConfig() + file, err := os.Create(confFile) + if err != nil { + log.Fatal(err) + } + defer file.Close() + data, err := yaml.Marshal(conf) + if err != nil { + log.Fatal(err) + } + _, err = file.Write(data) + if err != nil { + log.Fatal(err) + } + } +} + +func FlushConfig() { + ReadConfig() +} + +func WriteConfig() { + confFile := getConfigFile() + data, err := yaml.Marshal(GetClientConfig()) + if err != nil { + log.Fatal(err) + } + err = os.WriteFile(confFile, data, 0755) + if err != nil { + log.Fatal(err) + } +} + +func defaultConfig() *ClientConfig { + return &ClientConfig{ + Server: "", + RsaPublicKey: "", + } +} + +func getConfigFile() string { + return env.GetOrDefaultString(ENV_CLIENT_CONF_FILE, "config.yml") +} + +const ENV_CLIENT_CONF_FILE = "ACME_MANA_CONF_FILE" + +type ClientConfig struct { + Server string + RsaPublicKey string + Domains []Domain +} + +type Domain struct { + Name string + CertFile string + KeyFile string +} + +func (conf *ClientConfig) FindDomain(name string) *Domain { + for _, domain := range conf.Domains { + if domain.Name == name { + return &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/crypto/crypto.go b/src/crypto/crypto.go new file mode 100644 index 0000000..3bd4d93 --- /dev/null +++ b/src/crypto/crypto.go @@ -0,0 +1,96 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "io" +) + +func EncryptAES(key []byte, plaintext []byte) []byte { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic(err) + } + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) + return ciphertext +} + +func DecryptAES(key []byte, ciphertext []byte) []byte { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + if len(ciphertext) < aes.BlockSize { + panic("ciphertext too short") + } + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(ciphertext, ciphertext) + return ciphertext +} + +func EncryptRSA(publicKey *rsa.PublicKey, message []byte) ([]byte, error) { + ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, message) + if err != nil { + return nil, err + } + return ciphertext, nil +} + +func EncryptRSABase64(publicKey string, content []byte) ([]byte, error) { + pubKey, err := base64.StdEncoding.DecodeString(publicKey) + if err != nil { + return nil, err + } + key, err := x509.ParsePKCS1PublicKey(pubKey) + if err != nil { + return nil, err + } + return EncryptRSA(key, content) +} + +func DecryptRSA(privateKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) { + plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext) + if err != nil { + return nil, err + } + return plaintext, nil +} + +func DecryptRSABase64(privateKey string, ciphertext []byte) ([]byte, error) { + priKey, err := base64.StdEncoding.DecodeString(privateKey) + if err != nil { + return nil, err + } + key, err := x509.ParsePKCS1PrivateKey(priKey) + if err != nil { + return nil, err + } + return DecryptRSA(key, ciphertext) +} + +func GenRSA() (priKey string, pubKey string, err error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return "", "", err + } + priKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + + publicKey := &privateKey.PublicKey + publicKeyBytes := x509.MarshalPKCS1PublicKey(publicKey) + pubKey = base64.StdEncoding.EncodeToString(publicKeyBytes) + priKey = base64.StdEncoding.EncodeToString(priKeyBytes) + err = nil + return +} diff --git a/src/token.go b/src/token.go new file mode 100644 index 0000000..a2224b3 --- /dev/null +++ b/src/token.go @@ -0,0 +1,76 @@ +package src + +import ( + "acme-client/src/crypto" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "github.com/go-acme/lego/v4/log" + "math/big" +) + +const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +func randomStr(length int) string { + b := make([]byte, length) + for i := range b { + randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + b[i] = charset[randomIndex.Int64()] + } + return string(b) +} + +func GenToken() (token string, encryptToken string) { + token = randomStr(32) + key := GetClientConfig().RsaPublicKey + + tokenBytes, err := crypto.EncryptRSABase64(key, []byte(token)) + 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) + 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) +} diff --git a/src/variable.go b/src/variable.go new file mode 100644 index 0000000..5929221 --- /dev/null +++ b/src/variable.go @@ -0,0 +1,17 @@ +package src + +var clientConfig ClientConfig = ReadConfig() + +func GetClientConfig() *ClientConfig { + return &clientConfig +} + +func ReloadClientConfig() { + clientConfig = ReadConfig() +} + +type Result struct { + Code int + Msg string + Data string +}