// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package pki import ( "crypto" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "encoding/pem" "fmt" "io" "math/big" "net" "net/url" "regexp" "strconv" "strings" "time" "github.com/hashicorp/vault/builtin/logical/pki/issuing" "github.com/hashicorp/vault/builtin/logical/pki/parsing" "github.com/hashicorp/vault/builtin/logical/pki/pki_backend" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/errutil" "github.com/hashicorp/vault/sdk/logical" "golang.org/x/crypto/cryptobyte" cbbasn1 "golang.org/x/crypto/cryptobyte/asn1" ) type inputBundle struct { role *issuing.RoleEntry req *logical.Request apiData *framework.FieldData } var ( // labelRegex is a single label from a valid domain name and was extracted // from hostnameRegex below for use in leftWildLabelRegex, without any // label separators (`.`). labelRegex = `([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])` // A note on hostnameRegex: although we set the StrictDomainName option // when doing the idna conversion, this appears to only affect output, not // input, so it will allow e.g. host^123.example.com straight through. So // we still need to use this to check the output. hostnameRegex = regexp.MustCompile(`^(\*\.)?(` + labelRegex + `\.)*` + labelRegex + `\.?$`) // Left Wildcard Label Regex is equivalent to a single domain label // component from hostnameRegex above, but with additional wildcard // characters added. There are four possibilities here: // // 1. Entire label is a wildcard, // 2. Wildcard exists at the start, // 3. Wildcard exists at the end, // 4. Wildcard exists in the middle. allWildRegex = `\*` startWildRegex = `\*` + labelRegex endWildRegex = labelRegex + `\*` middleWildRegex = labelRegex + `\*` + labelRegex leftWildLabelRegex = regexp.MustCompile(`^(` + allWildRegex + `|` + startWildRegex + `|` + endWildRegex + `|` + middleWildRegex + `)$`) // Cloned from https://github.com/golang/go/blob/82c713feb05da594567631972082af2fcba0ee4f/src/crypto/x509/x509.go#L327-L379 oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29} signatureAlgorithmDetails = []struct { algo x509.SignatureAlgorithm name string oid asn1.ObjectIdentifier pubKeyAlgo x509.PublicKeyAlgorithm hash crypto.Hash }{ {x509.MD2WithRSA, "MD2-RSA", oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */}, {x509.MD5WithRSA, "MD5-RSA", oidSignatureMD5WithRSA, x509.RSA, crypto.MD5}, {x509.SHA1WithRSA, "SHA1-RSA", oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, {x509.SHA1WithRSA, "SHA1-RSA", oidISOSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, {x509.SHA256WithRSA, "SHA256-RSA", oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256}, {x509.SHA384WithRSA, "SHA384-RSA", oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384}, {x509.SHA512WithRSA, "SHA512-RSA", oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512}, {x509.SHA256WithRSAPSS, "SHA256-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA256}, {x509.SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA384}, {x509.SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA512}, {x509.DSAWithSHA1, "DSA-SHA1", oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1}, {x509.DSAWithSHA256, "DSA-SHA256", oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256}, {x509.ECDSAWithSHA1, "ECDSA-SHA1", oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1}, {x509.ECDSAWithSHA256, "ECDSA-SHA256", oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256}, {x509.ECDSAWithSHA384, "ECDSA-SHA384", oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384}, {x509.ECDSAWithSHA512, "ECDSA-SHA512", oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512}, {x509.PureEd25519, "Ed25519", oidSignatureEd25519, x509.Ed25519, crypto.Hash(0) /* no pre-hashing */}, } ) func doesPublicKeyAlgoMatchSignatureAlgo(pubKey x509.PublicKeyAlgorithm, algo x509.SignatureAlgorithm) bool { for _, detail := range signatureAlgorithmDetails { if detail.algo == algo { return pubKey == detail.pubKeyAlgo } } return false } func getFormat(data *framework.FieldData) string { format := data.Get("format").(string) switch format { case "pem": case "der": case "pem_bundle": default: format = "" } return format } // fetchCAInfo will fetch the CA info, will return an error if no ca info exists, this does NOT support // loading using the legacyBundleShimID and should be used with care. This should be called only once // within the request path otherwise you run the risk of a race condition with the issuer migration on perf-secondaries. func (sc *storageContext) fetchCAInfo(issuerRef string, usage issuing.IssuerUsage) (*certutil.CAInfoBundle, error) { bundle, _, err := sc.fetchCAInfoWithIssuer(issuerRef, usage) return bundle, err } func (sc *storageContext) fetchCAInfoWithIssuer(issuerRef string, usage issuing.IssuerUsage) (*certutil.CAInfoBundle, issuing.IssuerID, error) { var issuerId issuing.IssuerID if sc.UseLegacyBundleCaStorage() { // We have not completed the migration so attempt to load the bundle from the legacy location sc.Logger().Info("Using legacy CA bundle as PKI migration has not completed.") issuerId = legacyBundleShimID } else { var err error issuerId, err = sc.resolveIssuerReference(issuerRef) if err != nil { // Usually a bad label from the user or mis-configured default. return nil, issuing.IssuerRefNotFound, errutil.UserError{Err: err.Error()} } } bundle, err := sc.fetchCAInfoByIssuerId(issuerId, usage) if err != nil { return nil, issuing.IssuerRefNotFound, err } return bundle, issuerId, nil } // fetchCAInfoByIssuerId will fetch the CA info, will return an error if no ca info exists for the given issuerId. // This does support the loading using the legacyBundleShimID func (sc *storageContext) fetchCAInfoByIssuerId(issuerId issuing.IssuerID, usage issuing.IssuerUsage) (*certutil.CAInfoBundle, error) { return issuing.FetchCAInfoByIssuerId(sc.Context, sc.Storage, sc.GetPkiManagedView(), issuerId, usage) } func fetchCertBySerialBigInt(sc *storageContext, prefix string, serial *big.Int) (*logical.StorageEntry, error) { return fetchCertBySerial(sc, prefix, serialFromBigInt(serial)) } // fetchCertBySerial allows fetching certificates from the backend; it handles the slightly // separate pathing for CRL, and revoked certificates. // // Support for fetching CA certificates was removed, due to the new issuers // changes. func fetchCertBySerial(sc pki_backend.StorageContext, prefix, serial string) (*logical.StorageEntry, error) { var path, legacyPath string var err error var certEntry *logical.StorageEntry hyphenSerial := parsing.NormalizeSerialForStorage(serial) colonSerial := strings.ReplaceAll(strings.ToLower(serial), "-", ":") switch { // Revoked goes first as otherwise crl get hardcoded paths which fail if // we actually want revocation info case strings.HasPrefix(prefix, "revoked/"): legacyPath = "revoked/" + colonSerial path = "revoked/" + hyphenSerial case serial == issuing.LegacyCRLPath || serial == issuing.DeltaCRLPath || serial == issuing.UnifiedCRLPath || serial == issuing.UnifiedDeltaCRLPath: warnings, err := sc.CrlBuilder().RebuildIfForced(sc) if err != nil { return nil, err } if len(warnings) > 0 { msg := "During rebuild of CRL for cert fetch, got the following warnings:" for index, warning := range warnings { msg = fmt.Sprintf("%v\n %d. %v", msg, index+1, warning) } sc.Logger().Warn(msg) } unified := serial == issuing.UnifiedCRLPath || serial == issuing.UnifiedDeltaCRLPath path, err = issuing.ResolveIssuerCRLPath(sc.GetContext(), sc.GetStorage(), sc.UseLegacyBundleCaStorage(), issuing.DefaultRef, unified) if err != nil { return nil, err } if serial == issuing.DeltaCRLPath || serial == issuing.UnifiedDeltaCRLPath { if sc.UseLegacyBundleCaStorage() { return nil, fmt.Errorf("refusing to serve delta CRL with legacy CA bundle") } path += issuing.DeltaCRLPathSuffix } default: legacyPath = issuing.PathCerts + colonSerial path = issuing.PathCerts + hyphenSerial } certEntry, err = sc.GetStorage().Get(sc.GetContext(), path) if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching certificate %s: %s", serial, err)} } if certEntry != nil { if certEntry.Value == nil || len(certEntry.Value) == 0 { return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)} } return certEntry, nil } // If legacyPath is unset, it's going to be a CA or CRL; return immediately if legacyPath == "" { return nil, nil } // Retrieve the old-style path. We disregard errors here because they // always manifest on Windows, and thus the initial check for a revoked // cert fails would return an error when the cert isn't revoked, preventing // the happy path from working. certEntry, _ = sc.GetStorage().Get(sc.GetContext(), legacyPath) if certEntry == nil { return nil, nil } if certEntry.Value == nil || len(certEntry.Value) == 0 { return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)} } // Update old-style paths to new-style paths certEntry.Key = path certCounter := sc.GetCertificateCounter() certsCounted := certCounter.IsInitialized() if err = sc.GetStorage().Put(sc.GetContext(), certEntry); err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("error saving certificate with serial %s to new location: %s", serial, err)} } if err = sc.GetStorage().Delete(sc.GetContext(), legacyPath); err != nil { // If we fail here, we have an extra (copy) of a cert in storage, add to metrics: switch { case strings.HasPrefix(prefix, "revoked/"): certCounter.IncrementTotalRevokedCertificatesCount(certsCounted, path) default: certCounter.IncrementTotalCertificatesCount(certsCounted, path) } return nil, errutil.InternalError{Err: fmt.Sprintf("error deleting certificate with serial %s from old location: %s", serial, err)} } return certEntry, nil } // Given a URI SAN, verify that it is allowed. func validateURISAN(b *backend, data *inputBundle, uri string) bool { entityInfo := issuing.NewEntityInfoFromReq(data.req) return issuing.ValidateURISAN(b.System(), data.role, entityInfo, uri) } // Validates a given common name, ensuring it's either an email or a hostname // after validating it according to the role parameters, or disables // validation altogether. func validateCommonName(b *backend, data *inputBundle, name string) string { entityInfo := issuing.NewEntityInfoFromReq(data.req) return issuing.ValidateCommonName(b.System(), data.role, entityInfo, name) } func isWildcardDomain(name string) bool { return issuing.IsWildcardDomain(name) } func validateWildcardDomain(name string) (string, string, error) { return issuing.ValidateWildcardDomain(name) } // Given a set of requested names for a certificate, verifies that all of them // match the various toggles set in the role for controlling issuance. // If one does not pass, it is returned in the string argument. func validateNames(b *backend, data *inputBundle, names []string) string { entityInfo := issuing.NewEntityInfoFromReq(data.req) return issuing.ValidateNames(b.System(), data.role, entityInfo, names) } // validateOtherSANs checks if the values requested are allowed. If an OID // isn't allowed, it will be returned as the first string. If a value isn't // allowed, it will be returned as the second string. Empty strings + error // means everything is okay. func validateOtherSANs(data *inputBundle, requested map[string][]string) (string, string, error) { return issuing.ValidateOtherSANs(data.role, requested) } func parseOtherSANs(others []string) (map[string][]string, error) { return issuing.ParseOtherSANs(others) } // Returns bool stating whether the given UserId is Valid func validateUserId(data *inputBundle, userId string) bool { return issuing.ValidateUserId(data.role, userId) } func validateSerialNumber(data *inputBundle, serialNumber string) string { return issuing.ValidateSerialNumber(data.role, serialNumber) } func generateCert(sc *storageContext, input *inputBundle, caSign *certutil.CAInfoBundle, isCA bool, randomSource io.Reader) (*certutil.ParsedCertBundle, []string, error, ) { ctx := sc.Context if input.role == nil { return nil, nil, errutil.InternalError{Err: "no role found in data bundle"} } if input.role.KeyType == "rsa" && input.role.KeyBits < 2048 { return nil, nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} } data, warnings, err := generateCreationBundle(sc.System(), input, caSign, nil) if err != nil { return nil, nil, err } if data.Params == nil { return nil, nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} } if isCA { data.Params.IsCA = isCA data.Params.PermittedDNSDomains = input.apiData.Get("permitted_dns_domains").([]string) if data.SigningBundle == nil { // Generating a self-signed root certificate. Since we have no // issuer entry yet, we default to the global URLs. entries, err := getGlobalAIAURLs(ctx, sc.Storage) if err != nil { return nil, nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch AIA URL information: %v", err)} } uris, err := ToURLEntries(sc, issuing.IssuerID(""), entries) if err != nil { // When generating root issuers, don't err on missing issuer // ID; there is little value in including AIA info on a root, // as this info would point back to itself; though RFC 5280 is // a touch vague on this point, this seems to be consensus // from public CAs such as DigiCert Global Root G3, ISRG Root // X1, and others. // // This is a UX bug if we do err here, as it requires AIA // templating to not include issuer id (a best practice for // child certs issued from root and intermediate mounts // however), and setting this before root generation (or, on // root renewal) could cause problems. if _, nonEmptyIssuerErr := ToURLEntries(sc, issuing.IssuerID("empty-issuer-id"), entries); nonEmptyIssuerErr != nil { return nil, nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse AIA URL information: %v\nUsing templated AIA URL's {{issuer_id}} field when generating root certificates is not supported.", err)} } uris = &certutil.URLEntries{} msg := "When generating root CA, found global AIA configuration with issuer_id template unsuitable for root generation. This AIA configuration has been ignored. To include AIA on this root CA, set the global AIA configuration to not include issuer_id and instead to refer to a static issuer name." warnings = append(warnings, msg) } data.Params.URLs = uris if input.role.MaxPathLength == nil { data.Params.MaxPathLength = -1 } else { data.Params.MaxPathLength = *input.role.MaxPathLength } } } parsedBundle, err := generateCABundle(sc, input, data, randomSource) if err != nil { return nil, nil, err } return parsedBundle, warnings, nil } // N.B.: This is only meant to be used for generating intermediate CAs. // It skips some sanity checks. func generateIntermediateCSR(sc *storageContext, input *inputBundle, randomSource io.Reader) (*certutil.ParsedCSRBundle, []string, error) { creation, warnings, err := generateCreationBundle(sc.System(), input, nil, nil) if err != nil { return nil, nil, err } if creation.Params == nil { return nil, nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} } _, exists := input.apiData.GetOk("key_usage") if !exists { creation.Params.KeyUsage = 0 } addBasicConstraints := input.apiData != nil && input.apiData.Get("add_basic_constraints").(bool) parsedBundle, err := generateCSRBundle(sc, input, creation, addBasicConstraints, randomSource) if err != nil { return nil, nil, err } return parsedBundle, warnings, nil } func NewSignCertInputFromDataFields(data *framework.FieldData, isCA bool, useCSRValues bool) SignCertInputFromDataFields { certBundle := NewCreationBundleInputFromFieldData(data) return SignCertInputFromDataFields{ CreationBundleInputFromFieldData: certBundle, data: data, isCA: isCA, useCSRValues: useCSRValues, } } type SignCertInputFromDataFields struct { CreationBundleInputFromFieldData data *framework.FieldData isCA bool useCSRValues bool } var _ issuing.SignCertInput = SignCertInputFromDataFields{} func (i SignCertInputFromDataFields) GetCSR() (*x509.CertificateRequest, error) { csrString := i.data.Get("csr").(string) if csrString == "" { return nil, errutil.UserError{Err: "\"csr\" is empty"} } pemBlock, _ := pem.Decode([]byte(csrString)) if pemBlock == nil { return nil, errutil.UserError{Err: "csr contains no data"} } csr, err := x509.ParseCertificateRequest(pemBlock.Bytes) if err != nil { return nil, errutil.UserError{Err: fmt.Sprintf("certificate request could not be parsed: %v", err)} } return csr, nil } func (i SignCertInputFromDataFields) IsCA() bool { return i.isCA } func (i SignCertInputFromDataFields) UseCSRValues() bool { return i.useCSRValues } func (i SignCertInputFromDataFields) GetPermittedDomains() []string { return i.data.Get("permitted_dns_domains").([]string) } func (i SignCertInputFromDataFields) IgnoreCSRSignature() bool { return false } func signCert(sysView logical.SystemView, data *inputBundle, caSign *certutil.CAInfoBundle, isCA bool, useCSRValues bool) (*certutil.ParsedCertBundle, []string, error) { if data.role == nil { return nil, nil, errutil.InternalError{Err: "no role found in data bundle"} } entityInfo := issuing.NewEntityInfoFromReq(data.req) signCertInput := NewSignCertInputFromDataFields(data.apiData, isCA, useCSRValues) return issuing.SignCert(sysView, data.role, entityInfo, caSign, signCertInput) } func getOtherSANsFromX509Extensions(exts []pkix.Extension) ([]certutil.OtherNameUtf8, error) { return certutil.GetOtherSANsFromX509Extensions(exts) } var _ issuing.CreationBundleInput = CreationBundleInputFromFieldData{} func NewCreationBundleInputFromFieldData(data *framework.FieldData) CreationBundleInputFromFieldData { certNotAfter := NewCertNotAfterInputFromFieldData(data) return CreationBundleInputFromFieldData{ CertNotAfterInputFromFieldData: certNotAfter, data: data, } } type CreationBundleInputFromFieldData struct { CertNotAfterInputFromFieldData data *framework.FieldData } func (cb CreationBundleInputFromFieldData) IgnoreCSRSignature() bool { return false } func (cb CreationBundleInputFromFieldData) GetCommonName() string { return cb.data.Get("common_name").(string) } func (cb CreationBundleInputFromFieldData) GetSerialNumber() string { return cb.data.Get("serial_number").(string) } func (cb CreationBundleInputFromFieldData) GetExcludeCnFromSans() bool { return cb.data.Get("exclude_cn_from_sans").(bool) } func (cb CreationBundleInputFromFieldData) GetOptionalAltNames() (interface{}, bool) { return cb.data.GetOk("alt_names") } func (cb CreationBundleInputFromFieldData) GetOtherSans() []string { return cb.data.Get("other_sans").([]string) } func (cb CreationBundleInputFromFieldData) GetIpSans() []string { return cb.data.Get("ip_sans").([]string) } func (cb CreationBundleInputFromFieldData) GetURISans() []string { return cb.data.Get("uri_sans").([]string) } func (cb CreationBundleInputFromFieldData) GetOptionalSkid() (interface{}, bool) { return cb.data.GetOk("skid") } func (cb CreationBundleInputFromFieldData) IsUserIdInSchema() (interface{}, bool) { val, present := cb.data.Schema["user_ids"] return val, present } func (cb CreationBundleInputFromFieldData) GetUserIds() []string { return cb.data.Get("user_ids").([]string) } // generateCreationBundle is a shared function that reads parameters supplied // from the various endpoints and generates a CreationParameters with the // parameters that can be used to issue or sign func generateCreationBundle(sysView logical.SystemView, data *inputBundle, caSign *certutil.CAInfoBundle, csr *x509.CertificateRequest) (*certutil.CreationBundle, []string, error) { entityInfo := issuing.NewEntityInfoFromReq(data.req) creationBundleInput := NewCreationBundleInputFromFieldData(data.apiData) return issuing.GenerateCreationBundle(sysView, data.role, entityInfo, creationBundleInput, caSign, csr) } // getCertificateNotAfter compute a certificate's NotAfter date based on the mount ttl, role, signing bundle and input // api data being sent. Returns a NotAfter time, a set of warnings or an error. func getCertificateNotAfter(sysView logical.SystemView, data *inputBundle, caSign *certutil.CAInfoBundle) (time.Time, []string, error) { input := NewCertNotAfterInputFromFieldData(data.apiData) return issuing.GetCertificateNotAfter(sysView, data.role, input, caSign) } // applyIssuerLeafNotAfterBehavior resets a certificate's notAfter time or errors out based on the // issuer's notAfter date along with the LeafNotAfterBehavior configuration func applyIssuerLeafNotAfterBehavior(caSign *certutil.CAInfoBundle, notAfter time.Time) (time.Time, error) { return issuing.ApplyIssuerLeafNotAfterBehavior(caSign, notAfter) } func convertRespToPKCS8(resp *logical.Response) error { privRaw, ok := resp.Data["private_key"] if !ok { return nil } priv, ok := privRaw.(string) if !ok { return fmt.Errorf("error converting response to pkcs8: could not parse original value as string") } privKeyTypeRaw, ok := resp.Data["private_key_type"] if !ok { return fmt.Errorf("error converting response to pkcs8: %q not found in response", "private_key_type") } privKeyType, ok := privKeyTypeRaw.(certutil.PrivateKeyType) if !ok { return fmt.Errorf("error converting response to pkcs8: could not parse original type value as string") } var keyData []byte var pemUsed bool var err error var signer crypto.Signer block, _ := pem.Decode([]byte(priv)) if block == nil { keyData, err = base64.StdEncoding.DecodeString(priv) if err != nil { return fmt.Errorf("error converting response to pkcs8: error decoding original value: %w", err) } } else { keyData = block.Bytes pemUsed = true } switch privKeyType { case certutil.RSAPrivateKey: signer, err = x509.ParsePKCS1PrivateKey(keyData) case certutil.ECPrivateKey: signer, err = x509.ParseECPrivateKey(keyData) case certutil.Ed25519PrivateKey: k, err := x509.ParsePKCS8PrivateKey(keyData) if err != nil { return fmt.Errorf("error converting response to pkcs8: error parsing previous key: %w", err) } signer = k.(crypto.Signer) default: return fmt.Errorf("unknown private key type %q", privKeyType) } if err != nil { return fmt.Errorf("error converting response to pkcs8: error parsing previous key: %w", err) } keyData, err = x509.MarshalPKCS8PrivateKey(signer) if err != nil { return fmt.Errorf("error converting response to pkcs8: error marshaling pkcs8 key: %w", err) } if pemUsed { block.Type = "PRIVATE KEY" block.Bytes = keyData resp.Data["private_key"] = strings.TrimSpace(string(pem.EncodeToMemory(block))) } else { resp.Data["private_key"] = base64.StdEncoding.EncodeToString(keyData) } return nil } 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 { in.ExtraExtensions = append(in.ExtraExtensions, certTemplate.ExtraExtensions...) } 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(cbbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { b.AddASN1(cbbasn1.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: certutil.OidExtensionSubjectAltName, } 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 ( nameTypeOther = 0 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 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 ret, nil } func parseCertificateFromBytes(certBytes []byte) (*x509.Certificate, error) { return parsing.ParseCertificateFromBytes(certBytes) } func NewCertNotAfterInputFromFieldData(data *framework.FieldData) CertNotAfterInputFromFieldData { return CertNotAfterInputFromFieldData{data: data} } var _ issuing.CertNotAfterInput = CertNotAfterInputFromFieldData{} type CertNotAfterInputFromFieldData struct { data *framework.FieldData } func (i CertNotAfterInputFromFieldData) GetTTL() int { return i.data.Get("ttl").(int) } func (i CertNotAfterInputFromFieldData) GetOptionalNotAfter() (interface{}, bool) { return i.data.GetOk("not_after") }