diff --git a/cmd/certs_storage.go b/cmd/certs_storage.go index 5709f1a8a1..5e2ef21544 100644 --- a/cmd/certs_storage.go +++ b/cmd/certs_storage.go @@ -43,6 +43,8 @@ const ( type CertificatesStorage struct { rootPath string archivePath string + bundle bool + bupem bool pem bool pfx bool pfxPassword string @@ -54,6 +56,8 @@ func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage { return &CertificatesStorage{ rootPath: filepath.Join(ctx.String("path"), baseCertificatesFolderName), archivePath: filepath.Join(ctx.String("path"), baseArchivesFolderName), + bundle: !ctx.Bool("no-bundle"), + bupem: ctx.Bool("bupem"), pem: ctx.Bool("pem"), pfx: ctx.Bool("pfx"), pfxPassword: ctx.String("pfx.pass"), @@ -102,8 +106,8 @@ func (s *CertificatesStorage) SaveResource(certRes *certificate.Resource) { if err != nil { log.Fatalf("Unable to save PrivateKey for domain %s\n\t%v", domain, err) } - } else if s.pem || s.pfx { - // we don't have the private key; can't write the .pem or .pfx file + } else if s.bupem || s.pem || s.pfx { + // we don't have the private key; can't write the .bupem, .pem or .pfx file log.Fatalf("Unable to save PEM or PFX without private key for domain %s. Are you using a CSR?", domain) } @@ -181,6 +185,20 @@ func (s *CertificatesStorage) WriteCertificateFiles(domain string, certRes *cert return fmt.Errorf("unable to save key file: %w", err) } + if s.bupem { // how we make the bupem depends on whether the bundle flag is true. bottom-up == private key first. + if s.bundle { // bundle is true by default. So certRes.Certificate already consists of OurX509+Int+CA + err = s.WriteFile(domain, ".bupem", bytes.Join([][]byte{certRes.PrivateKey, certRes.Certificate}, nil)) + if err != nil { + return fmt.Errorf("unable to save bottom-up PEM file: %w", err) + } + } else { // when bundle is false, we want pKey+OurX509+Int+CA + err = s.WriteFile(domain, ".bupem", bytes.Join([][]byte{certRes.PrivateKey, certRes.Certificate, certRes.IssuerCertificate}, nil)) + if err != nil { + return fmt.Errorf("unable to save bottom-up PEM file: %w", err) + } + } + } + if s.pem { err = s.WriteFile(domain, ".pem", bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil)) if err != nil { diff --git a/cmd/flags.go b/cmd/flags.go index 8413426f58..ca91a7607c 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -118,6 +118,10 @@ func CreateFlags(defaultPath string) []cli.Flag { Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries.", Value: 10, }, + &cli.BoolFlag{ + Name: "bupem", + Usage: "Generate an additional 'bottom-up' .bupem (PEM, base64) file by concatenating the .key, .crt and issuer .crt together, in that order.", + }, &cli.BoolFlag{ Name: "pem", Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.", diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index f3a65e5f8a..34c848159a 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -20,6 +20,7 @@ COMMANDS: GLOBAL OPTIONS: --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) + --bupem Generate an additional 'bottom-up' .bupem (PEM, base64) file by concatenating the .key, .crt and issuer .crt together, in that order. (default: false) --cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30) --csr value, -c value Certificate signing request filename, if an external CSR is to be used. --dns value Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.