mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
feat(provider): support pfx encoder in local/ssh/ftp/s3 deployers
This commit is contained in:
@@ -118,7 +118,7 @@ func (s *CertificateService) DownloadCertificate(ctx context.Context, req *dtos.
|
||||
{
|
||||
const pfxPassword = "certimate"
|
||||
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certificate.Certificate, certificate.PrivateKey, pfxPassword)
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certificate.Certificate, certificate.PrivateKey, pfxPassword, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func init() {
|
||||
FilePathForCrtOnlyServer: xmaps.GetString(options.ProviderExtendedConfig, "filePathForCrtOnlyServer"),
|
||||
FilePathForCrtOnlyIntermedia: xmaps.GetString(options.ProviderExtendedConfig, "filePathForCrtOnlyIntermedia"),
|
||||
PfxPassword: xmaps.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
||||
PfxEncoder: xmaps.GetString(options.ProviderExtendedConfig, "pfxEncoder"),
|
||||
JksAlias: xmaps.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
||||
JksKeypass: xmaps.GetString(options.ProviderExtendedConfig, "jksKeypass"),
|
||||
JksStorepass: xmaps.GetString(options.ProviderExtendedConfig, "jksStorepass"),
|
||||
|
||||
@@ -19,6 +19,7 @@ func init() {
|
||||
FilePathForCrtOnlyServer: xmaps.GetString(options.ProviderExtendedConfig, "filePathForCrtOnlyServer"),
|
||||
FilePathForCrtOnlyIntermedia: xmaps.GetString(options.ProviderExtendedConfig, "filePathForCrtOnlyIntermedia"),
|
||||
PfxPassword: xmaps.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
||||
PfxEncoder: xmaps.GetString(options.ProviderExtendedConfig, "pfxEncoder"),
|
||||
JksAlias: xmaps.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
||||
JksKeypass: xmaps.GetString(options.ProviderExtendedConfig, "jksKeypass"),
|
||||
JksStorepass: xmaps.GetString(options.ProviderExtendedConfig, "jksStorepass"),
|
||||
|
||||
@@ -31,6 +31,7 @@ func init() {
|
||||
ObjectKeyForCrtOnlyServer: xmaps.GetString(options.ProviderExtendedConfig, "objectKeyForCrtOnlyServer"),
|
||||
ObjectKeyForCrtOnlyIntermedia: xmaps.GetString(options.ProviderExtendedConfig, "objectKeyForCrtOnlyIntermedia"),
|
||||
PfxPassword: xmaps.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
||||
PfxEncoder: xmaps.GetString(options.ProviderExtendedConfig, "pfxEncoder"),
|
||||
JksAlias: xmaps.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
||||
JksKeypass: xmaps.GetString(options.ProviderExtendedConfig, "jksKeypass"),
|
||||
JksStorepass: xmaps.GetString(options.ProviderExtendedConfig, "jksStorepass"),
|
||||
|
||||
@@ -49,6 +49,7 @@ func init() {
|
||||
FilePathForCrtOnlyServer: xmaps.GetString(options.ProviderExtendedConfig, "filePathForCrtOnlyServer"),
|
||||
FilePathForCrtOnlyIntermedia: xmaps.GetString(options.ProviderExtendedConfig, "filePathForCrtOnlyIntermedia"),
|
||||
PfxPassword: xmaps.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
||||
PfxEncoder: xmaps.GetString(options.ProviderExtendedConfig, "pfxEncoder"),
|
||||
JksAlias: xmaps.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
||||
JksKeypass: xmaps.GetString(options.ProviderExtendedConfig, "jksKeypass"),
|
||||
JksStorepass: xmaps.GetString(options.ProviderExtendedConfig, "jksStorepass"),
|
||||
|
||||
@@ -136,7 +136,7 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*cert
|
||||
// Azure Key Vault 不支持导入带有 Certificate Chain 的 PEM 证书。
|
||||
// Issue Link: https://github.com/Azure/azure-cli/issues/19017
|
||||
// 暂时的解决方法是,将 PEM 证书转换成 PFX 格式,然后再导入。
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, "")
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, "", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform certificate from PEM to PFX: %w", err)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, pri
|
||||
}
|
||||
|
||||
// 转换证书格式
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, "")
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, "", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform certificate from PEM to PFX: %w", err)
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func (d *Deployer) updateSiteCertificate(ctx context.Context, siteType, siteName
|
||||
if pfxRequried {
|
||||
// 转换证书格式
|
||||
certPFXPassword := "certimate"
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, certPFXPassword)
|
||||
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, certPFXPassword, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to transform certificate from PEM to PFX: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer/providers/local/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
FILE_FORMAT_PEM = string(domain.CertificateFormatTypePEM)
|
||||
FILE_FORMAT_PFX = string(domain.CertificateFormatTypePFX)
|
||||
FILE_FORMAT_JKS = string(domain.CertificateFormatTypeJKS)
|
||||
FILE_FORMAT_PEM = shared.FILE_FORMAT_PEM
|
||||
FILE_FORMAT_PFX = shared.FILE_FORMAT_PFX
|
||||
FILE_FORMAT_JKS = shared.FILE_FORMAT_JKS
|
||||
)
|
||||
|
||||
const (
|
||||
PFX_ENCODER_LEGACYRC2 = shared.PFX_ENCODER_LEGACYRC2
|
||||
PFX_ENCODER_LEGACYDES = shared.PFX_ENCODER_LEGACYDES
|
||||
PFX_ENCODER_MODERN2023 = shared.PFX_ENCODER_MODERN2023
|
||||
PFX_ENCODER_MODERN2026 = shared.PFX_ENCODER_MODERN2026
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/certimate-go/certimate/internal/tools/ftp"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer"
|
||||
shared "github.com/certimate-go/certimate/pkg/core/deployer/providers/local/shared"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
)
|
||||
|
||||
@@ -36,6 +37,9 @@ type DeployerConfig struct {
|
||||
// PFX 导出密码。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时必填。
|
||||
PfxPassword string `json:"pfxPassword,omitempty"`
|
||||
// PFX 编码器。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时可选。
|
||||
PfxEncoder string `json:"pfxEncoder,omitempty"`
|
||||
// JKS 别名。
|
||||
// 证书格式为 [FILE_FORMAT_JKS] 时必填。
|
||||
JksAlias string `json:"jksAlias,omitempty"`
|
||||
@@ -151,7 +155,12 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
return nil, fmt.Errorf("config `pfxPassword` is required")
|
||||
}
|
||||
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
|
||||
pfxEncoder, err := shared.ResolvePfxEncoder(d.config.PfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config `pfxEncoder` is invalid: %w", err)
|
||||
}
|
||||
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword, pfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ func init() {
|
||||
fp.DefineInt64(&fFtpPort, "FTPPORT")
|
||||
fp.DefineString(&fFtpUsername, "FTPUSERNAME")
|
||||
fp.DefineString(&fFtpPassword, "FTPPASSWORD")
|
||||
fp.DefineString(&fFilePathForCrt, "OUTPUTCERTPATH")
|
||||
fp.DefineString(&fFilePathForKey, "OUTPUTKEYPATH")
|
||||
fp.DefineString(&fFilePathForCrt, "FILEPATHFORCRT")
|
||||
fp.DefineString(&fFilePathForKey, "FILEPATHFORKEY")
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -40,8 +40,8 @@ Shell command to run this test:
|
||||
--FTP_FTPPORT=21 \
|
||||
--FTP_FTPUSERNAME="USER" \
|
||||
--FTP_FTPPASSWORD="PASS" \
|
||||
--FTP_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \
|
||||
--FTP_OUTPUTKEYPATH="/path/to/your-output-key.pem"
|
||||
--FTP_FILEPATHFORCRT="/path/to/your-output-cert.pem" \
|
||||
--FTP_FILEPATHFORKEY="/path/to/your-output-key.pem"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
@@ -15,3 +15,10 @@ const (
|
||||
FILE_FORMAT_PFX = string(domain.CertificateFormatTypePFX)
|
||||
FILE_FORMAT_JKS = string(domain.CertificateFormatTypeJKS)
|
||||
)
|
||||
|
||||
const (
|
||||
PFX_ENCODER_LEGACYRC2 = "LegacyRC2"
|
||||
PFX_ENCODER_LEGACYDES = "LegacyDES"
|
||||
PFX_ENCODER_MODERN2023 = "Modern2023"
|
||||
PFX_ENCODER_MODERN2026 = "Modern2026"
|
||||
)
|
||||
|
||||
@@ -37,6 +37,9 @@ type DeployerConfig struct {
|
||||
// PFX 导出密码。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时必填。
|
||||
PfxPassword string `json:"pfxPassword,omitempty"`
|
||||
// PFX 编码器。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时可选。
|
||||
PfxEncoder string `json:"pfxEncoder,omitempty"`
|
||||
// JKS 别名。
|
||||
// 证书格式为 [FILE_FORMAT_JKS] 时必填。
|
||||
JksAlias string `json:"jksAlias,omitempty"`
|
||||
@@ -139,7 +142,12 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
return nil, fmt.Errorf("config `pfxPassword` is required")
|
||||
}
|
||||
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
|
||||
pfxEncoder, err := ResolvePfxEncoder(d.config.PfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config `pfxEncoder` is invalid: %w", err)
|
||||
}
|
||||
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword, pfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ var (
|
||||
fFilePathForCrt string
|
||||
fFilePathForKey string
|
||||
fPfxPassword string
|
||||
fPfxEncoder string
|
||||
fJksAlias string
|
||||
fJksKeypass string
|
||||
fJksStorepass string
|
||||
@@ -25,9 +26,10 @@ var (
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fFilePathForCrt, "OUTPUTCERTPATH")
|
||||
fp.DefineString(&fFilePathForKey, "OUTPUTKEYPATH")
|
||||
fp.DefineString(&fFilePathForCrt, "FILEPATHFORCRT")
|
||||
fp.DefineString(&fFilePathForKey, "FILEPATHFORKEY")
|
||||
fp.DefineString(&fPfxPassword, "PFXPASSWORD")
|
||||
fp.DefineString(&fPfxEncoder, "PFXENCODER")
|
||||
fp.DefineString(&fJksAlias, "JKSALIAS")
|
||||
fp.DefineString(&fJksKeypass, "JKSKEYPASS")
|
||||
fp.DefineString(&fJksStorepass, "JKSSTOREPASS")
|
||||
@@ -42,9 +44,10 @@ Shell command to run this test:
|
||||
go test -v ./local_test.go -args \
|
||||
--LOCAL_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--LOCAL_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert" \
|
||||
--LOCAL_OUTPUTKEYPATH="/path/to/your-output-key" \
|
||||
--LOCAL_FILEPATHFORCRT="/path/to/your-output-cert" \
|
||||
--LOCAL_FILEPATHFORKEY="/path/to/your-output-key" \
|
||||
--LOCAL_PFXPASSWORD="your-pfx-password" \
|
||||
--LOCAL_PFXENCODER="modern2023" \
|
||||
--LOCAL_JKSALIAS="your-jks-alias" \
|
||||
--LOCAL_JKSKEYPASS="your-jks-keypass" \
|
||||
--LOCAL_JKSSTOREPASS="your-jks-storepass" \
|
||||
@@ -77,6 +80,7 @@ func TestProvider(t *testing.T) {
|
||||
FileFormat: impl.FILE_FORMAT_PFX,
|
||||
FilePathForCrt: fFilePathForCrt + ".pfx",
|
||||
PfxPassword: fPfxPassword,
|
||||
PfxEncoder: fPfxEncoder,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"software.sslmate.com/src/go-pkcs12"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer/providers/local"
|
||||
)
|
||||
|
||||
const (
|
||||
FILE_FORMAT_PEM = local.FILE_FORMAT_PEM
|
||||
FILE_FORMAT_PFX = local.FILE_FORMAT_PFX
|
||||
FILE_FORMAT_JKS = local.FILE_FORMAT_JKS
|
||||
)
|
||||
|
||||
const (
|
||||
PFX_ENCODER_LEGACYRC2 = local.PFX_ENCODER_LEGACYRC2
|
||||
PFX_ENCODER_LEGACYDES = local.PFX_ENCODER_LEGACYDES
|
||||
PFX_ENCODER_MODERN2023 = local.PFX_ENCODER_MODERN2023
|
||||
PFX_ENCODER_MODERN2026 = local.PFX_ENCODER_MODERN2026
|
||||
)
|
||||
|
||||
func ResolvePfxEncoder(encoderName string) (*pkcs12.Encoder, error) {
|
||||
return local.ResolvePfxEncoder(encoderName)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"software.sslmate.com/src/go-pkcs12"
|
||||
)
|
||||
|
||||
func ResolvePfxEncoder(encoderName string) (*pkcs12.Encoder, error) {
|
||||
var encoder *pkcs12.Encoder
|
||||
|
||||
if encoderName != "" {
|
||||
if strings.EqualFold(encoderName, PFX_ENCODER_LEGACYRC2) {
|
||||
encoder = pkcs12.LegacyRC2
|
||||
} else if strings.EqualFold(encoderName, PFX_ENCODER_LEGACYDES) {
|
||||
encoder = pkcs12.LegacyDES
|
||||
} else if strings.EqualFold(encoderName, PFX_ENCODER_MODERN2023) {
|
||||
encoder = pkcs12.Modern2023
|
||||
} else if strings.EqualFold(encoderName, PFX_ENCODER_MODERN2026) {
|
||||
encoder = pkcs12.Modern2026
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported encoder name: '%s'", encoderName)
|
||||
}
|
||||
}
|
||||
|
||||
return encoder, nil
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer/providers/local/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
FILE_FORMAT_PEM = string(domain.CertificateFormatTypePEM)
|
||||
FILE_FORMAT_PFX = string(domain.CertificateFormatTypePFX)
|
||||
FILE_FORMAT_JKS = string(domain.CertificateFormatTypeJKS)
|
||||
FILE_FORMAT_PEM = shared.FILE_FORMAT_PEM
|
||||
FILE_FORMAT_PFX = shared.FILE_FORMAT_PFX
|
||||
FILE_FORMAT_JKS = shared.FILE_FORMAT_JKS
|
||||
)
|
||||
|
||||
const (
|
||||
PFX_ENCODER_LEGACYRC2 = shared.PFX_ENCODER_LEGACYRC2
|
||||
PFX_ENCODER_LEGACYDES = shared.PFX_ENCODER_LEGACYDES
|
||||
PFX_ENCODER_MODERN2023 = shared.PFX_ENCODER_MODERN2023
|
||||
PFX_ENCODER_MODERN2026 = shared.PFX_ENCODER_MODERN2026
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/certimate-go/certimate/internal/tools/s3"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer/providers/local/shared"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
)
|
||||
|
||||
@@ -44,6 +45,9 @@ type DeployerConfig struct {
|
||||
// PFX 导出密码。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时必填。
|
||||
PfxPassword string `json:"pfxPassword,omitempty"`
|
||||
// PFX 编码器。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时可选。
|
||||
PfxEncoder string `json:"pfxEncoder,omitempty"`
|
||||
// JKS 别名。
|
||||
// 证书格式为 [FILE_FORMAT_JKS] 时必填。
|
||||
JksAlias string `json:"jksAlias,omitempty"`
|
||||
@@ -133,7 +137,12 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
return nil, fmt.Errorf("config `pfxPassword` is required")
|
||||
}
|
||||
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
|
||||
pfxEncoder, err := shared.ResolvePfxEncoder(d.config.PfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config `pfxEncoder` is invalid: %w", err)
|
||||
}
|
||||
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword, pfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("S3_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fSshHost string
|
||||
fAccessKey string
|
||||
fSecretKey string
|
||||
fRegion string
|
||||
fBucket string
|
||||
fFilePathForCrt string
|
||||
fFilePathForKey string
|
||||
fp = tester.Args("S3_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fSshHost string
|
||||
fAccessKey string
|
||||
fSecretKey string
|
||||
fRegion string
|
||||
fBucket string
|
||||
fObjectKeyForCrt string
|
||||
fObjectKeyForKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -28,8 +28,8 @@ func init() {
|
||||
fp.DefineString(&fSecretKey, "SECRETKEY")
|
||||
fp.DefineString(&fRegion, "REGION")
|
||||
fp.DefineString(&fBucket, "BUCKET")
|
||||
fp.DefineString(&fFilePathForCrt, "OUTPUTCERTPATH")
|
||||
fp.DefineString(&fFilePathForKey, "OUTPUTKEYPATH")
|
||||
fp.DefineString(&fObjectKeyForCrt, "OBJECTKEYFORCRT")
|
||||
fp.DefineString(&fObjectKeyForKey, "OBJECTKEYFORKEY")
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -43,8 +43,8 @@ Shell command to run this test:
|
||||
--S3_SECRETKEY="your-secret-key" \
|
||||
--S3_REGION="your-region" \
|
||||
--S3_BUCKET="your-bucket" \
|
||||
--S3_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \
|
||||
--S3_OUTPUTKEYPATH="/path/to/your-output-key.pem"
|
||||
--S3_OBJECTKEYFORCRT="/path/to/your-output-cert.pem" \
|
||||
--S3_OBJECTKEYFORKEY="/path/to/your-output-key.pem"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
@@ -57,8 +57,8 @@ func TestProvider(t *testing.T) {
|
||||
Region: fRegion,
|
||||
Bucket: fBucket,
|
||||
FileFormat: impl.FILE_FORMAT_PEM,
|
||||
ObjectKeyForCrt: fFilePathForCrt + ".pem",
|
||||
ObjectKeyForKey: fFilePathForKey + ".pem",
|
||||
ObjectKeyForCrt: fObjectKeyForCrt + ".pem",
|
||||
ObjectKeyForKey: fObjectKeyForKey + ".pem",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer/providers/local/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
FILE_FORMAT_PEM = string(domain.CertificateFormatTypePEM)
|
||||
FILE_FORMAT_PFX = string(domain.CertificateFormatTypePFX)
|
||||
FILE_FORMAT_JKS = string(domain.CertificateFormatTypeJKS)
|
||||
FILE_FORMAT_PEM = shared.FILE_FORMAT_PEM
|
||||
FILE_FORMAT_PFX = shared.FILE_FORMAT_PFX
|
||||
FILE_FORMAT_JKS = shared.FILE_FORMAT_JKS
|
||||
)
|
||||
|
||||
const (
|
||||
PFX_ENCODER_LEGACYRC2 = shared.PFX_ENCODER_LEGACYRC2
|
||||
PFX_ENCODER_LEGACYDES = shared.PFX_ENCODER_LEGACYDES
|
||||
PFX_ENCODER_MODERN2023 = shared.PFX_ENCODER_MODERN2023
|
||||
PFX_ENCODER_MODERN2026 = shared.PFX_ENCODER_MODERN2026
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/certimate-go/certimate/internal/tools/ssh"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer"
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer/providers/local/shared"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
xssh "github.com/certimate-go/certimate/pkg/utils/ssh"
|
||||
)
|
||||
@@ -59,6 +60,9 @@ type DeployerConfig struct {
|
||||
// PFX 导出密码。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时必填。
|
||||
PfxPassword string `json:"pfxPassword,omitempty"`
|
||||
// PFX 编码器。
|
||||
// 证书格式为 [FILE_FORMAT_PFX] 时可选。
|
||||
PfxEncoder string `json:"pfxEncoder,omitempty"`
|
||||
// JKS 别名。
|
||||
// 证书格式为 [FILE_FORMAT_JKS] 时必填。
|
||||
JksAlias string `json:"jksAlias,omitempty"`
|
||||
@@ -134,10 +138,6 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
switch d.config.FileFormat {
|
||||
case FILE_FORMAT_PEM:
|
||||
{
|
||||
if d.config.PfxPassword == "" {
|
||||
return nil, fmt.Errorf("config `pfxPassword` is required")
|
||||
}
|
||||
|
||||
if d.config.FilePathForKey != "" {
|
||||
if err := xssh.WriteRemoteString(sshClient.RawClient(), d.config.FilePathForKey, privkeyPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload private key file: %w", err)
|
||||
@@ -169,7 +169,16 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
|
||||
case FILE_FORMAT_PFX:
|
||||
{
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
|
||||
if d.config.PfxPassword == "" {
|
||||
return nil, fmt.Errorf("config `pfxPassword` is required")
|
||||
}
|
||||
|
||||
pfxEncoder, err := shared.ResolvePfxEncoder(d.config.PfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config `pfxEncoder` is invalid: %w", err)
|
||||
}
|
||||
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword, pfxEncoder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to transform certificate to PFX: %w", err)
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ func init() {
|
||||
fp.DefineInt64(&fSshPort, "SSHPORT")
|
||||
fp.DefineString(&fSshUsername, "SSHUSERNAME")
|
||||
fp.DefineString(&fSshPassword, "SSHPASSWORD")
|
||||
fp.DefineString(&fFilePathForCrt, "OUTPUTCERTPATH")
|
||||
fp.DefineString(&fFilePathForKey, "OUTPUTKEYPATH")
|
||||
fp.DefineString(&fFilePathForCrt, "FILEPATHFORCRT")
|
||||
fp.DefineString(&fFilePathForKey, "FILEPATHFORKEY")
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -40,8 +40,8 @@ Shell command to run this test:
|
||||
--SSH_SSHPORT=22 \
|
||||
--SSH_SSHUSERNAME="root" \
|
||||
--SSH_SSHPASSWORD="password" \
|
||||
--SSH_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \
|
||||
--SSH_OUTPUTKEYPATH="/path/to/your-output-key.pem"
|
||||
--SSH_FILEPATHFORCRT="/path/to/your-output-cert.pem" \
|
||||
--SSH_FILEPATHFORKEY="/path/to/your-output-key.pem"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// 出参:
|
||||
// - data: PFX 格式的证书数据。
|
||||
// - err: 错误。
|
||||
func TransformCertificateFromPEMToPFX(certPEM string, privkeyPEM string, pfxPassword string) ([]byte, error) {
|
||||
func TransformCertificateFromPEMToPFX(certPEM string, privkeyPEM string, pfxPassword string, pfxEncoder *pkcs12.Encoder) ([]byte, error) {
|
||||
blocks := decodePEMBlocks([]byte(certPEM))
|
||||
|
||||
certs := make([]*x509.Certificate, 0, len(blocks))
|
||||
@@ -41,13 +41,18 @@ func TransformCertificateFromPEMToPFX(certPEM string, privkeyPEM string, pfxPass
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encoder := pfxEncoder
|
||||
if encoder == nil {
|
||||
encoder = pkcs12.Legacy
|
||||
}
|
||||
|
||||
var pfxData []byte
|
||||
if len(certs) == 0 {
|
||||
return nil, fmt.Errorf("failed to decode certificate PEM")
|
||||
} else if len(certs) == 1 {
|
||||
pfxData, err = pkcs12.Legacy.Encode(privkey, certs[0], nil, pfxPassword)
|
||||
pfxData, err = encoder.Encode(privkey, certs[0], nil, pfxPassword)
|
||||
} else {
|
||||
pfxData, err = pkcs12.Legacy.Encode(privkey, certs[0], certs[1:], pfxPassword)
|
||||
pfxData, err = encoder.Encode(privkey, certs[0], certs[1:], pfxPassword)
|
||||
}
|
||||
|
||||
return pfxData, err
|
||||
|
||||
@@ -98,10 +98,7 @@ const BizDeployNodeConfigFieldsProviderFTP = () => {
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_PFX}>
|
||||
<Form.Item
|
||||
label={t("workflow_node.deploy.form.shared_pfx_password.label")}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.shared_pfx_password.tooltip") }}></span>}
|
||||
>
|
||||
<Form.Item label={t("workflow_node.deploy.form.shared_pfx_password.label")}>
|
||||
<Space.Compact className="w-full">
|
||||
<Form.Item name={[parentNamePath, "pfxPassword"]} initialValue={initialValues.pfxPassword} rules={[formRule]} noStyle>
|
||||
<Input placeholder={t("workflow_node.deploy.form.shared_pfx_password.placeholder")} />
|
||||
@@ -113,6 +110,22 @@ const BizDeployNodeConfigFieldsProviderFTP = () => {
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "pfxEncoder"]}
|
||||
initialValue={initialValues.pfxEncoder}
|
||||
label={t("workflow_node.deploy.form.shared_pfx_encoder.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Select
|
||||
options={["LegacyRC2", "LegacyDES", "Modern2023", "Modern2026"].map((s) => ({
|
||||
key: s,
|
||||
label: t(`workflow_node.deploy.form.shared_pfx_encoder.option.${s.toLowerCase()}.label`),
|
||||
value: s,
|
||||
}))}
|
||||
placeholder={t("workflow_node.deploy.form.shared_pfx_encoder.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_JKS}>
|
||||
@@ -187,6 +200,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
filePathForCrtOnlyServer: z.string().max(256).nullish(),
|
||||
filePathForCrtOnlyIntermedia: z.string().max(256).nullish(),
|
||||
pfxPassword: z.string().nullish(),
|
||||
pfxEncoder: z.string().nullish(),
|
||||
jksAlias: z.string().nullish(),
|
||||
jksKeypass: z.string().nullish(),
|
||||
jksStorepass: z.string().nullish(),
|
||||
|
||||
+18
-4
@@ -343,10 +343,7 @@ const BizDeployNodeConfigFieldsProviderLocal = () => {
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_PFX}>
|
||||
<Form.Item
|
||||
label={t("workflow_node.deploy.form.shared_pfx_password.label")}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.shared_pfx_password.tooltip") }}></span>}
|
||||
>
|
||||
<Form.Item label={t("workflow_node.deploy.form.shared_pfx_password.label")}>
|
||||
<Space.Compact className="w-full">
|
||||
<Form.Item name={[parentNamePath, "pfxPassword"]} initialValue={initialValues.pfxPassword} rules={[formRule]} noStyle>
|
||||
<Input placeholder={t("workflow_node.deploy.form.shared_pfx_password.placeholder")} />
|
||||
@@ -358,6 +355,22 @@ const BizDeployNodeConfigFieldsProviderLocal = () => {
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "pfxEncoder"]}
|
||||
initialValue={initialValues.pfxEncoder}
|
||||
label={t("workflow_node.deploy.form.shared_pfx_encoder.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Select
|
||||
options={["LegacyRC2", "LegacyDES", "Modern2023", "Modern2026"].map((s) => ({
|
||||
key: s,
|
||||
label: t(`workflow_node.deploy.form.shared_pfx_encoder.option.${s.toLowerCase()}.label`),
|
||||
value: s,
|
||||
}))}
|
||||
placeholder={t("workflow_node.deploy.form.shared_pfx_encoder.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_JKS}>
|
||||
@@ -522,6 +535,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
filePathForCrtOnlyServer: z.string().max(256).nullish(),
|
||||
filePathForCrtOnlyIntermedia: z.string().max(256).nullish(),
|
||||
pfxPassword: z.string().nullish(),
|
||||
pfxEncoder: z.string().nullish(),
|
||||
jksAlias: z.string().nullish(),
|
||||
jksKeypass: z.string().nullish(),
|
||||
jksStorepass: z.string().nullish(),
|
||||
|
||||
@@ -114,10 +114,7 @@ const BizDeployNodeConfigFieldsProviderS3 = () => {
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_PFX}>
|
||||
<Form.Item
|
||||
label={t("workflow_node.deploy.form.shared_pfx_password.label")}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.shared_pfx_password.tooltip") }}></span>}
|
||||
>
|
||||
<Form.Item label={t("workflow_node.deploy.form.shared_pfx_password.label")}>
|
||||
<Space.Compact className="w-full">
|
||||
<Form.Item name={[parentNamePath, "pfxPassword"]} initialValue={initialValues.pfxPassword} rules={[formRule]} noStyle>
|
||||
<Input placeholder={t("workflow_node.deploy.form.shared_pfx_password.placeholder")} />
|
||||
@@ -129,6 +126,22 @@ const BizDeployNodeConfigFieldsProviderS3 = () => {
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "pfxEncoder"]}
|
||||
initialValue={initialValues.pfxEncoder}
|
||||
label={t("workflow_node.deploy.form.shared_pfx_encoder.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Select
|
||||
options={["LegacyRC2", "LegacyDES", "Modern2023", "Modern2026"].map((s) => ({
|
||||
key: s,
|
||||
label: t(`workflow_node.deploy.form.shared_pfx_encoder.option.${s.toLowerCase()}.label`),
|
||||
value: s,
|
||||
}))}
|
||||
placeholder={t("workflow_node.deploy.form.shared_pfx_encoder.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_JKS}>
|
||||
@@ -207,6 +220,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
objectKeyForCrtOnlyServer: z.string().max(256).nullish(),
|
||||
objectKeyForCrtOnlyIntermedia: z.string().max(256).nullish(),
|
||||
pfxPassword: z.string().nullish(),
|
||||
pfxEncoder: z.string().nullish(),
|
||||
jksAlias: z.string().nullish(),
|
||||
jksKeypass: z.string().nullish(),
|
||||
jksStorepass: z.string().nullish(),
|
||||
|
||||
@@ -309,10 +309,7 @@ const BizDeployNodeConfigFieldsProviderSSH = () => {
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_PFX}>
|
||||
<Form.Item
|
||||
label={t("workflow_node.deploy.form.shared_pfx_password.label")}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.shared_pfx_password.tooltip") }}></span>}
|
||||
>
|
||||
<Form.Item label={t("workflow_node.deploy.form.shared_pfx_password.label")}>
|
||||
<Space.Compact className="w-full">
|
||||
<Form.Item name={[parentNamePath, "pfxPassword"]} initialValue={initialValues.pfxPassword} rules={[formRule]} noStyle>
|
||||
<Input placeholder={t("workflow_node.deploy.form.shared_pfx_password.placeholder")} />
|
||||
@@ -324,6 +321,22 @@ const BizDeployNodeConfigFieldsProviderSSH = () => {
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "pfxEncoder"]}
|
||||
initialValue={initialValues.pfxEncoder}
|
||||
label={t("workflow_node.deploy.form.shared_pfx_encoder.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Select
|
||||
options={["LegacyRC2", "LegacyDES", "Modern2023", "Modern2026"].map((s) => ({
|
||||
key: s,
|
||||
label: t(`workflow_node.deploy.form.shared_pfx_encoder.option.${s.toLowerCase()}.label`),
|
||||
value: s,
|
||||
}))}
|
||||
placeholder={t("workflow_node.deploy.form.shared_pfx_encoder.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFileFormat === FORMAT_JKS}>
|
||||
@@ -480,6 +493,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
filePathForCrtOnlyServer: z.string().max(256).nullish(),
|
||||
filePathForCrtOnlyIntermedia: z.string().max(256).nullish(),
|
||||
pfxPassword: z.string().nullish(),
|
||||
pfxEncoder: z.string().nullish(),
|
||||
jksAlias: z.string().nullish(),
|
||||
jksKeypass: z.string().nullish(),
|
||||
jksStorepass: z.string().nullish(),
|
||||
|
||||
@@ -534,8 +534,25 @@
|
||||
},
|
||||
"shared_pfx_password": {
|
||||
"label": "PFX password",
|
||||
"placeholder": "Please enter PFX password",
|
||||
"tooltip": "For more information, see <a href=\"https://learn.microsoft.com/en-us/windows-hardware/drivers/install/personal-information-exchange---pfx--files\" target=\"_blank\">https://learn.microsoft.com/en-us/windows-hardware/drivers/install/personal-information-exchange---pfx--files</a>"
|
||||
"placeholder": "Please enter PFX password"
|
||||
},
|
||||
"shared_pfx_encoder": {
|
||||
"label": "PFX encoder (Optional)",
|
||||
"placeholder": "Please select PFX encoder",
|
||||
"option": {
|
||||
"legacyrc2": {
|
||||
"label": "LegacyRC2 (RC2, compatible with older systems, but less secure)"
|
||||
},
|
||||
"legacydes": {
|
||||
"label": "LegacyDES (3DES + SHA-1, default)"
|
||||
},
|
||||
"modern2023": {
|
||||
"label": "Modern2023 (AES-256-CBC + SHA-256 MAC, compatible with OpenSSL v1.1.1+)"
|
||||
},
|
||||
"modern2026": {
|
||||
"label": "Modern2026 (AES-256-CBC + PBMAC1 MAC, compatible with OpenSSL v3.4.0+)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shared_jks_alias": {
|
||||
"label": "JKS alias",
|
||||
|
||||
@@ -533,8 +533,25 @@
|
||||
},
|
||||
"shared_pfx_password": {
|
||||
"label": "PFX 导出密码",
|
||||
"placeholder": "请输入 PFX 导出密码",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://learn.microsoft.com/zh-cn/windows-hardware/drivers/install/personal-information-exchange---pfx--files\" target=\"_blank\">https://learn.microsoft.com/zh-cn/windows-hardware/drivers/install/personal-information-exchange---pfx--files</a>"
|
||||
"placeholder": "请输入 PFX 导出密码"
|
||||
},
|
||||
"shared_pfx_encoder": {
|
||||
"label": "PFX 编码器(可选)",
|
||||
"placeholder": "请选择 PFX 编码器",
|
||||
"option": {
|
||||
"legacyrc2": {
|
||||
"label": "LegacyRC2(RC2,兼容旧系统,但安全性较低)"
|
||||
},
|
||||
"legacydes": {
|
||||
"label": "LegacyDES(3DES + SHA-1,默认值)"
|
||||
},
|
||||
"modern2023": {
|
||||
"label": "Modern2023(AES-256-CBC + SHA-256 MAC,适配 OpenSSL v1.1.1+)"
|
||||
},
|
||||
"modern2026": {
|
||||
"label": "Modern2026(AES-256-CBC + PBMAC1 MAC,适配 OpenSSL v3.4.0+)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shared_jks_alias": {
|
||||
"label": "JKS 别名",
|
||||
|
||||
Reference in New Issue
Block a user