feat(cli): add format option for PFX encoding (#2063)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
parent
9c1a856b73
commit
4eab81a9eb
5 changed files with 84 additions and 30 deletions
|
@ -3,10 +3,10 @@ package cmd
|
|||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -55,17 +55,27 @@ type CertificatesStorage struct {
|
|||
pem bool
|
||||
pfx bool
|
||||
pfxPassword string
|
||||
pfxFormat string
|
||||
filename string // Deprecated
|
||||
}
|
||||
|
||||
// NewCertificatesStorage create a new certificates storage.
|
||||
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
|
||||
pfxFormat := ctx.String("pfx.format")
|
||||
|
||||
switch pfxFormat {
|
||||
case "DES", "RC2", "SHA256":
|
||||
default:
|
||||
log.Fatalf("Invalid PFX format: %s", pfxFormat)
|
||||
}
|
||||
|
||||
return &CertificatesStorage{
|
||||
rootPath: filepath.Join(ctx.String("path"), baseCertificatesFolderName),
|
||||
archivePath: filepath.Join(ctx.String("path"), baseArchivesFolderName),
|
||||
pem: ctx.Bool("pem"),
|
||||
pfx: ctx.Bool("pfx"),
|
||||
pfxPassword: ctx.String("pfx.pass"),
|
||||
pfxFormat: pfxFormat,
|
||||
filename: ctx.String("filename"),
|
||||
}
|
||||
}
|
||||
|
@ -218,14 +228,9 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
|
|||
return fmt.Errorf("unable to load Certificate for domain %s: %w", domain, err)
|
||||
}
|
||||
|
||||
issuerCertPemBlock, _ := pem.Decode(certRes.IssuerCertificate)
|
||||
if issuerCertPemBlock == nil {
|
||||
return fmt.Errorf("unable to parse Issuer Certificate for domain %s", domain)
|
||||
}
|
||||
|
||||
issuerCert, err := x509.ParseCertificate(issuerCertPemBlock.Bytes)
|
||||
certChain, err := getCertificateChain(certRes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load Issuer Certificate for domain %s: %w", domain, err)
|
||||
return fmt.Errorf("unable to get certificate chain for domain %s: %w", domain, err)
|
||||
}
|
||||
|
||||
keyPemBlock, _ := pem.Decode(certRes.PrivateKey)
|
||||
|
@ -251,7 +256,12 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
|
|||
return fmt.Errorf("unsupported PrivateKey type '%s' for domain %s", keyPemBlock.Type, domain)
|
||||
}
|
||||
|
||||
pfxBytes, err := pkcs12.Encode(rand.Reader, privateKey, cert, []*x509.Certificate{issuerCert}, s.pfxPassword)
|
||||
encoder, err := getPFXEncoder(s.pfxFormat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PFX encoder: %w", err)
|
||||
}
|
||||
|
||||
pfxBytes, err := encoder.Encode(privateKey, cert, certChain, s.pfxPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to encode PFX data for domain %s: %w", domain, err)
|
||||
}
|
||||
|
@ -285,6 +295,42 @@ func (s *CertificatesStorage) MoveToArchive(domain string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getCertificateChain(certRes *certificate.Resource) ([]*x509.Certificate, error) {
|
||||
chainCertPemBlock, rest := pem.Decode(certRes.IssuerCertificate)
|
||||
if chainCertPemBlock == nil {
|
||||
return nil, errors.New("unable to parse Issuer Certificate")
|
||||
}
|
||||
|
||||
var certChain []*x509.Certificate
|
||||
for chainCertPemBlock != nil {
|
||||
chainCert, err := x509.ParseCertificate(chainCertPemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse Chain Certificate: %w", err)
|
||||
}
|
||||
|
||||
certChain = append(certChain, chainCert)
|
||||
chainCertPemBlock, rest = pem.Decode(rest) // Try decoding the next pem block
|
||||
}
|
||||
|
||||
return certChain, nil
|
||||
}
|
||||
|
||||
func getPFXEncoder(pfxFormat string) (*pkcs12.Encoder, error) {
|
||||
var encoder *pkcs12.Encoder
|
||||
switch pfxFormat {
|
||||
case "SHA256":
|
||||
encoder = pkcs12.Modern2023
|
||||
case "DES":
|
||||
encoder = pkcs12.LegacyDES
|
||||
case "RC2":
|
||||
encoder = pkcs12.LegacyRC2
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid PFX format: %s", pfxFormat)
|
||||
}
|
||||
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
|
||||
func sanitizedDomain(domain string) string {
|
||||
safe, err := idna.ToASCII(strings.NewReplacer(":", "-", "*", "_").Replace(domain))
|
||||
|
|
18
cmd/flags.go
18
cmd/flags.go
|
@ -133,13 +133,21 @@ func CreateFlags(defaultPath string) []cli.Flag {
|
|||
Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "pfx",
|
||||
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
|
||||
Name: "pfx",
|
||||
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
|
||||
EnvVars: []string{"LEGO_PFX"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pfx.pass",
|
||||
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
|
||||
Value: pkcs12.DefaultPassword,
|
||||
Name: "pfx.pass",
|
||||
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
|
||||
Value: pkcs12.DefaultPassword,
|
||||
EnvVars: []string{"LEGO_PFX_PASSWORD"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pfx.format",
|
||||
Usage: "The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256.",
|
||||
Value: "RC2",
|
||||
EnvVars: []string{"LEGO_PFX_FORMAT"},
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "cert.timeout",
|
||||
|
|
|
@ -44,8 +44,9 @@ GLOBAL OPTIONS:
|
|||
--http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
|
||||
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)
|
||||
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together. (default: false)
|
||||
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. (default: false)
|
||||
--pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit")
|
||||
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. (default: false) [$LEGO_PFX]
|
||||
--pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") [$LEGO_PFX_PASSWORD]
|
||||
--pfx.format value The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256. (default: "RC2") [$LEGO_PFX_FORMAT]
|
||||
--cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
|
||||
--user-agent value Add to the user-agent sent to the CA to identify an application embedding lego-cli
|
||||
--help, -h show help
|
||||
|
|
8
go.mod
8
go.mod
|
@ -74,14 +74,14 @@ require (
|
|||
github.com/vultr/govultr/v2 v2.17.2
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/oauth2 v0.9.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/api v0.111.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -155,8 +155,8 @@ require (
|
|||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect
|
||||
|
|
19
go.sum
19
go.sum
|
@ -652,9 +652,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -769,12 +768,12 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@ -783,8 +782,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -915,5 +914,5 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
|
|
Loading…
Reference in a new issue