// 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 } }