forked from forgejo/forgejo
Use caddy's certmagic library for extensible/robust ACME handling (#14177)
* use certmagic for more extensible/robust ACME cert handling * accept TOS based on config option Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
bc05ddc0eb
commit
d2ea21d0d8
437 changed files with 56286 additions and 4270 deletions
324
vendor/github.com/caddyserver/certmagic/crypto.go
generated
vendored
Normal file
324
vendor/github.com/caddyserver/certmagic/crypto.go
generated
vendored
Normal file
|
@ -0,0 +1,324 @@
|
|||
// 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")
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue