diff --git a/backend/internal/cert/apply/account.go b/backend/internal/cert/apply/account.go index f1e7136..d75a273 100644 --- a/backend/internal/cert/apply/account.go +++ b/backend/internal/cert/apply/account.go @@ -3,6 +3,9 @@ package apply import ( "ALLinSSL/backend/public" "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/x509" "encoding/json" "encoding/pem" @@ -47,48 +50,158 @@ func SaveUserToDB(db *public.Sqlite, user *MyUser, Type string) error { Bytes: keyBytes, }) now := time.Now().Format("2006-01-02 15:04:05") - _, err = db.Insert(map[string]interface{}{ - "email": user.Email, - "private_key": string(pemBytes), - "reg": regBytes, - "create_time": now, - "update_time": now, - "type": Type, - }) + data, err := db.Where(`email=? and type=?`, []interface{}{user.Email, Type}).Select() + if err != nil { + return err + } + if len(data) > 0 { + _, err = db.Update(map[string]interface{}{ + "private_key": string(pemBytes), + "reg": regBytes, + "update_time": now, + }) + } else { + _, err = db.Insert(map[string]interface{}{ + "email": user.Email, + "private_key": string(pemBytes), + "reg": regBytes, + "create_time": now, + "update_time": now, + "type": Type, + }) + } return err } -func LoadUserFromDB(db *public.Sqlite, email string, Type string) (*MyUser, error) { - data, err := db.Where(`email=? and type=?`, []interface{}{email, Type}).Select() +func GetAcmeUser(email string, logger *public.Logger, accData map[string]any) (user *MyUser) { + privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + user = &MyUser{ + Email: email, + key: privateKey, + } + + if accData == nil { + return + } + reg, ok := accData["reg"].(string) + if !ok || reg == "" { + logger.Debug("acme账号未注册,注册新账号") + return + } + key, ok := accData["private_key"].(string) + if !ok || key == "" { + logger.Debug("acme账号私钥不存在,注册新账号") + return + } + + var Registration registration.Resource + localKey, err1 := public.ParsePrivateKey([]byte(key)) + if err1 != nil { + logger.Debug("acme账号私钥解析失败", err1) + return + } + err2 := json.Unmarshal([]byte(reg), &Registration) + if err2 != nil { + return + } + logger.Debug("acme账号私钥和注册信息解析成功") + user.key = localKey + user.Registration = &Registration + + return +} + +func GetAccount(db *public.Sqlite, email, ca string) (map[string]interface{}, error) { + data, err := db.Where(`email=? and type=?`, []interface{}{email, ca}).Select() if err != nil { return nil, err } if len(data) == 0 { return nil, fmt.Errorf("user not found") } - regStr, ok := data[0]["reg"].(string) - if !ok { - return nil, fmt.Errorf("invalid reg data") - } - regBytes := []byte(regStr) - privPEM, ok := data[0]["private_key"].(string) - if !ok { - return nil, fmt.Errorf("invalid private key data") - } - privateKey, err := public.ParsePrivateKey([]byte(privPEM)) + return data[0], nil +} + +func AddAccount(email, ca, Kid, HmacEncoded, CADirURL string) error { + db, err := GetSqlite() if err != nil { - return nil, err + return fmt.Errorf("failed to get sqlite: %w", err) } - var reg *registration.Resource - if len(regBytes) > 0 { - reg = ®istration.Resource{} - if err := json.Unmarshal(regBytes, reg); err != nil { - return nil, err + now := time.Now().Format("2006-01-02 15:04:05") + account := map[string]interface{}{ + "email": email, + "type": ca, + "Kid": Kid, + "HmacEncoded": HmacEncoded, + "CADirURL": CADirURL, + "create_time": now, + "update_time": now, + } + _, err = db.Insert(account) + if err != nil { + return fmt.Errorf("failed to insert account: %w", err) + } + return nil +} + +func UpdateAccount(id, email, ca, Kid, HmacEncoded, CADirURL string) error { + db, err := GetSqlite() + if err != nil { + return fmt.Errorf("failed to get sqlite: %w", err) + } + account := map[string]interface{}{ + "email": email, + "type": ca, + "Kid": Kid, + "HmacEncoded": HmacEncoded, + "CADirURL": CADirURL, + "update_time": time.Now().Format("2006-01-02 15:04:05"), + } + _, err = db.Where("id=?", []any{id}).Update(account) + if err != nil { + return fmt.Errorf("failed to update account: %w", err) + } + return nil +} + +func DeleteAccount(id string) error { + db, err := GetSqlite() + if err != nil { + return fmt.Errorf("failed to get sqlite: %w", err) + } + _, err = db.Where("id=?", []any{id}).Delete() + if err != nil { + return fmt.Errorf("failed to delete account: %w", err) + } + return nil +} + +func GetAccountList(search, ca string, p, limit int64) ([]map[string]interface{}, error) { + db, err := GetSqlite() + if err != nil { + return nil, fmt.Errorf("failed to get sqlite: %w", err) + } + whereSql := "1=1" + var whereArgs []any + limits := []int64{0, 100} + if p >= 0 && limit >= 0 { + limits = []int64{0, limit} + if p > 1 { + limits[0] = (p - 1) * limit + limits[1] = limit } } - return &MyUser{ - Email: email, - key: privateKey, - Registration: reg, - }, nil + if search != "" { + whereSql += " and (email like ? or type like ?)" + whereArgs = append(whereArgs, "%"+search+"%", "%"+search+"%") + } + if ca != "" { + if ca == "custom" { + whereSql += `and type not in ('Let's Encrypt','buypass', 'google', 'sslcom', 'zerossl')` + } else { + whereSql += " and type=?" + whereArgs = append(whereArgs, ca) + } + } + return db.Where(whereSql, whereArgs).Limit(limits).Select() } diff --git a/backend/internal/cert/apply/apply.go b/backend/internal/cert/apply/apply.go index ca71a64..67a665e 100644 --- a/backend/internal/cert/apply/apply.go +++ b/backend/internal/cert/apply/apply.go @@ -5,9 +5,6 @@ import ( "ALLinSSL/backend/internal/cert" "ALLinSSL/backend/internal/cert/apply/lego/jdcloud" "ALLinSSL/backend/public" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "encoding/json" "fmt" azcorecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" @@ -67,7 +64,7 @@ func GetSqlite() (*public.Sqlite, error) { if err != nil { return nil, err } - s.TableName = "_accounts" + s.TableName = "accounts" return s, nil } @@ -200,7 +197,62 @@ func GetDNSProvider(providerName string, creds map[string]string, httpClient *ht } } -func GetAcmeClient(db *public.Sqlite, email, algorithm, eabId, ca string, httpClient *http.Client, logger *public.Logger) (*lego.Client, error) { +func GetZeroSSLEabFromEmail(email string, httpClient *http.Client) (map[string]any, error) { + APIPath := "https://api.zerossl.com/acme/eab-credentials-email" + data := map[string]any{ + "email": email, + } + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", APIPath, strings.NewReader(string(jsonData))) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + if httpClient == nil { + httpClient = &http.Client{} + } + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("获取ZeroSSL EAB信息失败,状态码:%d", resp.StatusCode) + } + var result map[string]any + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return nil, fmt.Errorf("解析ZeroSSL EAB信息失败:%v", err) + } + if result["eab_kid"] == nil || result["eab_hmac_key"] == nil { + return nil, fmt.Errorf("ZeroSSL EAB信息不完整,缺少kid或hmacEncoded") + } + return map[string]any{ + "Kid": result["eab_kid"], + "HmacEncoded": result["eab_hmac_key"], + }, nil +} + +func getEABFromAccData(accData map[string]any, eabData *map[string]any) bool { + if accData == nil { + return false + } + kid := accData["Kid"] + hmac := accData["HmacEncoded"] + if kid != nil && hmac != nil { + *eabData = map[string]any{ + "Kid": kid, + "HmacEncoded": hmac, + } + return true + } + return false +} + +func GetAcmeClient(email, algorithm, eabId, ca string, httpClient *http.Client, logger *public.Logger) (*lego.Client, error) { var ( eabData map[string]any err error @@ -229,28 +281,45 @@ func GetAcmeClient(db *public.Sqlite, email, algorithm, eabId, ca string, httpCl return nil, fmt.Errorf("HmacEncoded不能为空") } ca = eabData["ca"].(string) - if ca == "sslcom" { - switch algorithm[0] { - case 'R', 'r': - ca = "sslcom-rsa" - case 'E', 'e': - ca = "sslcom-ecc" - } - } } - user, err := LoadUserFromDB(db, email, ca) - if err != nil { - logger.Debug("acme账号不存在,注册新账号") - privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - user = &MyUser{ - Email: email, - key: privateKey, + CADirURL := CADirURLMap[ca] + if ca == "sslcom" { + if algorithm == "EC256" || algorithm == "EC384" { + CADirURL = CADirURLMap["sslcom-ecc"] + } else { + CADirURL = CADirURLMap["sslcom-rsa"] } } + db, err := GetSqlite() + var accData map[string]any + if err != nil { + logger.Debug("获取数据库连接失败", err) + if ca != "Let's Encrypt" && ca != "zerossl" && ca != "buypass" { + return nil, fmt.Errorf("当前CA【%s】 需要从数据库获取预设账号,但是连接数据库失败,请稍后重试,err:%w", ca, err) + } + } else { + defer db.Close() + accData, err = GetAccount(db, email, ca) + if err != nil || accData == nil { + logger.Debug("获取acme账号信息失败") + if ca != "Let's Encrypt" && ca != "zerossl" && ca != "buypass" { + return nil, fmt.Errorf("未找到%s账号信息,请先在账号管理中添加%s账号, email:%s", ca, ca, email) + } + } + if CADirURL == "" { + accCADirURL, ok := accData["CADirURL"].(string) + if !ok || accCADirURL == "" { + logger.Debug("未找到此CA的请求地址") + return nil, fmt.Errorf("未找到CA【%s】请求地址,请先在账号管理中检查%s账号, email:%s", ca, ca, email) + } + CADirURL = accCADirURL + } + } + user := GetAcmeUser(email, logger, accData) config := lego.NewConfig(user) config.Certificate.KeyType = AlgorithmMap[algorithm] - config.CADirURL = CADirURLMap[ca] + config.CADirURL = CADirURL if httpClient != nil { config.HTTPClient = httpClient } @@ -260,6 +329,20 @@ func GetAcmeClient(db *public.Sqlite, email, algorithm, eabId, ca string, httpCl } if user.Registration == nil { logger.Debug("正在注册账号:" + email) + if eabData == nil { + // 走新的逻辑,eab已合并到账号中 + if !getEABFromAccData(accData, &eabData) { + switch ca { + case "zerossl": + eabData, err = GetZeroSSLEabFromEmail(email, httpClient) + if err != nil { + return nil, fmt.Errorf("获取ZeroSSL EAB信息失败: %v", err) + } + case "sslcom", "google": + return nil, fmt.Errorf("未找到EAB信息,请在账号管理中添加%s账号", ca) + } + } + } var reg *registration.Resource if eabData != nil { Kid := eabData["Kid"].(string) @@ -279,7 +362,7 @@ func GetAcmeClient(db *public.Sqlite, email, algorithm, eabId, ca string, httpCl err = SaveUserToDB(db, user, ca) if err != nil { - return nil, err + logger.Debug("acme账号注册成功,但保存到数据库失败", err) } logger.Debug("acme账号注册并保存成功") } @@ -350,12 +433,7 @@ func GetCert(runId string, domainArr []string, endDay int, logger *public.Logger func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { log.Logger = logger.GetLogger() - db, err := GetSqlite() - if err != nil { - return nil, err - } - defer db.Close() - + var err error email, ok := cfg["email"].(string) if !ok { return nil, fmt.Errorf("参数错误:email") @@ -575,7 +653,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { logger.Debug("正在申请证书,域名: " + domains) os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(closeCname)) // 创建 ACME 客户端 - client, err := GetAcmeClient(db, email, algorithm, eabId, ca, httpClient, logger) + client, err := GetAcmeClient(email, algorithm, eabId, ca, httpClient, logger) if err != nil { return nil, err } diff --git a/backend/migrations/init.go b/backend/migrations/init.go index d88bf15..4dd4cea 100644 --- a/backend/migrations/init.go +++ b/backend/migrations/init.go @@ -278,7 +278,7 @@ func init() { Isql := fmt.Sprintf( `INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('log_path', 'logs/ALLinSSL.log', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'workflow_log_path', 'logs/workflows/', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); -INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'timeout', '3600', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); +INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'timeout', '86400', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'https', '0', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('session_key', '%s', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('secure', '/%s', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); @@ -304,34 +304,30 @@ INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES _, err = dbAcc.Exec(` PRAGMA journal_mode=WAL; - create table IF NOT EXISTS _accounts - ( - id integer not null - constraint _accounts_pk - primary key autoincrement, - private_key TEXT not null, - reg TEXT not null, - email TEXT not null, - create_time TEXT, - update_time TEXT, - type TEXT - ); - - create table IF NOT EXISTS _eab + create table if not exists accounts ( id integer not null - constraint _eab_pk + constraint _accounts_pk primary key autoincrement, - name TEXT, - Kid TEXT not null, - HmacEncoded TEXT not null, - ca TEXT not null, + private_key TEXT , + reg TEXT , + email TEXT not null, + type TEXT not null, + Kid TEXT , + HmacEncoded TEXT , + CADirURL TEXT , create_time TEXT, - update_time TEXT, - mail TEXT not null + update_time TEXT ); - `) + insertSql := ` + insert into accounts (id, private_key, reg, email, create_time, update_time, type, Kid, HmacEncoded) + select a.id, a.private_key, a.reg, a.email, a.create_time, a.update_time, case when a.type like 'sslcom%' then 'sslcom' else a.type end, b.Kid,b.HmacEncoded + from _accounts a + left join _eab b + on a.email = b.mail and a.type like b.ca||'%'; +` + insertDefaultData(dbAcc, "accounts", insertSql) } func insertDefaultData(db *sql.DB, table, insertSQL string) {