This commit is contained in:
ZhuoQinghui 2024-10-30 18:24:36 +08:00
commit 14831e38c8
8 changed files with 445 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.idea
log
vendor
/config.yml
acme-mana.exe
acme-mana.pid
acme-mana.sock

8
go.mod Normal file
View File

@ -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
)

12
go.sum Normal file
View File

@ -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=

122
main.go Normal file
View File

@ -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)
}

106
src/config.go Normal file
View File

@ -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
//}

96
src/crypto/crypto.go Normal file
View File

@ -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
}

76
src/token.go Normal file
View File

@ -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)
}

17
src/variable.go Normal file
View File

@ -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
}