123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- // Copyright 2015 Matthew Holt
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package certmagic
-
- import (
- "crypto"
- "crypto/ecdsa"
- "crypto/ed25519"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/rsa"
- "crypto/sha256"
- "crypto/tls"
- "crypto/x509"
- "encoding/json"
- "encoding/pem"
- "fmt"
- "hash/fnv"
- "sort"
- "strings"
-
- "github.com/klauspost/cpuid/v2"
- "go.uber.org/zap"
- "golang.org/x/net/idna"
- )
-
- // encodePrivateKey marshals a EC or RSA private key into a PEM-encoded array of bytes.
- func encodePrivateKey(key crypto.PrivateKey) ([]byte, error) {
- var pemType string
- var keyBytes []byte
- switch key := key.(type) {
- case *ecdsa.PrivateKey:
- var err error
- pemType = "EC"
- keyBytes, err = x509.MarshalECPrivateKey(key)
- if err != nil {
- return nil, err
- }
- case *rsa.PrivateKey:
- pemType = "RSA"
- keyBytes = x509.MarshalPKCS1PrivateKey(key)
- case ed25519.PrivateKey:
- var err error
- pemType = "ED25519"
- keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
- if err != nil {
- return nil, err
- }
- default:
- return nil, fmt.Errorf("unsupported key type: %T", key)
- }
- pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
- return pem.EncodeToMemory(&pemKey), nil
- }
-
- // decodePrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes.
- // Borrowed from Go standard library, to handle various private key and PEM block types.
- // https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
- // https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
- func decodePrivateKey(keyPEMBytes []byte) (crypto.Signer, error) {
- keyBlockDER, _ := pem.Decode(keyPEMBytes)
-
- if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
- return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
- }
-
- if key, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil {
- return key, nil
- }
-
- if key, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
- switch key := key.(type) {
- case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
- return key.(crypto.Signer), nil
- default:
- return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
- }
- }
-
- if key, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil {
- return key, nil
- }
-
- return nil, fmt.Errorf("unknown private key type")
- }
-
- // parseCertsFromPEMBundle parses a certificate bundle from top to bottom and returns
- // a slice of x509 certificates. This function will error if no certificates are found.
- func parseCertsFromPEMBundle(bundle []byte) ([]*x509.Certificate, error) {
- var certificates []*x509.Certificate
- var certDERBlock *pem.Block
- for {
- certDERBlock, bundle = pem.Decode(bundle)
- if certDERBlock == nil {
- break
- }
- if certDERBlock.Type == "CERTIFICATE" {
- cert, err := x509.ParseCertificate(certDERBlock.Bytes)
- if err != nil {
- return nil, err
- }
- certificates = append(certificates, cert)
- }
- }
- if len(certificates) == 0 {
- return nil, fmt.Errorf("no certificates found in bundle")
- }
- return certificates, nil
- }
-
- // fastHash hashes input using a hashing algorithm that
- // is fast, and returns the hash as a hex-encoded string.
- // Do not use this for cryptographic purposes.
- func fastHash(input []byte) string {
- h := fnv.New32a()
- h.Write(input)
- return fmt.Sprintf("%x", h.Sum32())
- }
-
- // saveCertResource saves the certificate resource to disk. This
- // includes the certificate file itself, the private key, and the
- // metadata file.
- func (cfg *Config) saveCertResource(issuer Issuer, cert CertificateResource) error {
- metaBytes, err := json.MarshalIndent(cert, "", "\t")
- if err != nil {
- return fmt.Errorf("encoding certificate metadata: %v", err)
- }
-
- issuerKey := issuer.IssuerKey()
- certKey := cert.NamesKey()
-
- all := []keyValue{
- {
- key: StorageKeys.SiteCert(issuerKey, certKey),
- value: cert.CertificatePEM,
- },
- {
- key: StorageKeys.SitePrivateKey(issuerKey, certKey),
- value: cert.PrivateKeyPEM,
- },
- {
- key: StorageKeys.SiteMeta(issuerKey, certKey),
- value: metaBytes,
- },
- }
-
- return storeTx(cfg.Storage, all)
- }
-
- // loadCertResourceAnyIssuer loads and returns the certificate resource from any
- // of the configured issuers. If multiple are found (e.g. if there are 3 issuers
- // configured, and all 3 have a resource matching certNamesKey), then the newest
- // (latest NotBefore date) resource will be chosen.
- func (cfg *Config) loadCertResourceAnyIssuer(certNamesKey string) (CertificateResource, error) {
- // we can save some extra decoding steps if there's only one issuer, since
- // we don't need to compare potentially multiple available resources to
- // select the best one, when there's only one choice anyway
- if len(cfg.Issuers) == 1 {
- return cfg.loadCertResource(cfg.Issuers[0], certNamesKey)
- }
-
- type decodedCertResource struct {
- CertificateResource
- issuer Issuer
- decoded *x509.Certificate
- }
- var certResources []decodedCertResource
- var lastErr error
-
- // load and decode all certificate resources found with the
- // configured issuers so we can sort by newest
- for _, issuer := range cfg.Issuers {
- certRes, err := cfg.loadCertResource(issuer, certNamesKey)
- if err != nil {
- if _, ok := err.(ErrNotExist); ok {
- // not a problem, but we need to remember the error
- // in case we end up not finding any cert resources
- // since we'll need an error to return in that case
- lastErr = err
- continue
- }
- return CertificateResource{}, err
- }
- certs, err := parseCertsFromPEMBundle(certRes.CertificatePEM)
- if err != nil {
- return CertificateResource{}, err
- }
- certResources = append(certResources, decodedCertResource{
- CertificateResource: certRes,
- issuer: issuer,
- decoded: certs[0],
- })
- }
- if len(certResources) == 0 {
- if lastErr == nil {
- lastErr = fmt.Errorf("no certificate resources found") // just in case; e.g. no Issuers configured
- }
- return CertificateResource{}, lastErr
- }
-
- // sort by date so the most recently issued comes first
- sort.Slice(certResources, func(i, j int) bool {
- return certResources[j].decoded.NotBefore.Before(certResources[i].decoded.NotBefore)
- })
-
- if cfg.Logger != nil {
- cfg.Logger.Debug("loading managed certificate",
- zap.String("domain", certNamesKey),
- zap.Time("expiration", certResources[0].decoded.NotAfter),
- zap.String("issuer_key", certResources[0].issuer.IssuerKey()),
- zap.Any("storage", cfg.Storage),
- )
- }
-
- return certResources[0].CertificateResource, nil
- }
-
- // loadCertResource loads a certificate resource from the given issuer's storage location.
- func (cfg *Config) loadCertResource(issuer Issuer, certNamesKey string) (CertificateResource, error) {
- certRes := CertificateResource{issuerKey: issuer.IssuerKey()}
-
- normalizedName, err := idna.ToASCII(certNamesKey)
- if err != nil {
- return CertificateResource{}, fmt.Errorf("converting '%s' to ASCII: %v", certNamesKey, err)
- }
-
- certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(certRes.issuerKey, normalizedName))
- if err != nil {
- return CertificateResource{}, err
- }
- certRes.CertificatePEM = certBytes
- keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(certRes.issuerKey, normalizedName))
- if err != nil {
- return CertificateResource{}, err
- }
- certRes.PrivateKeyPEM = keyBytes
- metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(certRes.issuerKey, normalizedName))
- if err != nil {
- return CertificateResource{}, err
- }
- err = json.Unmarshal(metaBytes, &certRes)
- if err != nil {
- return CertificateResource{}, fmt.Errorf("decoding certificate metadata: %v", err)
- }
-
- return certRes, nil
- }
-
- // hashCertificateChain computes the unique hash of certChain,
- // which is the chain of DER-encoded bytes. It returns the
- // hex encoding of the hash.
- func hashCertificateChain(certChain [][]byte) string {
- h := sha256.New()
- for _, certInChain := range certChain {
- h.Write(certInChain)
- }
- return fmt.Sprintf("%x", h.Sum(nil))
- }
-
- func namesFromCSR(csr *x509.CertificateRequest) []string {
- var nameSet []string
- nameSet = append(nameSet, csr.DNSNames...)
- nameSet = append(nameSet, csr.EmailAddresses...)
- for _, v := range csr.IPAddresses {
- nameSet = append(nameSet, v.String())
- }
- for _, v := range csr.URIs {
- nameSet = append(nameSet, v.String())
- }
- return nameSet
- }
-
- // preferredDefaultCipherSuites returns an appropriate
- // cipher suite to use depending on hardware support
- // for AES-NI.
- //
- // See https://github.com/mholt/caddy/issues/1674
- func preferredDefaultCipherSuites() []uint16 {
- if cpuid.CPU.Supports(cpuid.AESNI) {
- return defaultCiphersPreferAES
- }
- return defaultCiphersPreferChaCha
- }
-
- var (
- defaultCiphersPreferAES = []uint16{
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- }
- defaultCiphersPreferChaCha = []uint16{
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- }
- )
-
- // StandardKeyGenerator is the standard, in-memory key source
- // that uses crypto/rand.
- type StandardKeyGenerator struct {
- // The type of keys to generate.
- KeyType KeyType
- }
-
- // GenerateKey generates a new private key according to kg.KeyType.
- func (kg StandardKeyGenerator) GenerateKey() (crypto.PrivateKey, error) {
- switch kg.KeyType {
- case ED25519:
- _, priv, err := ed25519.GenerateKey(rand.Reader)
- return priv, err
- case "", P256:
- return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- case P384:
- return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
- case RSA2048:
- return rsa.GenerateKey(rand.Reader, 2048)
- case RSA4096:
- return rsa.GenerateKey(rand.Reader, 4096)
- case RSA8192:
- return rsa.GenerateKey(rand.Reader, 8192)
- }
- return nil, fmt.Errorf("unrecognized or unsupported key type: %s", kg.KeyType)
- }
-
- // DefaultKeyGenerator is the default key source.
- var DefaultKeyGenerator = StandardKeyGenerator{KeyType: P256}
-
- // KeyType enumerates the known/supported key types.
- type KeyType string
-
- // Constants for all key types we support.
- const (
- ED25519 = KeyType("ed25519")
- P256 = KeyType("p256")
- P384 = KeyType("p384")
- RSA2048 = KeyType("rsa2048")
- RSA4096 = KeyType("rsa4096")
- RSA8192 = KeyType("rsa8192")
- )
|