forked from TrueCloudLab/lego
Added a --pfx, and --pfx.pass option to generate a PKCS#12 (.pfx) file. (#1387)
This commit is contained in:
parent
f4b153f26f
commit
a6855cb69f
5 changed files with 107 additions and 15 deletions
|
@ -2,8 +2,12 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -15,6 +19,7 @@ import (
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,6 +45,8 @@ type CertificatesStorage struct {
|
||||||
rootPath string
|
rootPath string
|
||||||
archivePath string
|
archivePath string
|
||||||
pem bool
|
pem bool
|
||||||
|
pfx bool
|
||||||
|
pfxPassword string
|
||||||
filename string // Deprecated
|
filename string // Deprecated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +56,8 @@ func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
|
||||||
rootPath: filepath.Join(ctx.GlobalString("path"), baseCertificatesFolderName),
|
rootPath: filepath.Join(ctx.GlobalString("path"), baseCertificatesFolderName),
|
||||||
archivePath: filepath.Join(ctx.GlobalString("path"), baseArchivesFolderName),
|
archivePath: filepath.Join(ctx.GlobalString("path"), baseArchivesFolderName),
|
||||||
pem: ctx.GlobalBool("pem"),
|
pem: ctx.GlobalBool("pem"),
|
||||||
|
pfx: ctx.GlobalBool("pfx"),
|
||||||
|
pfxPassword: ctx.GlobalString("pfx.pass"),
|
||||||
filename: ctx.GlobalString("filename"),
|
filename: ctx.GlobalString("filename"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,22 +97,15 @@ func (s *CertificatesStorage) SaveResource(certRes *certificate.Resource) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if certRes.PrivateKey != nil {
|
|
||||||
// if we were given a CSR, we don't know the private key
|
// if we were given a CSR, we don't know the private key
|
||||||
err = s.WriteFile(domain, ".key", certRes.PrivateKey)
|
if certRes.PrivateKey != nil {
|
||||||
|
err = s.WriteCertificateFiles(domain, certRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to save PrivateKey for domain %s\n\t%v", domain, err)
|
log.Fatalf("Unable to save PrivateKey for domain %s\n\t%v", domain, err)
|
||||||
}
|
}
|
||||||
|
} else if s.pem || s.pfx {
|
||||||
if s.pem {
|
// we don't have the private key; can't write the .pem or .pfx file
|
||||||
err = s.WriteFile(domain, ".pem", bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil))
|
log.Fatalf("Unable to save PEM or PFX without private key for domain %s. Are you using a CSR?", domain)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to save Certificate and PrivateKey in .pem for domain %s\n\t%v", domain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if s.pem {
|
|
||||||
// we don't have the private key; can't write the .pem file
|
|
||||||
log.Fatalf("Unable to save pem without private key for domain %s\n\t%v; are you using a CSR?", domain, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
|
jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
|
||||||
|
@ -174,6 +176,81 @@ func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) e
|
||||||
return os.WriteFile(filePath, data, filePerm)
|
return os.WriteFile(filePath, data, filePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CertificatesStorage) WriteCertificateFiles(domain string, certRes *certificate.Resource) error {
|
||||||
|
err := s.WriteFile(domain, ".key", certRes.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to save key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.pem {
|
||||||
|
err = s.WriteFile(domain, ".pem", bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to save PEM file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.pfx {
|
||||||
|
err = s.WritePFXFile(domain, certRes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to save PFX file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.Resource) error {
|
||||||
|
certPemBlock, _ := pem.Decode(certRes.Certificate)
|
||||||
|
if certPemBlock == nil {
|
||||||
|
return fmt.Errorf("unable to parse Certificate for domain %s", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(certPemBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to load Issuer Certificate for domain %s: %w", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPemBlock, _ := pem.Decode(certRes.PrivateKey)
|
||||||
|
if keyPemBlock == nil {
|
||||||
|
return fmt.Errorf("unable to parse PrivateKey for domain %s", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateKey crypto.Signer
|
||||||
|
var keyErr error
|
||||||
|
|
||||||
|
switch keyPemBlock.Type {
|
||||||
|
case "RSA PRIVATE KEY":
|
||||||
|
privateKey, keyErr = x509.ParsePKCS1PrivateKey(keyPemBlock.Bytes)
|
||||||
|
if keyErr != nil {
|
||||||
|
return fmt.Errorf("unable to load RSA PrivateKey for domain %s: %w", domain, keyErr)
|
||||||
|
}
|
||||||
|
case "EC PRIVATE KEY":
|
||||||
|
privateKey, keyErr = x509.ParseECPrivateKey(keyPemBlock.Bytes)
|
||||||
|
if keyErr != nil {
|
||||||
|
return fmt.Errorf("unable to load EC PrivateKey for domain %s: %w", domain, keyErr)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to encode PFX data for domain %s: %w", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.WriteFile(domain, ".pfx", pfxBytes)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) MoveToArchive(domain string) error {
|
func (s *CertificatesStorage) MoveToArchive(domain string) error {
|
||||||
matches, err := filepath.Glob(filepath.Join(s.rootPath, sanitizedDomain(domain)+".*"))
|
matches, err := filepath.Glob(filepath.Join(s.rootPath, sanitizedDomain(domain)+".*"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
10
cmd/flags.go
10
cmd/flags.go
|
@ -3,6 +3,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
pkcs12 "software.sslmate.com/src/go-pkcs12"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateFlags(defaultPath string) []cli.Flag {
|
func CreateFlags(defaultPath string) []cli.Flag {
|
||||||
|
@ -111,6 +112,15 @@ func CreateFlags(defaultPath string) []cli.Flag {
|
||||||
Name: "pem",
|
Name: "pem",
|
||||||
Usage: "Generate a .pem file by concatenating the .key and .crt files together.",
|
Usage: "Generate a .pem file by concatenating the .key and .crt files together.",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "pfx",
|
||||||
|
Usage: "Generate a .pfx (PKCS#12) file by with the .key and .crt and issuer .crt files together.",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "pfx.pass",
|
||||||
|
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
|
||||||
|
Value: pkcs12.DefaultPassword,
|
||||||
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "cert.timeout",
|
Name: "cert.timeout",
|
||||||
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
|
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
|
||||||
|
|
|
@ -50,6 +50,8 @@ GLOBAL OPTIONS:
|
||||||
--http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
|
--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 servers queries. (default: 10)
|
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. (default: 10)
|
||||||
--pem Generate a .pem file by concatenating the .key and .crt files together.
|
--pem Generate a .pem file by concatenating the .key and .crt files together.
|
||||||
|
--pfx Generate a .pfx (PKCS#12) file by with the .key and .crt and issuer .crt files together.
|
||||||
|
--pfx.pass The password used to encrypt the .pfx (PCKS#12) file (default: changeit).
|
||||||
--cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
|
--cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
|
||||||
--help, -h show help
|
--help, -h show help
|
||||||
--version, -v print the version
|
--version, -v print the version
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -56,11 +56,12 @@ require (
|
||||||
github.com/vinyldns/go-vinyldns v0.9.16
|
github.com/vinyldns/go-vinyldns v0.9.16
|
||||||
github.com/vultr/govultr/v2 v2.7.1
|
github.com/vultr/govultr/v2 v2.7.1
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
|
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
|
||||||
google.golang.org/api v0.20.0
|
google.golang.org/api v0.20.0
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.6.2
|
gopkg.in/ns1/ns1-go.v2 v2.6.2
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
|
||||||
)
|
)
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -515,6 +515,7 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh
|
||||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
@ -579,9 +580,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -799,3 +799,5 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg=
|
||||||
|
|
Loading…
Reference in a new issue