2018-12-16 11:19:16 +00:00
|
|
|
// 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/rsa"
|
|
|
|
"crypto/sha256"
|
2018-12-31 01:20:22 +00:00
|
|
|
"crypto/tls"
|
2018-12-16 11:19:16 +00:00
|
|
|
"crypto/x509"
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/pem"
|
|
|
|
"fmt"
|
|
|
|
"hash/fnv"
|
|
|
|
|
2019-06-09 00:01:48 +00:00
|
|
|
"github.com/go-acme/lego/certificate"
|
2018-12-31 01:20:22 +00:00
|
|
|
"github.com/klauspost/cpuid"
|
2018-12-16 11:19:16 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
func decodePrivateKey(keyPEMBytes []byte) (crypto.PrivateKey, error) {
|
|
|
|
keyBlock, _ := pem.Decode(keyPEMBytes)
|
|
|
|
switch keyBlock.Type {
|
|
|
|
case "RSA PRIVATE KEY":
|
|
|
|
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
|
|
|
case "EC PRIVATE KEY":
|
|
|
|
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
|
|
|
}
|
|
|
|
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 *certificate.Resource) error {
|
|
|
|
metaBytes, err := json.MarshalIndent(&cert, "", "\t")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("encoding certificate metadata: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
all := []keyValue{
|
|
|
|
{
|
|
|
|
key: StorageKeys.SiteCert(cfg.CA, cert.Domain),
|
|
|
|
value: cert.Certificate,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: StorageKeys.SitePrivateKey(cfg.CA, cert.Domain),
|
|
|
|
value: cert.PrivateKey,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: StorageKeys.SiteMeta(cfg.CA, cert.Domain),
|
|
|
|
value: metaBytes,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-06-09 00:01:48 +00:00
|
|
|
return storeTx(cfg.Storage, all)
|
2018-12-16 11:19:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cfg *Config) loadCertResource(domain string) (certificate.Resource, error) {
|
|
|
|
var certRes certificate.Resource
|
2019-06-09 00:01:48 +00:00
|
|
|
certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(cfg.CA, domain))
|
2018-12-16 11:19:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return certRes, err
|
|
|
|
}
|
2019-06-09 00:01:48 +00:00
|
|
|
keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(cfg.CA, domain))
|
2018-12-16 11:19:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return certRes, err
|
|
|
|
}
|
2019-06-09 00:01:48 +00:00
|
|
|
metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(cfg.CA, domain))
|
2018-12-16 11:19:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return certRes, err
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(metaBytes, &certRes)
|
|
|
|
if err != nil {
|
|
|
|
return certRes, fmt.Errorf("decoding certificate metadata: %v", err)
|
|
|
|
}
|
|
|
|
certRes.Certificate = certBytes
|
|
|
|
certRes.PrivateKey = keyBytes
|
|
|
|
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))
|
|
|
|
}
|
2018-12-31 01:20:22 +00:00
|
|
|
|
|
|
|
// 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,
|
|
|
|
}
|
|
|
|
)
|