mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-07 07:07:05 +02:00
* Use DRBG based RSA key generation everywhere * switch to the conditional generator * Use DRBG based RSA key generation everywhere * switch to the conditional generator * Add an ENV var to disable the DRBG in a pinch * update go.mod * Use DRBG based RSA key generation everywhere * switch to the conditional generator * Add an ENV var to disable the DRBG in a pinch * Use DRBG based RSA key generation everywhere * update go.mod * fix import * Remove rsa2 alias, remove test code * move cryptoutil/rsa.go to sdk * move imports too * remove makefile change * rsa2->rsa * more rsa2->rsa, remove test code * fix some overzelous search/replace * Update to a real tag * changelog * copyright * work around copyright check * work around copyright check pt2 * bunch of dupe imports * missing import * wrong license * fix go.mod conflict * missed a spot * dupe import
2112 lines
68 KiB
Go
2112 lines
68 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package certutil
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha1"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/cryptoutil"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
"github.com/hashicorp/vault/sdk/helper/errutil"
|
|
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
|
"github.com/mitchellh/mapstructure"
|
|
"golang.org/x/crypto/cryptobyte"
|
|
cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
|
|
)
|
|
|
|
const rsaMinimumSecureKeySize = 2048
|
|
|
|
// Mapping of key types to default key lengths
|
|
var defaultAlgorithmKeyBits = map[string]int{
|
|
"rsa": 2048,
|
|
"ec": 256,
|
|
}
|
|
|
|
// Mapping of NIST P-Curve's key length to expected signature bits.
|
|
var expectedNISTPCurveHashBits = map[int]int{
|
|
224: 256,
|
|
256: 256,
|
|
384: 384,
|
|
521: 512,
|
|
}
|
|
|
|
// Mapping of constant names<->constant values for SignatureAlgorithm
|
|
var SignatureAlgorithmNames = map[string]x509.SignatureAlgorithm{
|
|
"sha256withrsa": x509.SHA256WithRSA,
|
|
"sha384withrsa": x509.SHA384WithRSA,
|
|
"sha512withrsa": x509.SHA512WithRSA,
|
|
"ecdsawithsha256": x509.ECDSAWithSHA256,
|
|
"ecdsawithsha384": x509.ECDSAWithSHA384,
|
|
"ecdsawithsha512": x509.ECDSAWithSHA512,
|
|
"sha256withrsapss": x509.SHA256WithRSAPSS,
|
|
"sha384withrsapss": x509.SHA384WithRSAPSS,
|
|
"sha512withrsapss": x509.SHA512WithRSAPSS,
|
|
"pureed25519": x509.PureEd25519,
|
|
"ed25519": x509.PureEd25519, // Duplicated for clarity; most won't expect the "Pure" prefix.
|
|
}
|
|
|
|
// Mapping of constant values<->constant names for SignatureAlgorithm
|
|
var InvSignatureAlgorithmNames = map[x509.SignatureAlgorithm]string{
|
|
x509.SHA256WithRSA: "SHA256WithRSA",
|
|
x509.SHA384WithRSA: "SHA384WithRSA",
|
|
x509.SHA512WithRSA: "SHA512WithRSA",
|
|
x509.ECDSAWithSHA256: "ECDSAWithSHA256",
|
|
x509.ECDSAWithSHA384: "ECDSAWithSHA384",
|
|
x509.ECDSAWithSHA512: "ECDSAWithSHA512",
|
|
x509.SHA256WithRSAPSS: "SHA256WithRSAPSS",
|
|
x509.SHA384WithRSAPSS: "SHA384WithRSAPSS",
|
|
x509.SHA512WithRSAPSS: "SHA512WithRSAPSS",
|
|
x509.PureEd25519: "Ed25519",
|
|
}
|
|
|
|
// OIDs for X.509 SAN Extension
|
|
var OidExtensionSubjectAltName = asn1.ObjectIdentifier([]int{2, 5, 29, 17})
|
|
|
|
// OID for RFC 5280 CRL Number extension.
|
|
//
|
|
// > id-ce-cRLNumber OBJECT IDENTIFIER ::= { id-ce 20 }
|
|
var CRLNumberOID = asn1.ObjectIdentifier([]int{2, 5, 29, 20})
|
|
|
|
// OID for RFC 5280 Delta CRL Indicator CRL extension.
|
|
//
|
|
// > id-ce-deltaCRLIndicator OBJECT IDENTIFIER ::= { id-ce 27 }
|
|
var DeltaCRLIndicatorOID = asn1.ObjectIdentifier([]int{2, 5, 29, 27})
|
|
|
|
// OID for KeyUsage from RFC 2459 : https://www.rfc-editor.org/rfc/rfc2459.html#section-4.2.1.3
|
|
//
|
|
// > id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }
|
|
var KeyUsageOID = asn1.ObjectIdentifier([]int{2, 5, 29, 15})
|
|
|
|
// OID for Extended Key Usage from RFC 5280 : https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12
|
|
//
|
|
// id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
|
|
var ExtendedKeyUsageOID = asn1.ObjectIdentifier([]int{2, 5, 29, 37})
|
|
|
|
// GetHexFormatted returns the byte buffer formatted in hex with
|
|
// the specified separator between bytes.
|
|
func GetHexFormatted(buf []byte, sep string) string {
|
|
var ret bytes.Buffer
|
|
for _, cur := range buf {
|
|
if ret.Len() > 0 {
|
|
fmt.Fprintf(&ret, sep)
|
|
}
|
|
fmt.Fprintf(&ret, "%02x", cur)
|
|
}
|
|
return ret.String()
|
|
}
|
|
|
|
// ParseHexFormatted returns the raw bytes from a formatted hex string
|
|
func ParseHexFormatted(in, sep string) []byte {
|
|
var ret bytes.Buffer
|
|
var err error
|
|
var inBits uint64
|
|
inBytes := strings.Split(in, sep)
|
|
for _, inByte := range inBytes {
|
|
if inBits, err = strconv.ParseUint(inByte, 16, 8); err != nil {
|
|
return nil
|
|
}
|
|
ret.WriteByte(uint8(inBits))
|
|
}
|
|
return ret.Bytes()
|
|
}
|
|
|
|
// GetSubjKeyID returns the subject key ID. The computed ID is the SHA-1 hash of
|
|
// the marshaled public key according to
|
|
// https://tools.ietf.org/html/rfc5280#section-4.2.1.2 (1)
|
|
func GetSubjKeyID(privateKey crypto.Signer) ([]byte, error) {
|
|
if privateKey == nil {
|
|
return nil, errutil.InternalError{Err: "passed-in private key is nil"}
|
|
}
|
|
return GetSubjectKeyID(privateKey.Public())
|
|
}
|
|
|
|
// Returns the explicit SKID when used for cross-signing, else computes a new
|
|
// SKID from the key itself.
|
|
func getSubjectKeyIDFromBundle(data *CreationBundle) ([]byte, error) {
|
|
if len(data.Params.SKID) > 0 {
|
|
return data.Params.SKID, nil
|
|
}
|
|
|
|
return GetSubjectKeyID(data.CSR.PublicKey)
|
|
}
|
|
|
|
func GetSubjectKeyID(pub interface{}) ([]byte, error) {
|
|
var publicKeyBytes []byte
|
|
switch pub := pub.(type) {
|
|
case *rsa.PublicKey:
|
|
type pkcs1PublicKey struct {
|
|
N *big.Int
|
|
E int
|
|
}
|
|
|
|
var err error
|
|
publicKeyBytes, err = asn1.Marshal(pkcs1PublicKey{
|
|
N: pub.N,
|
|
E: pub.E,
|
|
})
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("error marshalling public key: %s", err)}
|
|
}
|
|
case *ecdsa.PublicKey:
|
|
publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
|
|
case ed25519.PublicKey:
|
|
publicKeyBytes = pub
|
|
default:
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unsupported public key type: %T", pub)}
|
|
}
|
|
skid := sha1.Sum(publicKeyBytes)
|
|
return skid[:], nil
|
|
}
|
|
|
|
// ParsePKIMap takes a map (for instance, the Secret.Data
|
|
// returned from the PKI backend) and returns a ParsedCertBundle.
|
|
func ParsePKIMap(data map[string]interface{}) (*ParsedCertBundle, error) {
|
|
result := &CertBundle{}
|
|
err := mapstructure.Decode(data, result)
|
|
if err != nil {
|
|
return nil, errutil.UserError{Err: err.Error()}
|
|
}
|
|
|
|
return result.ToParsedCertBundle()
|
|
}
|
|
|
|
// ParsePKIJSON takes a JSON-encoded string and returns a ParsedCertBundle.
|
|
//
|
|
// This can be either the output of an
|
|
// issue call from the PKI backend or just its data member; or,
|
|
// JSON not coming from the PKI backend.
|
|
func ParsePKIJSON(input []byte) (*ParsedCertBundle, error) {
|
|
result := &CertBundle{}
|
|
err := jsonutil.DecodeJSON(input, &result)
|
|
|
|
if err == nil {
|
|
return result.ToParsedCertBundle()
|
|
}
|
|
|
|
var secret Secret
|
|
err = jsonutil.DecodeJSON(input, &secret)
|
|
|
|
if err == nil {
|
|
return ParsePKIMap(secret.Data)
|
|
}
|
|
|
|
return nil, errutil.UserError{Err: "unable to parse out of either secret data or a secret object"}
|
|
}
|
|
|
|
func ParseDERKey(privateKeyBytes []byte) (signer crypto.Signer, format BlockType, err error) {
|
|
var firstError error
|
|
if signer, firstError = x509.ParseECPrivateKey(privateKeyBytes); firstError == nil {
|
|
format = ECBlock
|
|
return
|
|
}
|
|
|
|
var secondError error
|
|
if signer, secondError = x509.ParsePKCS1PrivateKey(privateKeyBytes); secondError == nil {
|
|
format = PKCS1Block
|
|
return
|
|
}
|
|
|
|
var thirdError error
|
|
var rawKey interface{}
|
|
if rawKey, thirdError = x509.ParsePKCS8PrivateKey(privateKeyBytes); thirdError == nil {
|
|
switch rawSigner := rawKey.(type) {
|
|
case *rsa.PrivateKey:
|
|
signer = rawSigner
|
|
case *ecdsa.PrivateKey:
|
|
signer = rawSigner
|
|
case ed25519.PrivateKey:
|
|
signer = rawSigner
|
|
default:
|
|
return nil, UnknownBlock, errutil.InternalError{Err: "unknown type for parsed PKCS8 Private Key"}
|
|
}
|
|
|
|
format = PKCS8Block
|
|
return
|
|
}
|
|
|
|
return nil, UnknownBlock, fmt.Errorf("got errors attempting to parse DER private key:\n1. %v\n2. %v\n3. %v", firstError, secondError, thirdError)
|
|
}
|
|
|
|
func ParsePEMKey(keyPem string) (crypto.Signer, BlockType, error) {
|
|
pemBlock, _ := pem.Decode([]byte(keyPem))
|
|
if pemBlock == nil {
|
|
return nil, UnknownBlock, errutil.UserError{Err: "no data found in PEM block"}
|
|
}
|
|
|
|
return ParseDERKey(pemBlock.Bytes)
|
|
}
|
|
|
|
// ParsePEMBundle takes a string of concatenated PEM-format certificate
|
|
// and private key values and decodes/parses them, checking validity along
|
|
// the way. The first certificate must be the subject certificate and issuing
|
|
// certificates may follow. There must be at most one private key.
|
|
func ParsePEMBundle(pemBundle string) (*ParsedCertBundle, error) {
|
|
if len(pemBundle) == 0 {
|
|
return nil, errutil.UserError{Err: "empty pem bundle"}
|
|
}
|
|
|
|
pemBytes := []byte(pemBundle)
|
|
var pemBlock *pem.Block
|
|
parsedBundle := &ParsedCertBundle{}
|
|
var certPath []*CertBlock
|
|
|
|
for len(pemBytes) > 0 {
|
|
pemBlock, pemBytes = pem.Decode(pemBytes)
|
|
if pemBlock == nil {
|
|
return nil, errutil.UserError{Err: "no data found in PEM block"}
|
|
}
|
|
|
|
if signer, format, err := ParseDERKey(pemBlock.Bytes); err == nil {
|
|
if parsedBundle.PrivateKeyType != UnknownPrivateKey {
|
|
return nil, errutil.UserError{Err: "more than one private key given; provide only one private key in the bundle"}
|
|
}
|
|
|
|
parsedBundle.PrivateKeyFormat = format
|
|
parsedBundle.PrivateKeyType = GetPrivateKeyTypeFromSigner(signer)
|
|
if parsedBundle.PrivateKeyType == UnknownPrivateKey {
|
|
return nil, errutil.UserError{Err: "Unknown type of private key included in the bundle: %v"}
|
|
}
|
|
|
|
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
|
|
parsedBundle.PrivateKey = signer
|
|
} else if certificates, err := x509.ParseCertificates(pemBlock.Bytes); err == nil {
|
|
certPath = append(certPath, &CertBlock{
|
|
Certificate: certificates[0],
|
|
Bytes: pemBlock.Bytes,
|
|
})
|
|
} else if x509.IsEncryptedPEMBlock(pemBlock) {
|
|
return nil, errutil.UserError{Err: "Encrypted private key given; provide only decrypted private key in the bundle"}
|
|
}
|
|
}
|
|
|
|
for i, certBlock := range certPath {
|
|
if i == 0 {
|
|
parsedBundle.Certificate = certBlock.Certificate
|
|
parsedBundle.CertificateBytes = certBlock.Bytes
|
|
} else {
|
|
parsedBundle.CAChain = append(parsedBundle.CAChain, certBlock)
|
|
}
|
|
}
|
|
|
|
if err := parsedBundle.Verify(); err != nil {
|
|
return nil, errutil.UserError{Err: fmt.Sprintf("verification of parsed bundle failed: %s", err)}
|
|
}
|
|
|
|
return parsedBundle, nil
|
|
}
|
|
|
|
func (p *ParsedCertBundle) ToTLSCertificate() tls.Certificate {
|
|
var cert tls.Certificate
|
|
cert.Certificate = append(cert.Certificate, p.CertificateBytes)
|
|
cert.Leaf = p.Certificate
|
|
cert.PrivateKey = p.PrivateKey
|
|
for _, ca := range p.CAChain {
|
|
cert.Certificate = append(cert.Certificate, ca.Bytes)
|
|
}
|
|
|
|
return cert
|
|
}
|
|
|
|
// GeneratePrivateKey generates a private key with the specified type and key bits.
|
|
func GeneratePrivateKey(keyType string, keyBits int, container ParsedPrivateKeyContainer) error {
|
|
return generatePrivateKey(keyType, keyBits, container, nil)
|
|
}
|
|
|
|
// GeneratePrivateKeyWithRandomSource generates a private key with the specified type and key bits.
|
|
// GeneratePrivateKeyWithRandomSource uses randomness from the entropyReader to generate the private key.
|
|
func GeneratePrivateKeyWithRandomSource(keyType string, keyBits int, container ParsedPrivateKeyContainer, entropyReader io.Reader) error {
|
|
return generatePrivateKey(keyType, keyBits, container, entropyReader)
|
|
}
|
|
|
|
// generatePrivateKey generates a private key with the specified type and key bits.
|
|
// generatePrivateKey uses randomness from the entropyReader to generate the private key.
|
|
func generatePrivateKey(keyType string, keyBits int, container ParsedPrivateKeyContainer, entropyReader io.Reader) error {
|
|
var err error
|
|
var privateKeyType PrivateKeyType
|
|
var privateKeyBytes []byte
|
|
var privateKey crypto.Signer
|
|
|
|
var randReader io.Reader = rand.Reader
|
|
if entropyReader != nil {
|
|
randReader = entropyReader
|
|
}
|
|
|
|
switch keyType {
|
|
case "rsa":
|
|
// XXX: there is a false-positive CodeQL path here around keyBits;
|
|
// because of a default zero value in the TypeDurationSecond and
|
|
// TypeSignedDurationSecond cases of schema.DefaultOrZero(), it
|
|
// thinks it is possible to end up with < 2048 bit RSA Key here.
|
|
// While this is true for SSH keys, it isn't true for PKI keys
|
|
// due to ValidateKeyTypeLength(...) below. While we could close
|
|
// the report as a false-positive, enforcing a minimum keyBits size
|
|
// here of 2048 would ensure no other paths exist.
|
|
if keyBits < 2048 {
|
|
return errutil.InternalError{Err: fmt.Sprintf("insecure bit length for RSA private key: %d", keyBits)}
|
|
}
|
|
privateKeyType = RSAPrivateKey
|
|
privateKey, err = cryptoutil.GenerateRSAKey(randReader, keyBits)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("error generating RSA private key: %v", err)}
|
|
}
|
|
privateKeyBytes = x509.MarshalPKCS1PrivateKey(privateKey.(*rsa.PrivateKey))
|
|
case "ec":
|
|
privateKeyType = ECPrivateKey
|
|
var curve elliptic.Curve
|
|
switch keyBits {
|
|
case 224:
|
|
curve = elliptic.P224()
|
|
case 256:
|
|
curve = elliptic.P256()
|
|
case 384:
|
|
curve = elliptic.P384()
|
|
case 521:
|
|
curve = elliptic.P521()
|
|
default:
|
|
return errutil.UserError{Err: fmt.Sprintf("unsupported bit length for EC key: %d", keyBits)}
|
|
}
|
|
privateKey, err = ecdsa.GenerateKey(curve, randReader)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("error generating EC private key: %v", err)}
|
|
}
|
|
privateKeyBytes, err = x509.MarshalECPrivateKey(privateKey.(*ecdsa.PrivateKey))
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("error marshalling EC private key: %v", err)}
|
|
}
|
|
case "ed25519":
|
|
privateKeyType = Ed25519PrivateKey
|
|
_, privateKey, err = ed25519.GenerateKey(randReader)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("error generating ed25519 private key: %v", err)}
|
|
}
|
|
privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey.(ed25519.PrivateKey))
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("error marshalling Ed25519 private key: %v", err)}
|
|
}
|
|
default:
|
|
return errutil.UserError{Err: fmt.Sprintf("unknown key type: %s", keyType)}
|
|
}
|
|
|
|
container.SetParsedPrivateKey(privateKey, privateKeyType, privateKeyBytes)
|
|
return nil
|
|
}
|
|
|
|
// GenerateSerialNumber generates a serial number suitable for a certificate
|
|
func GenerateSerialNumber() (*big.Int, error) {
|
|
return generateSerialNumber(rand.Reader)
|
|
}
|
|
|
|
// GenerateSerialNumberWithRandomSource generates a serial number suitable
|
|
// for a certificate with custom entropy.
|
|
func GenerateSerialNumberWithRandomSource(randReader io.Reader) (*big.Int, error) {
|
|
return generateSerialNumber(randReader)
|
|
}
|
|
|
|
func generateSerialNumber(randReader io.Reader) (*big.Int, error) {
|
|
serial, err := rand.Int(randReader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("error generating serial number: %v", err)}
|
|
}
|
|
return serial, nil
|
|
}
|
|
|
|
// ComparePublicKeysAndType compares two public keys and returns true if they match,
|
|
// false if their types or contents differ, and an error on unsupported key types.
|
|
func ComparePublicKeysAndType(key1Iface, key2Iface crypto.PublicKey) (bool, error) {
|
|
equal, err := ComparePublicKeys(key1Iface, key2Iface)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "key types do not match:") {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return equal, err
|
|
}
|
|
|
|
// ComparePublicKeys compares two public keys and returns true if they match,
|
|
// returns an error if public key types are mismatched, or they are an unsupported key type.
|
|
func ComparePublicKeys(key1Iface, key2Iface crypto.PublicKey) (bool, error) {
|
|
switch key1Iface.(type) {
|
|
case *rsa.PublicKey:
|
|
key1 := key1Iface.(*rsa.PublicKey)
|
|
key2, ok := key2Iface.(*rsa.PublicKey)
|
|
if !ok {
|
|
return false, fmt.Errorf("key types do not match: %T and %T", key1Iface, key2Iface)
|
|
}
|
|
if key1.N.Cmp(key2.N) != 0 ||
|
|
key1.E != key2.E {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
|
|
case *ecdsa.PublicKey:
|
|
key1 := key1Iface.(*ecdsa.PublicKey)
|
|
key2, ok := key2Iface.(*ecdsa.PublicKey)
|
|
if !ok {
|
|
return false, fmt.Errorf("key types do not match: %T and %T", key1Iface, key2Iface)
|
|
}
|
|
if key1.X.Cmp(key2.X) != 0 ||
|
|
key1.Y.Cmp(key2.Y) != 0 {
|
|
return false, nil
|
|
}
|
|
key1Params := key1.Params()
|
|
key2Params := key2.Params()
|
|
if key1Params.P.Cmp(key2Params.P) != 0 ||
|
|
key1Params.N.Cmp(key2Params.N) != 0 ||
|
|
key1Params.B.Cmp(key2Params.B) != 0 ||
|
|
key1Params.Gx.Cmp(key2Params.Gx) != 0 ||
|
|
key1Params.Gy.Cmp(key2Params.Gy) != 0 ||
|
|
key1Params.BitSize != key2Params.BitSize {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
case ed25519.PublicKey:
|
|
key1 := key1Iface.(ed25519.PublicKey)
|
|
key2, ok := key2Iface.(ed25519.PublicKey)
|
|
if !ok {
|
|
return false, fmt.Errorf("key types do not match: %T and %T", key1Iface, key2Iface)
|
|
}
|
|
if !key1.Equal(key2) {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
default:
|
|
return false, fmt.Errorf("cannot compare key with type %T", key1Iface)
|
|
}
|
|
}
|
|
|
|
// ParsePublicKeyPEM is used to parse RSA and ECDSA public keys from PEMs
|
|
func ParsePublicKeyPEM(data []byte) (interface{}, error) {
|
|
block, data := pem.Decode(data)
|
|
if block != nil {
|
|
if len(bytes.TrimSpace(data)) > 0 {
|
|
return nil, errutil.UserError{Err: "unexpected trailing data after parsed PEM block"}
|
|
}
|
|
var rawKey interface{}
|
|
var err error
|
|
if rawKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
|
|
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
|
|
rawKey = cert.PublicKey
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
switch key := rawKey.(type) {
|
|
case *rsa.PublicKey:
|
|
return key, nil
|
|
case *ecdsa.PublicKey:
|
|
return key, nil
|
|
case ed25519.PublicKey:
|
|
return key, nil
|
|
}
|
|
}
|
|
return nil, errors.New("data does not contain any valid public keys")
|
|
}
|
|
|
|
// AddPolicyIdentifiers adds certificate policies extension, based on CreationBundle
|
|
func AddPolicyIdentifiers(data *CreationBundle, certTemplate *x509.Certificate) {
|
|
oidOnly := true
|
|
for _, oidStr := range data.Params.PolicyIdentifiers {
|
|
oid, err := StringToOid(oidStr)
|
|
if err == nil {
|
|
certTemplate.PolicyIdentifiers = append(certTemplate.PolicyIdentifiers, oid)
|
|
}
|
|
if err != nil {
|
|
oidOnly = false
|
|
}
|
|
}
|
|
if !oidOnly { // Because all policy information is held in the same extension, when we use an extra extension to
|
|
// add policy qualifier information, that overwrites any information in the PolicyIdentifiers field on the Cert
|
|
// Template, so we need to reparse all the policy identifiers here
|
|
extension, err := CreatePolicyInformationExtensionFromStorageStrings(data.Params.PolicyIdentifiers)
|
|
if err == nil {
|
|
// If this errors out, don't add it, rely on the OIDs parsed into PolicyIdentifiers above
|
|
certTemplate.ExtraExtensions = append(certTemplate.ExtraExtensions, *extension)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddExtKeyUsageOids adds custom extended key usage OIDs to certificate
|
|
func AddExtKeyUsageOids(data *CreationBundle, certTemplate *x509.Certificate) {
|
|
for _, oidstr := range data.Params.ExtKeyUsageOIDs {
|
|
oid, err := StringToOid(oidstr)
|
|
if err == nil {
|
|
certTemplate.UnknownExtKeyUsage = append(certTemplate.UnknownExtKeyUsage, oid)
|
|
}
|
|
}
|
|
}
|
|
|
|
func HandleOtherCSRSANs(in *x509.CertificateRequest, sans map[string][]string) error {
|
|
certTemplate := &x509.Certificate{
|
|
DNSNames: in.DNSNames,
|
|
IPAddresses: in.IPAddresses,
|
|
EmailAddresses: in.EmailAddresses,
|
|
URIs: in.URIs,
|
|
}
|
|
if err := HandleOtherSANs(certTemplate, sans); err != nil {
|
|
return err
|
|
}
|
|
if len(certTemplate.ExtraExtensions) > 0 {
|
|
for _, v := range certTemplate.ExtraExtensions {
|
|
in.ExtraExtensions = append(in.ExtraExtensions, v)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func HandleOtherSANs(in *x509.Certificate, sans map[string][]string) error {
|
|
// If other SANs is empty we return which causes normal Go stdlib parsing
|
|
// of the other SAN types
|
|
if len(sans) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var rawValues []asn1.RawValue
|
|
|
|
// We need to generate an IMPLICIT sequence for compatibility with OpenSSL
|
|
// -- it's an open question what the default for RFC 5280 actually is, see
|
|
// https://github.com/openssl/openssl/issues/5091 -- so we have to use
|
|
// cryptobyte because using the asn1 package's marshaling always produces
|
|
// an EXPLICIT sequence. Note that asn1 is way too magical according to
|
|
// agl, and cryptobyte is modeled after the CBB/CBS bits that agl put into
|
|
// boringssl.
|
|
for oid, vals := range sans {
|
|
for _, val := range vals {
|
|
var b cryptobyte.Builder
|
|
oidStr, err := StringToOid(oid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.AddASN1ObjectIdentifier(oidStr)
|
|
b.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
|
|
b.AddASN1(cbasn1.UTF8String, func(b *cryptobyte.Builder) {
|
|
b.AddBytes([]byte(val))
|
|
})
|
|
})
|
|
m, err := b.Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: 0, Class: 2, IsCompound: true, Bytes: m})
|
|
}
|
|
}
|
|
|
|
// If other SANs is empty we return which causes normal Go stdlib parsing
|
|
// of the other SAN types
|
|
if len(rawValues) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Append any existing SANs, sans marshalling
|
|
rawValues = append(rawValues, marshalSANs(in.DNSNames, in.EmailAddresses, in.IPAddresses, in.URIs)...)
|
|
|
|
// Marshal and add to ExtraExtensions
|
|
ext := pkix.Extension{
|
|
// This is the defined OID for subjectAltName
|
|
Id: asn1.ObjectIdentifier{2, 5, 29, 17},
|
|
}
|
|
var err error
|
|
ext.Value, err = asn1.Marshal(rawValues)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
in.ExtraExtensions = append(in.ExtraExtensions, ext)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Note: Taken from the Go source code since it's not public, and used in the
|
|
// modified function below (which also uses these consts upstream)
|
|
const (
|
|
nameTypeEmail = 1
|
|
nameTypeDNS = 2
|
|
nameTypeURI = 6
|
|
nameTypeIP = 7
|
|
)
|
|
|
|
// Note: Taken from the Go source code since it's not public, plus changed to not marshal
|
|
// marshalSANs marshals a list of addresses into a the contents of an X.509
|
|
// SubjectAlternativeName extension.
|
|
func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL) []asn1.RawValue {
|
|
var rawValues []asn1.RawValue
|
|
for _, name := range dnsNames {
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeDNS, Class: 2, Bytes: []byte(name)})
|
|
}
|
|
for _, email := range emailAddresses {
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeEmail, Class: 2, Bytes: []byte(email)})
|
|
}
|
|
for _, rawIP := range ipAddresses {
|
|
// If possible, we always want to encode IPv4 addresses in 4 bytes.
|
|
ip := rawIP.To4()
|
|
if ip == nil {
|
|
ip = rawIP
|
|
}
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIP, Class: 2, Bytes: ip})
|
|
}
|
|
for _, uri := range uris {
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: 2, Bytes: []byte(uri.String())})
|
|
}
|
|
return rawValues
|
|
}
|
|
|
|
func StringToOid(in string) (asn1.ObjectIdentifier, error) {
|
|
split := strings.Split(in, ".")
|
|
ret := make(asn1.ObjectIdentifier, 0, len(split))
|
|
for _, v := range split {
|
|
i, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, i)
|
|
}
|
|
return asn1.ObjectIdentifier(ret), nil
|
|
}
|
|
|
|
// Returns default key bits for the specified key type, or the present value
|
|
// if keyBits is non-zero.
|
|
func DefaultOrValueKeyBits(keyType string, keyBits int) (int, error) {
|
|
if keyBits == 0 {
|
|
newValue, present := defaultAlgorithmKeyBits[keyType]
|
|
if present {
|
|
keyBits = newValue
|
|
} /* else {
|
|
// We cannot return an error here as ed25519 (and potentially ed448
|
|
// in the future) aren't in defaultAlgorithmKeyBits -- the value of
|
|
// the keyBits parameter is ignored under that algorithm.
|
|
} */
|
|
}
|
|
|
|
return keyBits, nil
|
|
}
|
|
|
|
// Returns default signature hash bit length for the specified key type and
|
|
// bits, or the present value if hashBits is non-zero. Returns an error under
|
|
// certain internal circumstances.
|
|
func DefaultOrValueHashBits(keyType string, keyBits int, hashBits int) (int, error) {
|
|
if keyType == "ec" {
|
|
// Enforcement of curve moved to selectSignatureAlgorithmForECDSA. See
|
|
// note there about why.
|
|
} else if keyType == "rsa" && hashBits == 0 {
|
|
// To match previous behavior (and ignoring NIST's recommendations for
|
|
// hash size to align with RSA key sizes), default to SHA-2-256.
|
|
hashBits = 256
|
|
} else if keyType == "ed25519" || keyType == "ed448" || keyType == "any" {
|
|
// No-op; ed25519 and ed448 internally specify their own hash and
|
|
// we do not need to select one. Double hashing isn't supported in
|
|
// certificate signing. Additionally, the any key type can't know
|
|
// what hash algorithm to use yet, so default to zero.
|
|
return 0, nil
|
|
}
|
|
|
|
return hashBits, nil
|
|
}
|
|
|
|
// Validates that the combination of keyType, keyBits, and hashBits are
|
|
// valid together; replaces individual calls to ValidateSignatureLength and
|
|
// ValidateKeyTypeLength. Also updates the value of keyBits and hashBits on
|
|
// return.
|
|
func ValidateDefaultOrValueKeyTypeSignatureLength(keyType string, keyBits int, hashBits int) (int, int, error) {
|
|
var err error
|
|
|
|
if keyBits, err = DefaultOrValueKeyBits(keyType, keyBits); err != nil {
|
|
return keyBits, hashBits, err
|
|
}
|
|
|
|
if err = ValidateKeyTypeLength(keyType, keyBits); err != nil {
|
|
return keyBits, hashBits, err
|
|
}
|
|
|
|
if hashBits, err = DefaultOrValueHashBits(keyType, keyBits, hashBits); err != nil {
|
|
return keyBits, hashBits, err
|
|
}
|
|
|
|
// Note that this check must come after we've selected a value for
|
|
// hashBits above, in the event it was left as the default, but we
|
|
// were allowed to update it.
|
|
if err = ValidateSignatureLength(keyType, hashBits); err != nil {
|
|
return keyBits, hashBits, err
|
|
}
|
|
|
|
return keyBits, hashBits, nil
|
|
}
|
|
|
|
// Validates that the length of the hash (in bits) used in the signature
|
|
// calculation is a known, approved value.
|
|
func ValidateSignatureLength(keyType string, hashBits int) error {
|
|
if keyType == "any" || keyType == "ec" || keyType == "ed25519" || keyType == "ed448" {
|
|
// ed25519 and ed448 include built-in hashing and is not externally
|
|
// configurable. There are three modes for each of these schemes:
|
|
//
|
|
// 1. Built-in hash (default, used in TLS, x509).
|
|
// 2. Double hash (notably used in some block-chain implementations,
|
|
// but largely regarded as a specialized use case with security
|
|
// concerns).
|
|
// 3. No hash (bring your own hash function, less commonly used).
|
|
//
|
|
// In all cases, we won't have a hash algorithm to validate here, so
|
|
// return nil.
|
|
//
|
|
// Additionally, when KeyType is any, we can't yet validate the
|
|
// signature algorithm size, so it takes the default zero value.
|
|
//
|
|
// When KeyType is ec, we also can't validate this value as we're
|
|
// forcefully ignoring the users' choice and specifying a value based
|
|
// on issuer type.
|
|
return nil
|
|
}
|
|
|
|
switch hashBits {
|
|
case 256:
|
|
case 384:
|
|
case 512:
|
|
default:
|
|
return fmt.Errorf("unsupported hash signature algorithm: %d", hashBits)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ValidateKeyTypeLength(keyType string, keyBits int) error {
|
|
switch keyType {
|
|
case "rsa":
|
|
if keyBits < rsaMinimumSecureKeySize {
|
|
return fmt.Errorf("RSA keys < %d bits are unsafe and not supported: got %d", rsaMinimumSecureKeySize, keyBits)
|
|
}
|
|
|
|
switch keyBits {
|
|
case 2048:
|
|
case 3072:
|
|
case 4096:
|
|
case 8192:
|
|
default:
|
|
return fmt.Errorf("unsupported bit length for RSA key: %d", keyBits)
|
|
}
|
|
case "ec":
|
|
_, present := expectedNISTPCurveHashBits[keyBits]
|
|
if !present {
|
|
return fmt.Errorf("unsupported bit length for EC key: %d", keyBits)
|
|
}
|
|
case "any", "ed25519":
|
|
default:
|
|
return fmt.Errorf("unknown key type %s", keyType)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateCertificate uses CreationBundle and the default rand.Reader to
|
|
// generate a cert/keypair.
|
|
func CreateCertificate(data *CreationBundle) (*ParsedCertBundle, error) {
|
|
return createCertificate(data, rand.Reader, generatePrivateKey)
|
|
}
|
|
|
|
// CreateCertificateWithRandomSource uses CreationBundle and a custom
|
|
// io.Reader for randomness to generate a cert/keypair.
|
|
func CreateCertificateWithRandomSource(data *CreationBundle, randReader io.Reader) (*ParsedCertBundle, error) {
|
|
return createCertificate(data, randReader, generatePrivateKey)
|
|
}
|
|
|
|
// KeyGenerator Allow us to override how/what generates the private key
|
|
type KeyGenerator func(keyType string, keyBits int, container ParsedPrivateKeyContainer, entropyReader io.Reader) error
|
|
|
|
func CreateCertificateWithKeyGenerator(data *CreationBundle, randReader io.Reader, keyGenerator KeyGenerator) (*ParsedCertBundle, error) {
|
|
return createCertificate(data, randReader, keyGenerator)
|
|
}
|
|
|
|
// Set correct RSA sig algo
|
|
func certTemplateSetSigAlgo(certTemplate *x509.Certificate, data *CreationBundle) {
|
|
if data.Params.UsePSS {
|
|
switch data.Params.SignatureBits {
|
|
case 256:
|
|
certTemplate.SignatureAlgorithm = x509.SHA256WithRSAPSS
|
|
case 384:
|
|
certTemplate.SignatureAlgorithm = x509.SHA384WithRSAPSS
|
|
case 512:
|
|
certTemplate.SignatureAlgorithm = x509.SHA512WithRSAPSS
|
|
}
|
|
} else {
|
|
switch data.Params.SignatureBits {
|
|
case 256:
|
|
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
|
|
case 384:
|
|
certTemplate.SignatureAlgorithm = x509.SHA384WithRSA
|
|
case 512:
|
|
certTemplate.SignatureAlgorithm = x509.SHA512WithRSA
|
|
}
|
|
}
|
|
}
|
|
|
|
// selectSignatureAlgorithmForRSA returns the proper x509.SignatureAlgorithm based on various properties set in the
|
|
// Creation Bundle parameter. This method will default to a SHA256 signature algorithm if the requested signature
|
|
// bits is not set/unknown.
|
|
func selectSignatureAlgorithmForRSA(data *CreationBundle) x509.SignatureAlgorithm {
|
|
if data.Params.UsePSS {
|
|
switch data.Params.SignatureBits {
|
|
case 256:
|
|
return x509.SHA256WithRSAPSS
|
|
case 384:
|
|
return x509.SHA384WithRSAPSS
|
|
case 512:
|
|
return x509.SHA512WithRSAPSS
|
|
default:
|
|
return x509.SHA256WithRSAPSS
|
|
}
|
|
}
|
|
|
|
switch data.Params.SignatureBits {
|
|
case 256:
|
|
return x509.SHA256WithRSA
|
|
case 384:
|
|
return x509.SHA384WithRSA
|
|
case 512:
|
|
return x509.SHA512WithRSA
|
|
default:
|
|
return x509.SHA256WithRSA
|
|
}
|
|
}
|
|
|
|
func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGenerator KeyGenerator) (*ParsedCertBundle, error) {
|
|
var err error
|
|
result := &ParsedCertBundle{}
|
|
|
|
serialNumber, err := GenerateSerialNumber()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := privateKeyGenerator(data.Params.KeyType,
|
|
data.Params.KeyBits,
|
|
result, randReader); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
subjKeyID, err := GetSubjKeyID(result.PrivateKey)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("error getting subject key ID: %s", err)}
|
|
}
|
|
|
|
certTemplate := &x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
NotAfter: data.Params.NotAfter,
|
|
IsCA: false,
|
|
SubjectKeyId: subjKeyID,
|
|
Subject: data.Params.Subject,
|
|
DNSNames: data.Params.DNSNames,
|
|
EmailAddresses: data.Params.EmailAddresses,
|
|
IPAddresses: data.Params.IPAddresses,
|
|
URIs: data.Params.URIs,
|
|
}
|
|
if data.Params.NotBeforeDuration > 0 {
|
|
certTemplate.NotBefore = time.Now().Add(-1 * data.Params.NotBeforeDuration)
|
|
}
|
|
|
|
if err := HandleOtherSANs(certTemplate, data.Params.OtherSANs); err != nil {
|
|
return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()}
|
|
}
|
|
|
|
// Add this before calling addKeyUsages
|
|
if data.SigningBundle == nil {
|
|
certTemplate.IsCA = true
|
|
} else if data.Params.BasicConstraintsValidForNonCA {
|
|
certTemplate.BasicConstraintsValid = true
|
|
certTemplate.IsCA = false
|
|
}
|
|
|
|
// This will only be filled in from the generation paths
|
|
if len(data.Params.PermittedDNSDomains) > 0 {
|
|
certTemplate.PermittedDNSDomains = data.Params.PermittedDNSDomains
|
|
certTemplate.PermittedDNSDomainsCritical = true
|
|
}
|
|
|
|
AddPolicyIdentifiers(data, certTemplate)
|
|
|
|
AddKeyUsages(data, certTemplate)
|
|
|
|
AddExtKeyUsageOids(data, certTemplate)
|
|
|
|
certTemplate.IssuingCertificateURL = data.Params.URLs.IssuingCertificates
|
|
certTemplate.CRLDistributionPoints = data.Params.URLs.CRLDistributionPoints
|
|
certTemplate.OCSPServer = data.Params.URLs.OCSPServers
|
|
|
|
var certBytes []byte
|
|
if data.SigningBundle != nil {
|
|
privateKeyType := data.SigningBundle.PrivateKeyType
|
|
if privateKeyType == ManagedPrivateKey {
|
|
privateKeyType = GetPrivateKeyTypeFromSigner(data.SigningBundle.PrivateKey)
|
|
}
|
|
switch privateKeyType {
|
|
case RSAPrivateKey:
|
|
certTemplateSetSigAlgo(certTemplate, data)
|
|
case Ed25519PrivateKey:
|
|
certTemplate.SignatureAlgorithm = x509.PureEd25519
|
|
case ECPrivateKey:
|
|
certTemplate.SignatureAlgorithm = selectSignatureAlgorithmForECDSA(data.SigningBundle.PrivateKey.Public(), data.Params.SignatureBits)
|
|
}
|
|
|
|
caCert := data.SigningBundle.Certificate
|
|
certTemplate.AuthorityKeyId = caCert.SubjectKeyId
|
|
|
|
certBytes, err = x509.CreateCertificate(randReader, certTemplate, caCert, result.PrivateKey.Public(), data.SigningBundle.PrivateKey)
|
|
} else {
|
|
// Creating a self-signed root
|
|
if data.Params.MaxPathLength == 0 {
|
|
certTemplate.MaxPathLen = 0
|
|
certTemplate.MaxPathLenZero = true
|
|
} else {
|
|
certTemplate.MaxPathLen = data.Params.MaxPathLength
|
|
}
|
|
|
|
switch data.Params.KeyType {
|
|
case "rsa":
|
|
certTemplateSetSigAlgo(certTemplate, data)
|
|
case "ed25519":
|
|
certTemplate.SignatureAlgorithm = x509.PureEd25519
|
|
case "ec":
|
|
certTemplate.SignatureAlgorithm = selectSignatureAlgorithmForECDSA(result.PrivateKey.Public(), data.Params.SignatureBits)
|
|
}
|
|
|
|
certTemplate.AuthorityKeyId = subjKeyID
|
|
certTemplate.BasicConstraintsValid = true
|
|
certBytes, err = x509.CreateCertificate(randReader, certTemplate, certTemplate, result.PrivateKey.Public(), result.PrivateKey)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)}
|
|
}
|
|
|
|
result.CertificateBytes = certBytes
|
|
result.Certificate, err = x509.ParseCertificate(certBytes)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
|
|
}
|
|
|
|
if data.SigningBundle != nil {
|
|
if (len(data.SigningBundle.Certificate.AuthorityKeyId) > 0 &&
|
|
!bytes.Equal(data.SigningBundle.Certificate.AuthorityKeyId, data.SigningBundle.Certificate.SubjectKeyId)) ||
|
|
data.Params.ForceAppendCaChain {
|
|
var chain []*CertBlock
|
|
|
|
signingChain := data.SigningBundle.CAChain
|
|
// Some bundles already include the root included in the chain, so don't include it twice.
|
|
if len(signingChain) == 0 || !bytes.Equal(signingChain[0].Bytes, data.SigningBundle.CertificateBytes) {
|
|
chain = append(chain, &CertBlock{
|
|
Certificate: data.SigningBundle.Certificate,
|
|
Bytes: data.SigningBundle.CertificateBytes,
|
|
})
|
|
}
|
|
|
|
if len(signingChain) > 0 {
|
|
chain = append(chain, signingChain...)
|
|
}
|
|
|
|
result.CAChain = chain
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func selectSignatureAlgorithmForECDSA(pub crypto.PublicKey, signatureBits int) x509.SignatureAlgorithm {
|
|
// Previously we preferred the user-specified signature bits for ECDSA
|
|
// keys. However, this could result in using a longer hash function than
|
|
// the underlying NIST P-curve will encode (e.g., a SHA-512 hash with a
|
|
// P-256 key). This isn't ideal: the hash is implicitly truncated
|
|
// (effectively turning it into SHA-512/256) and we then need to rely
|
|
// on the prefix security of the hash. Since both NIST and Mozilla guidance
|
|
// suggest instead using the correct hash function, we should prefer that
|
|
// over the operator-specified signatureBits.
|
|
//
|
|
// Lastly, note that pub above needs to be the _signer's_ public key;
|
|
// the issue with DefaultOrValueHashBits is that it is called at role
|
|
// configuration time, which might _precede_ issuer generation. Thus
|
|
// it only has access to the desired key type and not the actual issuer.
|
|
// The reference from that function is reproduced below:
|
|
//
|
|
// > To comply with BSI recommendations Section 4.2 and Mozilla root
|
|
// > store policy section 5.1.2, enforce that NIST P-curves use a hash
|
|
// > length corresponding to curve length. Note that ed25519 does not
|
|
// > implement the "ec" key type.
|
|
key, ok := pub.(*ecdsa.PublicKey)
|
|
if !ok {
|
|
return x509.ECDSAWithSHA256
|
|
}
|
|
switch key.Curve {
|
|
case elliptic.P224(), elliptic.P256():
|
|
return x509.ECDSAWithSHA256
|
|
case elliptic.P384():
|
|
return x509.ECDSAWithSHA384
|
|
case elliptic.P521():
|
|
return x509.ECDSAWithSHA512
|
|
default:
|
|
return x509.ECDSAWithSHA256
|
|
}
|
|
}
|
|
|
|
var (
|
|
ExtensionBasicConstraintsOID = []int{2, 5, 29, 19}
|
|
ExtensionSubjectAltNameOID = []int{2, 5, 29, 17}
|
|
)
|
|
|
|
// CreateCSR creates a CSR with the default rand.Reader to
|
|
// generate a cert/keypair. This is currently only meant
|
|
// for use when generating an intermediate certificate.
|
|
func CreateCSR(data *CreationBundle, addBasicConstraints bool) (*ParsedCSRBundle, error) {
|
|
return createCSR(data, addBasicConstraints, rand.Reader, generatePrivateKey)
|
|
}
|
|
|
|
// CreateCSRWithRandomSource creates a CSR with a custom io.Reader
|
|
// for randomness to generate a cert/keypair.
|
|
func CreateCSRWithRandomSource(data *CreationBundle, addBasicConstraints bool, randReader io.Reader) (*ParsedCSRBundle, error) {
|
|
return createCSR(data, addBasicConstraints, randReader, generatePrivateKey)
|
|
}
|
|
|
|
// CreateCSRWithKeyGenerator creates a CSR with a custom io.Reader
|
|
// for randomness to generate a cert/keypair with the provided private key generator.
|
|
func CreateCSRWithKeyGenerator(data *CreationBundle, addBasicConstraints bool, randReader io.Reader, keyGenerator KeyGenerator) (*ParsedCSRBundle, error) {
|
|
return createCSR(data, addBasicConstraints, randReader, keyGenerator)
|
|
}
|
|
|
|
func createCSR(data *CreationBundle, addBasicConstraints bool, randReader io.Reader, keyGenerator KeyGenerator) (*ParsedCSRBundle, error) {
|
|
var err error
|
|
result := &ParsedCSRBundle{}
|
|
|
|
if err := keyGenerator(data.Params.KeyType,
|
|
data.Params.KeyBits,
|
|
result, randReader); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Like many root CAs, other information is ignored
|
|
|
|
csrTemplate := &x509.CertificateRequest{
|
|
Subject: data.Params.Subject,
|
|
DNSNames: data.Params.DNSNames,
|
|
EmailAddresses: data.Params.EmailAddresses,
|
|
IPAddresses: data.Params.IPAddresses,
|
|
URIs: data.Params.URIs,
|
|
}
|
|
|
|
if data.Params.KeyUsage != 0 {
|
|
keyUsageExt, err := marshalKeyUsage(data.Params.KeyUsage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed marshalling existing key usage: %w", err)
|
|
}
|
|
csrTemplate.ExtraExtensions = []pkix.Extension{keyUsageExt}
|
|
}
|
|
|
|
if err := HandleOtherCSRSANs(csrTemplate, data.Params.OtherSANs); err != nil {
|
|
return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()}
|
|
}
|
|
|
|
if addBasicConstraints {
|
|
type basicConstraints struct {
|
|
IsCA bool `asn1:"optional"`
|
|
MaxPathLen int `asn1:"optional,default:-1"`
|
|
}
|
|
val, err := asn1.Marshal(basicConstraints{IsCA: true, MaxPathLen: -1})
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling basic constraints: {{err}}", err).Error()}
|
|
}
|
|
ext := pkix.Extension{
|
|
Id: ExtensionBasicConstraintsOID,
|
|
Value: val,
|
|
Critical: true,
|
|
}
|
|
csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, ext)
|
|
}
|
|
|
|
switch data.Params.KeyType {
|
|
case "rsa":
|
|
// use specified RSA algorithm defaulting to the appropriate SHA256 RSA signature type
|
|
csrTemplate.SignatureAlgorithm = selectSignatureAlgorithmForRSA(data)
|
|
case "ec":
|
|
csrTemplate.SignatureAlgorithm = selectSignatureAlgorithmForECDSA(result.PrivateKey.Public(), data.Params.SignatureBits)
|
|
case "ed25519":
|
|
csrTemplate.SignatureAlgorithm = x509.PureEd25519
|
|
}
|
|
|
|
csr, err := x509.CreateCertificateRequest(randReader, csrTemplate, result.PrivateKey)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)}
|
|
}
|
|
|
|
result.CSRBytes = csr
|
|
result.CSR, err = x509.ParseCertificateRequest(csr)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %v", err)}
|
|
}
|
|
|
|
if err = result.CSR.CheckSignature(); err != nil {
|
|
return nil, errors.New("failed signature validation for CSR")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Marshal Key Usage taken from: https://cs.opensource.google/go/go/+/master:src/crypto/x509/x509.go;drc=370a6959e3edd9d901446661ee9fef3f72d150d4;l=1339
|
|
// It requires the two following functions (reverseBitsInAByte and asn1BitLength) below, taken from the same code base
|
|
// It's used for that code only for certificates; but we need to marshal key usage on CSR (createCSR above) as well
|
|
func marshalKeyUsage(ku x509.KeyUsage) (pkix.Extension, error) {
|
|
ext := pkix.Extension{Id: KeyUsageOID, Critical: true}
|
|
|
|
var a [2]byte
|
|
a[0] = reverseBitsInAByte(byte(ku))
|
|
a[1] = reverseBitsInAByte(byte(ku >> 8))
|
|
|
|
l := 1
|
|
if a[1] != 0 {
|
|
l = 2
|
|
}
|
|
|
|
bitString := a[:l]
|
|
var err error
|
|
ext.Value, err = asn1.Marshal(asn1.BitString{Bytes: bitString, BitLength: asn1BitLength(bitString)})
|
|
return ext, err
|
|
}
|
|
|
|
// reverseBitsInAByte Taken from: https://cs.opensource.google/go/go/+/master:src/crypto/x509/x509.go;drc=370a6959e3edd9d901446661ee9fef3f72d150d4;l=1011
|
|
// needed for marshalKeyUsage called above
|
|
func reverseBitsInAByte(in byte) byte {
|
|
b1 := in>>4 | in<<4
|
|
b2 := b1>>2&0x33 | b1<<2&0xcc
|
|
b3 := b2>>1&0x55 | b2<<1&0xaa
|
|
return b3
|
|
}
|
|
|
|
// asn1BitLength returns the bit-length of bitString by considering the
|
|
// most-significant bit in a byte to be the "first" bit. This convention
|
|
// matches ASN.1, but differs from almost everything else.
|
|
func asn1BitLength(bitString []byte) int {
|
|
bitLen := len(bitString) * 8
|
|
|
|
for i := range bitString {
|
|
b := bitString[len(bitString)-i-1]
|
|
|
|
for bit := uint(0); bit < 8; bit++ {
|
|
if (b>>bit)&1 == 1 {
|
|
return bitLen
|
|
}
|
|
bitLen--
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// SignCertificate performs the heavy lifting
|
|
// of generating a certificate from a CSR.
|
|
// Returns a ParsedCertBundle sans private keys.
|
|
func SignCertificate(data *CreationBundle) (*ParsedCertBundle, error) {
|
|
return signCertificate(data, rand.Reader)
|
|
}
|
|
|
|
// SignCertificateWithRandomSource generates a certificate
|
|
// from a CSR, using custom randomness from the randReader.
|
|
// Returns a ParsedCertBundle sans private keys.
|
|
func SignCertificateWithRandomSource(data *CreationBundle, randReader io.Reader) (*ParsedCertBundle, error) {
|
|
return signCertificate(data, randReader)
|
|
}
|
|
|
|
func signCertificate(data *CreationBundle, randReader io.Reader) (*ParsedCertBundle, error) {
|
|
switch {
|
|
case data == nil:
|
|
return nil, errutil.UserError{Err: "nil data bundle given to signCertificate"}
|
|
case data.Params == nil:
|
|
return nil, errutil.UserError{Err: "nil parameters given to signCertificate"}
|
|
case data.SigningBundle == nil:
|
|
return nil, errutil.UserError{Err: "nil signing bundle given to signCertificate"}
|
|
case data.CSR == nil:
|
|
return nil, errutil.UserError{Err: "nil csr given to signCertificate"}
|
|
}
|
|
|
|
if !data.Params.IgnoreCSRSignature {
|
|
if err := data.CSR.CheckSignature(); err != nil {
|
|
return nil, errutil.UserError{Err: "request signature invalid"}
|
|
}
|
|
}
|
|
|
|
result := &ParsedCertBundle{}
|
|
|
|
serialNumber, err := GenerateSerialNumber()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
subjKeyID, err := getSubjectKeyIDFromBundle(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
caCert := data.SigningBundle.Certificate
|
|
|
|
certTemplate := &x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: data.Params.Subject,
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
NotAfter: data.Params.NotAfter,
|
|
SubjectKeyId: subjKeyID[:],
|
|
AuthorityKeyId: caCert.SubjectKeyId,
|
|
}
|
|
if data.Params.NotBeforeDuration > 0 {
|
|
certTemplate.NotBefore = time.Now().Add(-1 * data.Params.NotBeforeDuration)
|
|
}
|
|
|
|
privateKeyType := data.SigningBundle.PrivateKeyType
|
|
if privateKeyType == ManagedPrivateKey {
|
|
privateKeyType = GetPrivateKeyTypeFromSigner(data.SigningBundle.PrivateKey)
|
|
}
|
|
|
|
switch privateKeyType {
|
|
case RSAPrivateKey:
|
|
certTemplateSetSigAlgo(certTemplate, data)
|
|
case ECPrivateKey:
|
|
switch data.Params.SignatureBits {
|
|
case 256:
|
|
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256
|
|
case 384:
|
|
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA384
|
|
case 512:
|
|
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA512
|
|
}
|
|
}
|
|
|
|
if data.Params.UseCSRValues {
|
|
certTemplate.Subject = data.CSR.Subject
|
|
certTemplate.Subject.ExtraNames = certTemplate.Subject.Names
|
|
|
|
certTemplate.DNSNames = data.CSR.DNSNames
|
|
certTemplate.EmailAddresses = data.CSR.EmailAddresses
|
|
certTemplate.IPAddresses = data.CSR.IPAddresses
|
|
certTemplate.URIs = data.CSR.URIs
|
|
|
|
for _, name := range data.CSR.Extensions {
|
|
if !name.Id.Equal(ExtensionBasicConstraintsOID) && !(len(data.Params.OtherSANs) > 0 && name.Id.Equal(ExtensionSubjectAltNameOID)) {
|
|
certTemplate.ExtraExtensions = append(certTemplate.ExtraExtensions, name)
|
|
}
|
|
}
|
|
|
|
} else {
|
|
certTemplate.DNSNames = data.Params.DNSNames
|
|
certTemplate.EmailAddresses = data.Params.EmailAddresses
|
|
certTemplate.IPAddresses = data.Params.IPAddresses
|
|
certTemplate.URIs = data.Params.URIs
|
|
}
|
|
|
|
if err := HandleOtherSANs(certTemplate, data.Params.OtherSANs); err != nil {
|
|
return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()}
|
|
}
|
|
|
|
AddPolicyIdentifiers(data, certTemplate)
|
|
|
|
AddKeyUsages(data, certTemplate)
|
|
|
|
AddExtKeyUsageOids(data, certTemplate)
|
|
|
|
var certBytes []byte
|
|
|
|
certTemplate.IssuingCertificateURL = data.Params.URLs.IssuingCertificates
|
|
certTemplate.CRLDistributionPoints = data.Params.URLs.CRLDistributionPoints
|
|
certTemplate.OCSPServer = data.SigningBundle.URLs.OCSPServers
|
|
|
|
if data.Params.IsCA {
|
|
certTemplate.BasicConstraintsValid = true
|
|
certTemplate.IsCA = true
|
|
|
|
if data.SigningBundle.Certificate.MaxPathLen == 0 &&
|
|
data.SigningBundle.Certificate.MaxPathLenZero {
|
|
return nil, errutil.UserError{Err: "signing certificate has a max path length of zero, and cannot issue further CA certificates"}
|
|
}
|
|
|
|
certTemplate.MaxPathLen = data.Params.MaxPathLength
|
|
if certTemplate.MaxPathLen == 0 {
|
|
certTemplate.MaxPathLenZero = true
|
|
}
|
|
} else if data.Params.BasicConstraintsValidForNonCA {
|
|
certTemplate.BasicConstraintsValid = true
|
|
certTemplate.IsCA = false
|
|
}
|
|
|
|
if len(data.Params.PermittedDNSDomains) > 0 {
|
|
certTemplate.PermittedDNSDomains = data.Params.PermittedDNSDomains
|
|
certTemplate.PermittedDNSDomainsCritical = true
|
|
}
|
|
|
|
certBytes, err = x509.CreateCertificate(randReader, certTemplate, caCert, data.CSR.PublicKey, data.SigningBundle.PrivateKey)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)}
|
|
}
|
|
|
|
result.CertificateBytes = certBytes
|
|
result.Certificate, err = x509.ParseCertificate(certBytes)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
|
|
}
|
|
|
|
result.CAChain = data.SigningBundle.GetFullChain()
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func NewCertPool(reader io.Reader) (*x509.CertPool, error) {
|
|
pemBlock, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
certs, err := ParseCertsPEM(pemBlock)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading certs: %s", err)
|
|
}
|
|
pool := x509.NewCertPool()
|
|
for _, cert := range certs {
|
|
pool.AddCert(cert)
|
|
}
|
|
return pool, nil
|
|
}
|
|
|
|
// ParseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array
|
|
// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates
|
|
func ParseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) {
|
|
ok := false
|
|
certs := []*x509.Certificate{}
|
|
for len(pemCerts) > 0 {
|
|
var block *pem.Block
|
|
block, pemCerts = pem.Decode(pemCerts)
|
|
if block == nil {
|
|
break
|
|
}
|
|
// Only use PEM "CERTIFICATE" blocks without extra headers
|
|
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
|
|
continue
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return certs, err
|
|
}
|
|
|
|
certs = append(certs, cert)
|
|
ok = true
|
|
}
|
|
|
|
if !ok {
|
|
return certs, errors.New("data does not contain any valid RSA or ECDSA certificates")
|
|
}
|
|
return certs, nil
|
|
}
|
|
|
|
// GetPublicKeySize returns the key size in bits for a given arbitrary crypto.PublicKey
|
|
// Returns -1 for an unsupported key type.
|
|
func GetPublicKeySize(key crypto.PublicKey) int {
|
|
if key, ok := key.(*rsa.PublicKey); ok {
|
|
return key.Size() * 8
|
|
}
|
|
if key, ok := key.(*ecdsa.PublicKey); ok {
|
|
return key.Params().BitSize
|
|
}
|
|
if key, ok := key.(ed25519.PublicKey); ok {
|
|
return len(key) * 8
|
|
}
|
|
if key, ok := key.(dsa.PublicKey); ok {
|
|
return key.Y.BitLen()
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// CreateKeyBundle create a KeyBundle struct object which includes a generated key
|
|
// of keyType with keyBits leveraging the randomness from randReader.
|
|
func CreateKeyBundle(keyType string, keyBits int, randReader io.Reader) (KeyBundle, error) {
|
|
return CreateKeyBundleWithKeyGenerator(keyType, keyBits, randReader, generatePrivateKey)
|
|
}
|
|
|
|
// CreateKeyBundleWithKeyGenerator create a KeyBundle struct object which includes
|
|
// a generated key of keyType with keyBits leveraging the randomness from randReader and
|
|
// delegates the actual key generation to keyGenerator
|
|
func CreateKeyBundleWithKeyGenerator(keyType string, keyBits int, randReader io.Reader, keyGenerator KeyGenerator) (KeyBundle, error) {
|
|
result := KeyBundle{}
|
|
if err := keyGenerator(keyType, keyBits, &result, randReader); err != nil {
|
|
return result, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// CreateDeltaCRLIndicatorExt allows creating correctly formed delta CRLs
|
|
// that point back to the last complete CRL that they're based on.
|
|
func CreateDeltaCRLIndicatorExt(completeCRLNumber int64) (pkix.Extension, error) {
|
|
bigNum := big.NewInt(completeCRLNumber)
|
|
bigNumValue, err := asn1.Marshal(bigNum)
|
|
if err != nil {
|
|
return pkix.Extension{}, fmt.Errorf("unable to marshal complete CRL number (%v): %v", completeCRLNumber, err)
|
|
}
|
|
return pkix.Extension{
|
|
Id: DeltaCRLIndicatorOID,
|
|
// > When a conforming CRL issuer generates a delta CRL, the delta
|
|
// > CRL MUST include a critical delta CRL indicator extension.
|
|
Critical: true,
|
|
// This extension only includes the complete CRL number:
|
|
//
|
|
// > BaseCRLNumber ::= CRLNumber
|
|
//
|
|
// But, this needs to be encoded as a big number for encoding/asn1
|
|
// to work properly.
|
|
Value: bigNumValue,
|
|
}, nil
|
|
}
|
|
|
|
// ParseBasicConstraintExtension parses a basic constraint pkix.Extension, useful if attempting to validate
|
|
// CSRs are requesting CA privileges as Go does not expose its implementation. Values returned are
|
|
// IsCA, MaxPathLen or error. If MaxPathLen was not set, a value of -1 will be returned.
|
|
func ParseBasicConstraintExtension(ext pkix.Extension) (bool, int, error) {
|
|
if !ext.Id.Equal(ExtensionBasicConstraintsOID) {
|
|
return false, -1, fmt.Errorf("passed in extension was not a basic constraint extension")
|
|
}
|
|
|
|
// All elements are set to optional here, as it is possible that we receive a CSR with the extension
|
|
// containing an empty sequence by spec.
|
|
type basicConstraints struct {
|
|
IsCA bool `asn1:"optional"`
|
|
MaxPathLen int `asn1:"optional,default:-1"`
|
|
}
|
|
bc := &basicConstraints{}
|
|
leftOver, err := asn1.Unmarshal(ext.Value, bc)
|
|
if err != nil {
|
|
return false, -1, fmt.Errorf("failed unmarshalling extension value: %w", err)
|
|
}
|
|
|
|
numLeftOver := len(bytes.TrimSpace(leftOver))
|
|
if numLeftOver > 0 {
|
|
return false, -1, fmt.Errorf("%d extra bytes within basic constraints value extension", numLeftOver)
|
|
}
|
|
|
|
return bc.IsCA, bc.MaxPathLen, nil
|
|
}
|
|
|
|
// CreateBasicConstraintExtension create a basic constraint extension based on inputs,
|
|
// if isCa is false, an empty value sequence will be returned with maxPath being
|
|
// ignored. If isCa is true maxPath can be set to -1 to not set a maxPath value.
|
|
func CreateBasicConstraintExtension(isCa bool, maxPath int) (pkix.Extension, error) {
|
|
var asn1Bytes []byte
|
|
var err error
|
|
|
|
switch {
|
|
case isCa && maxPath >= 0:
|
|
CaAndMaxPathLen := struct {
|
|
IsCa bool `asn1:""`
|
|
MaxPathLen int `asn1:""`
|
|
}{
|
|
IsCa: isCa,
|
|
MaxPathLen: maxPath,
|
|
}
|
|
asn1Bytes, err = asn1.Marshal(CaAndMaxPathLen)
|
|
case isCa && maxPath < 0:
|
|
justCa := struct {
|
|
IsCa bool `asn1:""`
|
|
}{IsCa: isCa}
|
|
asn1Bytes, err = asn1.Marshal(justCa)
|
|
default:
|
|
asn1Bytes, err = asn1.Marshal(struct{}{})
|
|
}
|
|
|
|
if err != nil {
|
|
return pkix.Extension{}, err
|
|
}
|
|
|
|
return pkix.Extension{
|
|
Id: ExtensionBasicConstraintsOID,
|
|
Critical: true,
|
|
Value: asn1Bytes,
|
|
}, nil
|
|
}
|
|
|
|
// GetOtherSANsFromX509Extensions is used to find all the extensions which have the identifier (OID) of
|
|
// a SAN (Subject Alternative Name), and then look at each extension to find out if it is one of a set of
|
|
// well-known types (like IP SANs) or "other". Currently, the only OtherSANs vault supports are of type UTF8.
|
|
func GetOtherSANsFromX509Extensions(exts []pkix.Extension) ([]OtherNameUtf8, error) {
|
|
var ret []OtherNameUtf8
|
|
for _, ext := range exts {
|
|
if !ext.Id.Equal(OidExtensionSubjectAltName) {
|
|
continue
|
|
}
|
|
err := forEachSAN(ext.Value, func(tag int, data []byte) error {
|
|
if tag != 0 {
|
|
return nil
|
|
}
|
|
|
|
var other OtherNameRaw
|
|
_, err := asn1.UnmarshalWithParams(data, &other, "tag:0")
|
|
if err != nil {
|
|
return fmt.Errorf("could not parse requested other SAN: %w", err)
|
|
}
|
|
val, err := other.ExtractUTF8String()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ret = append(ret, *val)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func forEachSAN(extension []byte, callback func(tag int, data []byte) error) error {
|
|
// RFC 5280, 4.2.1.6
|
|
|
|
// SubjectAltName ::= GeneralNames
|
|
//
|
|
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
|
//
|
|
// GeneralName ::= CHOICE {
|
|
// otherName [0] OtherName,
|
|
// rfc822Name [1] IA5String,
|
|
// dNSName [2] IA5String,
|
|
// x400Address [3] ORAddress,
|
|
// directoryName [4] Name,
|
|
// ediPartyName [5] EDIPartyName,
|
|
// uniformResourceIdentifier [6] IA5String,
|
|
// iPAddress [7] OCTET STRING,
|
|
// registeredID [8] OBJECT IDENTIFIER }
|
|
var seq asn1.RawValue
|
|
rest, err := asn1.Unmarshal(extension, &seq)
|
|
if err != nil {
|
|
return err
|
|
} else if len(rest) != 0 {
|
|
return fmt.Errorf("x509: trailing data after X.509 extension")
|
|
}
|
|
if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
|
|
return asn1.StructuralError{Msg: "bad SAN sequence"}
|
|
}
|
|
|
|
rest = seq.Bytes
|
|
for len(rest) > 0 {
|
|
var v asn1.RawValue
|
|
rest, err = asn1.Unmarshal(rest, &v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := callback(v.Tag, v.FullBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// otherNameRaw describes a name related to a certificate which is not in one
|
|
// of the standard name formats. RFC 5280, 4.2.1.6:
|
|
//
|
|
// OtherName ::= SEQUENCE {
|
|
// type-id OBJECT IDENTIFIER,
|
|
// Value [0] EXPLICIT ANY DEFINED BY type-id }
|
|
type OtherNameRaw struct {
|
|
TypeID asn1.ObjectIdentifier
|
|
Value asn1.RawValue
|
|
}
|
|
|
|
type OtherNameUtf8 struct {
|
|
Oid string
|
|
Value string
|
|
}
|
|
|
|
// String() turns an OtherNameUtf8 object into the storage or field-value used to assign that name
|
|
// to a certificate in an API call
|
|
func (o OtherNameUtf8) String() string {
|
|
return fmt.Sprintf("%s;%s:%s", o.Oid, "UTF-8", o.Value)
|
|
}
|
|
|
|
// ExtractUTF8String returns the UTF8 string contained in the Value, or an error
|
|
// if none is present.
|
|
func (oraw *OtherNameRaw) ExtractUTF8String() (*OtherNameUtf8, error) {
|
|
svalue := cryptobyte.String(oraw.Value.Bytes)
|
|
var outTag cbasn1.Tag
|
|
var val cryptobyte.String
|
|
read := svalue.ReadAnyASN1(&val, &outTag)
|
|
|
|
if read && outTag == asn1.TagUTF8String {
|
|
return &OtherNameUtf8{Oid: oraw.TypeID.String(), Value: string(val)}, nil
|
|
}
|
|
return nil, fmt.Errorf("no UTF-8 string found in OtherName")
|
|
}
|
|
|
|
func getOtherSANsStringFromExtensions(exts []pkix.Extension) (string, error) {
|
|
otherNames, err := GetOtherSANsFromX509Extensions(exts)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
otherSansList := make([]string, len(otherNames))
|
|
for i, otherName := range otherNames {
|
|
otherSansList[i] = otherName.String()
|
|
}
|
|
|
|
otherSans := strings.Join(otherSansList, ",")
|
|
|
|
return otherSans, nil
|
|
}
|
|
|
|
func getOtherSANsMapFromExtensions(exts []pkix.Extension) (map[string][]string, error) {
|
|
otherNames, err := GetOtherSANsFromX509Extensions(exts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
otherSans := make(map[string][]string)
|
|
for _, name := range otherNames {
|
|
if otherSans[name.Oid] == nil {
|
|
otherSans[name.Oid] = []string{name.Value}
|
|
} else {
|
|
otherSans[name.Oid] = append(otherSans[name.Oid], name.Value)
|
|
}
|
|
}
|
|
|
|
return otherSans, nil
|
|
}
|
|
|
|
func getKeyUsage(exts []pkix.Extension) (x509.KeyUsage, error) {
|
|
keyUsage := x509.KeyUsage(0)
|
|
for _, ext := range exts {
|
|
if ext.Id.Equal(KeyUsageOID) {
|
|
return parseKeyUsageExtension(ext.Value)
|
|
}
|
|
}
|
|
return keyUsage, nil
|
|
}
|
|
|
|
// Taken from: https://cs.opensource.google/go/go/+/master:src/crypto/x509/parser.go;drc=dd84bb682482390bb8465482cb7b13d2e3b17297;l=319
|
|
func parseKeyUsageExtension(der cryptobyte.String) (x509.KeyUsage, error) {
|
|
var usageBits asn1.BitString
|
|
if !der.ReadASN1BitString(&usageBits) {
|
|
return 0, errors.New("x509: invalid key usage")
|
|
}
|
|
|
|
var usage int
|
|
for i := 0; i < 9; i++ {
|
|
if usageBits.At(i) != 0 {
|
|
usage |= 1 << uint(i)
|
|
}
|
|
}
|
|
return x509.KeyUsage(usage), nil
|
|
}
|
|
|
|
func getExtKeyUsageOids(exts []pkix.Extension) ([]string, error) {
|
|
keyUsageOidStrings := make([]string, 0)
|
|
keyUsageOids := make([]asn1.ObjectIdentifier, 0)
|
|
for _, ext := range exts {
|
|
if ext.Id.Equal(ExtendedKeyUsageOID) {
|
|
_, err := asn1.Unmarshal(ext.Value, &keyUsageOids)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to unmarshal KeyUsageOid extension: %w", err)
|
|
}
|
|
for _, oid := range keyUsageOids {
|
|
keyUsageOidStrings = append(keyUsageOidStrings, oid.String())
|
|
}
|
|
return keyUsageOidStrings, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func getPolicyIdentifiers(exts []pkix.Extension) ([]string, error) {
|
|
policyIdentifiers := make([]string, 0)
|
|
for _, ext := range exts {
|
|
if ext.Id.Equal(policyInformationOid) {
|
|
// PolicyInformation ::= SEQUENCE {
|
|
// policyIdentifier CertPolicyId,
|
|
// policyQualifiers SEQUENCE SIZE (1..MAX) OF
|
|
// PolicyQualifierInfo OPTIONAL }
|
|
type policyInformation struct {
|
|
PolicyIdentifier asn1.ObjectIdentifier `asn1:"optional"`
|
|
PolicyQualifier any `asn1:"optional"`
|
|
}
|
|
policies := make([]policyInformation, 0)
|
|
_, err := asn1.Unmarshal(ext.Value, &policies)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, policy := range policies {
|
|
policyIdentifiers = append(policyIdentifiers, policy.PolicyIdentifier.String())
|
|
}
|
|
return policyIdentifiers, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// Translate Certificates and CSRs into Certificate Template
|
|
// Four "Types" Here: Certificates; Certificate Signing Requests; Fields map[string]interface{}; Creation Parameters
|
|
|
|
func ParseCertificateToCreationParameters(certificate x509.Certificate) (creationParameters CreationParameters, err error) {
|
|
otherSans, err := getOtherSANsMapFromExtensions(certificate.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
|
|
creationParameters = CreationParameters{
|
|
Subject: removeNames(certificate.Subject),
|
|
DNSNames: certificate.DNSNames,
|
|
EmailAddresses: certificate.EmailAddresses,
|
|
IPAddresses: certificate.IPAddresses,
|
|
URIs: certificate.URIs,
|
|
OtherSANs: otherSans,
|
|
IsCA: certificate.IsCA,
|
|
KeyType: GetKeyType(certificate.PublicKeyAlgorithm.String()),
|
|
KeyBits: FindBitLength(certificate.PublicKey),
|
|
NotAfter: certificate.NotAfter,
|
|
KeyUsage: certificate.KeyUsage,
|
|
// ExtKeyUsage: We use ExtKeyUsageOIDs instead as the more general field
|
|
// ExtKeyUsageOIDs: this is an extension that may not be set, so is handled below
|
|
// PolicyIdentifiers: this is an extension that may not be set, so is handled below
|
|
BasicConstraintsValidForNonCA: certificate.BasicConstraintsValid,
|
|
SignatureBits: FindSignatureBits(certificate.SignatureAlgorithm),
|
|
UsePSS: IsPSS(certificate.SignatureAlgorithm),
|
|
// The following two values are on creation parameters, but are impossible to parse from the certificate
|
|
// ForceAppendCaChain
|
|
// UseCSRValues
|
|
PermittedDNSDomains: certificate.PermittedDNSDomains,
|
|
// URLs: punting on this for now
|
|
MaxPathLength: certificate.MaxPathLen,
|
|
NotBeforeDuration: time.Now().Sub(certificate.NotBefore), // Assumes Certificate was created this moment
|
|
SKID: certificate.SubjectKeyId,
|
|
}
|
|
|
|
extKeyUsageOIDS, err := getExtKeyUsageOids(certificate.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
creationParameters.ExtKeyUsageOIDs = extKeyUsageOIDS
|
|
|
|
policyInformationOids, err := getPolicyIdentifiers(certificate.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
creationParameters.PolicyIdentifiers = policyInformationOids
|
|
|
|
return creationParameters, err
|
|
}
|
|
|
|
func removeNames(name pkix.Name) pkix.Name {
|
|
name.Names = nil
|
|
return name
|
|
}
|
|
|
|
func ParseCsrToCreationParameters(csr x509.CertificateRequest) (CreationParameters, error) {
|
|
otherSANs, err := getOtherSANsMapFromExtensions(csr.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
|
|
creationParameters := CreationParameters{
|
|
Subject: removeNames(csr.Subject),
|
|
DNSNames: csr.DNSNames,
|
|
EmailAddresses: csr.EmailAddresses,
|
|
IPAddresses: csr.IPAddresses,
|
|
URIs: csr.URIs,
|
|
OtherSANs: otherSANs,
|
|
// IsCA: is handled below since the basic constraint it comes from might not be set on the CSR
|
|
KeyType: GetKeyType(csr.PublicKeyAlgorithm.String()),
|
|
KeyBits: FindBitLength(csr.PublicKey),
|
|
// NotAfter: this is not set on a CSR
|
|
// KeyUsage: handled below since this may not be set
|
|
// ExtKeyUsage: We use exclusively ExtKeyUsageOIDs here
|
|
// ExtKeyUsageOIDs: handled below since this may not be set
|
|
// PolicyIdentifiers: handled below since this may not be set
|
|
// BasicConstraintsValidForNonCA is handled below, since it may or may not be set on the CSR
|
|
SignatureBits: FindSignatureBits(csr.SignatureAlgorithm),
|
|
UsePSS: IsPSS(csr.SignatureAlgorithm),
|
|
// The following two values are on creation parameters, but are impossible to parse from the csr
|
|
// ForceAppendCaChain
|
|
// UseCSRValues
|
|
// PermittedDNSDomains : omitted, this generally isn't on a CSR
|
|
// URLs : omitted, this generally isn't on a CSR
|
|
// MaxPathLength is handled below since the basic constraint it comes from may not be set on the CSR
|
|
// NotBeforeDuration : this is not set on a CSR
|
|
// SKID: this is generally not set on a CSR, but calculated from the Key information itself
|
|
}
|
|
|
|
keyUsage, err := getKeyUsage(csr.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
creationParameters.KeyUsage = keyUsage
|
|
|
|
extKeyUsageOIDS, err := getExtKeyUsageOids(csr.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
creationParameters.ExtKeyUsageOIDs = extKeyUsageOIDS
|
|
|
|
policyInformationOids, err := getPolicyIdentifiers(csr.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
creationParameters.PolicyIdentifiers = policyInformationOids
|
|
|
|
found, isCA, maxPathLength, err := getBasicConstraintsFromExtension(csr.Extensions)
|
|
if err != nil {
|
|
return CreationParameters{}, err
|
|
}
|
|
if found {
|
|
creationParameters.IsCA = isCA
|
|
creationParameters.BasicConstraintsValidForNonCA = (isCA && maxPathLength != 0) || (!isCA && (maxPathLength == 0))
|
|
if isCA { // MaxPathLength Only Has a Meaning on a Certificate Authority
|
|
creationParameters.MaxPathLength = maxPathLength
|
|
}
|
|
}
|
|
|
|
return creationParameters, err
|
|
}
|
|
|
|
func ParseCsrToFields(csr x509.CertificateRequest) (map[string]interface{}, error) {
|
|
otherSans, err := getOtherSANsStringFromExtensions(csr.Extensions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
templateData := map[string]interface{}{
|
|
"common_name": csr.Subject.CommonName,
|
|
"alt_names": MakeAltNamesCommaSeparatedString(csr.DNSNames, csr.EmailAddresses),
|
|
"ip_sans": MakeIpAddressCommaSeparatedString(csr.IPAddresses),
|
|
"uri_sans": MakeUriCommaSeparatedString(csr.URIs),
|
|
"other_sans": otherSans,
|
|
"signature_bits": FindSignatureBits(csr.SignatureAlgorithm),
|
|
"exclude_cn_from_sans": DetermineExcludeCnFromCsrSans(csr),
|
|
"ou": makeCommaSeparatedString(csr.Subject.OrganizationalUnit),
|
|
"organization": makeCommaSeparatedString(csr.Subject.Organization),
|
|
"country": makeCommaSeparatedString(csr.Subject.Country),
|
|
"locality": makeCommaSeparatedString(csr.Subject.Locality),
|
|
"province": makeCommaSeparatedString(csr.Subject.Province),
|
|
"street_address": makeCommaSeparatedString(csr.Subject.StreetAddress),
|
|
"postal_code": makeCommaSeparatedString(csr.Subject.PostalCode),
|
|
"serial_number": csr.Subject.SerialNumber,
|
|
// There is no "TTL" on a CSR, that is always set by the signer
|
|
// max_path_length is handled below
|
|
// permitted_dns_domains is a CA thing, it generally does not appear on a CSR
|
|
"use_pss": IsPSS(csr.SignatureAlgorithm),
|
|
// skid could be calculated, but does not directly exist on a csr, so punting for now
|
|
"key_type": GetKeyType(csr.PublicKeyAlgorithm.String()),
|
|
"key_bits": FindBitLength(csr.PublicKey),
|
|
}
|
|
|
|
// isCA is not a field in our data call - that is represented inside vault by using a different endpoint
|
|
found, _, _, err := getBasicConstraintsFromExtension(csr.Extensions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
templateData["add_basic_constraints"] = found
|
|
|
|
return templateData, nil
|
|
}
|
|
|
|
func ParseCertificateToFields(certificate x509.Certificate) (map[string]interface{}, error) {
|
|
otherSans, err := getOtherSANsStringFromExtensions(certificate.Extensions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
templateData := map[string]interface{}{
|
|
"common_name": certificate.Subject.CommonName,
|
|
"alt_names": MakeAltNamesCommaSeparatedString(certificate.DNSNames, certificate.EmailAddresses),
|
|
"ip_sans": MakeIpAddressCommaSeparatedString(certificate.IPAddresses),
|
|
"uri_sans": MakeUriCommaSeparatedString(certificate.URIs),
|
|
"other_sans": otherSans,
|
|
"signature_bits": FindSignatureBits(certificate.SignatureAlgorithm),
|
|
"exclude_cn_from_sans": DetermineExcludeCnFromCertSans(certificate),
|
|
"ou": makeCommaSeparatedString(certificate.Subject.OrganizationalUnit),
|
|
"organization": makeCommaSeparatedString(certificate.Subject.Organization),
|
|
"country": makeCommaSeparatedString(certificate.Subject.Country),
|
|
"locality": makeCommaSeparatedString(certificate.Subject.Locality),
|
|
"province": makeCommaSeparatedString(certificate.Subject.Province),
|
|
"street_address": makeCommaSeparatedString(certificate.Subject.StreetAddress),
|
|
"postal_code": makeCommaSeparatedString(certificate.Subject.PostalCode),
|
|
"serial_number": certificate.Subject.SerialNumber,
|
|
"ttl": (certificate.NotAfter.Sub(certificate.NotBefore)).String(),
|
|
"max_path_length": certificate.MaxPathLen,
|
|
"permitted_dns_domains": strings.Join(certificate.PermittedDNSDomains, ","),
|
|
"use_pss": IsPSS(certificate.SignatureAlgorithm),
|
|
"skid": hex.EncodeToString(certificate.SubjectKeyId),
|
|
"key_type": GetKeyType(certificate.PublicKeyAlgorithm.String()),
|
|
"key_bits": FindBitLength(certificate.PublicKey),
|
|
}
|
|
|
|
return templateData, nil
|
|
}
|
|
|
|
func getBasicConstraintsFromExtension(exts []pkix.Extension) (found bool, isCA bool, maxPathLength int, err error) {
|
|
for _, ext := range exts {
|
|
if ext.Id.Equal(ExtensionBasicConstraintsOID) {
|
|
isCA, maxPathLength, err = ParseBasicConstraintExtension(ext)
|
|
if err != nil {
|
|
return false, false, -1, err
|
|
}
|
|
return true, isCA, maxPathLength, nil
|
|
}
|
|
}
|
|
|
|
return false, false, -1, nil
|
|
}
|
|
|
|
func MakeAltNamesCommaSeparatedString(names []string, emails []string) string {
|
|
return strings.Join(append(names, emails...), ",")
|
|
}
|
|
|
|
func MakeUriCommaSeparatedString(uris []*url.URL) string {
|
|
stringAddresses := make([]string, len(uris))
|
|
for i, uri := range uris {
|
|
stringAddresses[i] = uri.String()
|
|
}
|
|
return strings.Join(stringAddresses, ",")
|
|
}
|
|
|
|
func MakeIpAddressCommaSeparatedString(addresses []net.IP) string {
|
|
stringAddresses := make([]string, len(addresses))
|
|
for i, address := range addresses {
|
|
stringAddresses[i] = address.String()
|
|
}
|
|
return strings.Join(stringAddresses, ",")
|
|
}
|
|
|
|
func makeCommaSeparatedString(values []string) string {
|
|
return strings.Join(values, ",")
|
|
}
|
|
|
|
func DetermineExcludeCnFromCertSans(certificate x509.Certificate) bool {
|
|
cn := certificate.Subject.CommonName
|
|
if cn == "" {
|
|
return false
|
|
}
|
|
|
|
emails := certificate.EmailAddresses
|
|
for _, email := range emails {
|
|
if email == cn {
|
|
return false
|
|
}
|
|
}
|
|
|
|
dnses := certificate.DNSNames
|
|
for _, dns := range dnses {
|
|
if dns == cn {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func DetermineExcludeCnFromCsrSans(csr x509.CertificateRequest) bool {
|
|
cn := csr.Subject.CommonName
|
|
if cn == "" {
|
|
return false
|
|
}
|
|
|
|
emails := csr.EmailAddresses
|
|
for _, email := range emails {
|
|
if email == cn {
|
|
return false
|
|
}
|
|
}
|
|
|
|
dnses := csr.DNSNames
|
|
for _, dns := range dnses {
|
|
if dns == cn {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func FindBitLength(publicKey any) int {
|
|
if publicKey == nil {
|
|
return 0
|
|
}
|
|
switch pub := publicKey.(type) {
|
|
case *rsa.PublicKey:
|
|
return pub.N.BitLen()
|
|
case *ecdsa.PublicKey:
|
|
switch pub.Curve {
|
|
case elliptic.P224():
|
|
return 224
|
|
case elliptic.P256():
|
|
return 256
|
|
case elliptic.P384():
|
|
return 384
|
|
case elliptic.P521():
|
|
return 521
|
|
default:
|
|
return 0
|
|
}
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func FindSignatureBits(algo x509.SignatureAlgorithm) int {
|
|
switch algo {
|
|
case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.DSAWithSHA1, x509.ECDSAWithSHA1:
|
|
return -1
|
|
case x509.SHA256WithRSA, x509.DSAWithSHA256, x509.ECDSAWithSHA256, x509.SHA256WithRSAPSS:
|
|
return 256
|
|
case x509.SHA384WithRSA, x509.ECDSAWithSHA384, x509.SHA384WithRSAPSS:
|
|
return 384
|
|
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS, x509.ECDSAWithSHA512:
|
|
return 512
|
|
case x509.PureEd25519:
|
|
return 0
|
|
default:
|
|
return -1
|
|
}
|
|
}
|
|
|
|
func GetKeyType(goKeyType string) string {
|
|
switch goKeyType {
|
|
case "RSA":
|
|
return "rsa"
|
|
case "ECDSA":
|
|
return "ec"
|
|
case "Ed25519":
|
|
return "ed25519"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func IsPSS(algorithm x509.SignatureAlgorithm) bool {
|
|
switch algorithm {
|
|
case x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS, x509.SHA256WithRSAPSS:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|