From cfc95399a9b66e6cf70f5c8ccce1287df4e8b343 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 29 May 2026 23:42:56 +0800 Subject: [PATCH] feat: do not encode private key to pkcs#8 when obtaining the certificate --- internal/certacme/account.go | 2 +- internal/certacme/client_obtain.go | 12 +++- pkg/utils/cert/converter.go | 94 +++++++++++++++++++++++++++--- 3 files changed, 97 insertions(+), 11 deletions(-) diff --git a/internal/certacme/account.go b/internal/certacme/account.go index cb03b1d85..0de5116bb 100644 --- a/internal/certacme/account.go +++ b/internal/certacme/account.go @@ -45,7 +45,7 @@ func CreateACMEAccount(ctx context.Context, config *ACMEConfig, email string) (* return nil, err } - keyPEM, err := xcert.ConvertECPrivateKeyToPEM(key) + keyPEM, err := xcert.ConvertECPrivateKeyToPEM(key, false) if err != nil { return nil, err } diff --git a/internal/certacme/client_obtain.go b/internal/certacme/client_obtain.go index 3a75831d8..61907e186 100644 --- a/internal/certacme/client_obtain.go +++ b/internal/certacme/client_obtain.go @@ -20,6 +20,7 @@ import ( "github.com/certimate-go/certimate/internal/certacme/certifiers" "github.com/certimate-go/certimate/internal/domain" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type ObtainCertificateRequest struct { @@ -172,11 +173,20 @@ func (c *ACMEClient) ObtainCertificate(ctx context.Context, request *ObtainCerti } } + // lego 自 v5 起返回的私钥 PEM 内容使用 PKCS#8 格式编码, + // 这里转换为 PKCS#1 或 SEC1 格式编码,以满足更好的兼容性。 + privkeyPEM := strings.TrimSpace(string(resp.PrivateKey)) + if t1, err := xcert.ParsePrivateKeyFromPEM(privkeyPEM); err == nil { + if t2, err := xcert.ConvertPrivateKeyToPEM(t1, false); err == nil { + privkeyPEM = t2 + } + } + return &ObtainCertificateResponse{ CSR: strings.TrimSpace(string(resp.CSR)), FullChainCertificate: strings.TrimSpace(string(resp.Certificate)), IssuerCertificate: strings.TrimSpace(string(resp.IssuerCertificate)), - PrivateKey: strings.TrimSpace(string(resp.PrivateKey)), + PrivateKey: privkeyPEM, ACMEAcctUrl: c.account.ACMEAcctUrl, ACMECertUrl: resp.CertURL, ARIReplaced: req.ReplacesCertID != "", diff --git a/pkg/utils/cert/converter.go b/pkg/utils/cert/converter.go index 8848ec5dc..99bf17ba6 100644 --- a/pkg/utils/cert/converter.go +++ b/pkg/utils/cert/converter.go @@ -1,7 +1,9 @@ package cert import ( + "crypto" "crypto/ecdsa" + "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" @@ -28,27 +30,101 @@ func ConvertCertificateToPEM(cert *x509.Certificate) (_certPEM string, _err erro return string(pem.EncodeToMemory(block)), nil } -// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。 +// 将 rsa.PrivateKey 或 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。 // // 入参: -// - privkey: ecdsa.PrivateKey 对象。 +// - privkey: rsa.PrivateKey 或 ecdsa.PrivateKey 对象。 +// - pkcs8: 是否使用 PKCS#8 格式编码。 // // 出参: // - privkeyPEM: 私钥 PEM 内容。 // - err: 错误。 -func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (_privkeyPEM string, _err error) { +func ConvertPrivateKeyToPEM(privkey crypto.PrivateKey, pkcs8 bool) (_privkeyPEM string, _err error) { if privkey == nil { return "", fmt.Errorf("the input private key is nil") } - data, _err := x509.MarshalECPrivateKey(privkey) - if _err != nil { - return "", fmt.Errorf("failed to marshal EC private key: %w", _err) + switch t := privkey.(type) { + case *rsa.PrivateKey: + return ConvertRSAPrivateKeyToPEM(t, pkcs8) + + case *ecdsa.PrivateKey: + return ConvertECPrivateKeyToPEM(t, pkcs8) } - block := &pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: data, + return "", fmt.Errorf("unknown private key type") +} + +// 将 rsa.PrivateKey 对象转换为 PEM 编码的字符串。 +// +// 入参: +// - privkey: rsa.PrivateKey 对象。 +// - pkcs8: 是否使用 PKCS#8 格式编码。否则,使用 PKCS#1 格式编码。 +// +// 出参: +// - privkeyPEM: 私钥 PEM 内容。 +// - err: 错误。 +func ConvertRSAPrivateKeyToPEM(privkey *rsa.PrivateKey, pkcs8 bool) (_privkeyPEM string, _err error) { + if privkey == nil { + return "", fmt.Errorf("the input private key is nil") + } + + var data []byte + if pkcs8 { + data, _err = x509.MarshalPKCS8PrivateKey(privkey) + if _err != nil { + return "", fmt.Errorf("failed to marshal RSA private key: %w", _err) + } + } else { + data = x509.MarshalPKCS1PrivateKey(privkey) + if data == nil { + _err = fmt.Errorf("failed to marshal RSA private key") + return "", _err + } + } + + var block *pem.Block + if pkcs8 { + block = &pem.Block{Type: "PRIVATE KEY", Bytes: data} + } else { + block = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: data} + } + + return string(pem.EncodeToMemory(block)), nil +} + +// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。 +// +// 入参: +// - privkey: ecdsa.PrivateKey 对象。 +// - pkcs8: 是否使用 PKCS#8 格式编码。否则,使用 SEC1 格式编码。 +// +// 出参: +// - privkeyPEM: 私钥 PEM 内容。 +// - err: 错误。 +func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey, pkcs8 bool) (_privkeyPEM string, _err error) { + if privkey == nil { + return "", fmt.Errorf("the input private key is nil") + } + + var data []byte + if pkcs8 { + data, _err = x509.MarshalPKCS8PrivateKey(privkey) + if _err != nil { + return "", fmt.Errorf("failed to marshal EC private key: %w", _err) + } + } else { + data, _err = x509.MarshalECPrivateKey(privkey) + if _err != nil { + return "", fmt.Errorf("failed to marshal EC private key: %w", _err) + } + } + + var block *pem.Block + if pkcs8 { + block = &pem.Block{Type: "PRIVATE KEY", Bytes: data} + } else { + block = &pem.Block{Type: "EC PRIVATE KEY", Bytes: data} } return string(pem.EncodeToMemory(block)), nil