Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
9.6 KiB
324 lines
9.6 KiB
// 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" |
|
"strings" |
|
|
|
"github.com/klauspost/cpuid" |
|
) |
|
|
|
// 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(cert CertificateResource) error { |
|
metaBytes, err := json.MarshalIndent(cert, "", "\t") |
|
if err != nil { |
|
return fmt.Errorf("encoding certificate metadata: %v", err) |
|
} |
|
|
|
issuerKey := cfg.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) |
|
} |
|
|
|
func (cfg *Config) loadCertResource(certNamesKey string) (CertificateResource, error) { |
|
var certRes CertificateResource |
|
issuerKey := cfg.Issuer.IssuerKey() |
|
certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(issuerKey, certNamesKey)) |
|
if err != nil { |
|
return CertificateResource{}, err |
|
} |
|
certRes.CertificatePEM = certBytes |
|
keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(issuerKey, certNamesKey)) |
|
if err != nil { |
|
return CertificateResource{}, err |
|
} |
|
certRes.PrivateKeyPEM = keyBytes |
|
metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(issuerKey, certNamesKey)) |
|
if err != nil { |
|
return CertificateResource{}, err |
|
} |
|
err = json.Unmarshal(metaBytes, &certRes) |
|
if err != nil { |
|
return CertificateResource{}, fmt.Errorf("decoding certificate metadata: %v", err) |
|
} |
|
|
|
// TODO: July 2020 - transition to new ACME lib and cert resource structure; |
|
// for a while, we will need to convert old cert resources to new structure |
|
certRes, err = cfg.transitionCertMetaToACMEzJuly2020Format(certRes, metaBytes) |
|
if err != nil { |
|
return certRes, fmt.Errorf("one-time certificate resource transition: %v", err) |
|
} |
|
|
|
return certRes, nil |
|
} |
|
|
|
// TODO: this is a temporary transition helper starting July 2020. |
|
// It can go away when we think enough time has passed that most active assets have transitioned. |
|
func (cfg *Config) transitionCertMetaToACMEzJuly2020Format(certRes CertificateResource, metaBytes []byte) (CertificateResource, error) { |
|
data, ok := certRes.IssuerData.(map[string]interface{}) |
|
if !ok { |
|
return certRes, nil |
|
} |
|
if certURL, ok := data["url"].(string); ok && certURL != "" { |
|
return certRes, nil |
|
} |
|
|
|
var oldCertRes struct { |
|
SANs []string `json:"sans"` |
|
IssuerData struct { |
|
Domain string `json:"domain"` |
|
CertURL string `json:"certUrl"` |
|
CertStableURL string `json:"certStableUrl"` |
|
} `json:"issuer_data"` |
|
} |
|
err := json.Unmarshal(metaBytes, &oldCertRes) |
|
if err != nil { |
|
return certRes, fmt.Errorf("decoding into old certificate resource type: %v", err) |
|
} |
|
|
|
data = map[string]interface{}{ |
|
"url": oldCertRes.IssuerData.CertURL, |
|
} |
|
certRes.IssuerData = data |
|
|
|
err = cfg.saveCertResource(certRes) |
|
if err != nil { |
|
return certRes, fmt.Errorf("saving converted certificate resource: %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.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") |
|
)
|
|
|