diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 38bbe2baa5..7a05ce434f 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -432,7 +432,7 @@ func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUs } func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep { - expected := urlEntries{ + expected := certutil.URLEntries{ IssuingCertificates: []string{ "http://example.com/ca1", "http://example.com/ca2", @@ -499,7 +499,7 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s if resp.Data == nil { return fmt.Errorf("no data returned") } - var entries urlEntries + var entries certutil.URLEntries err := mapstructure.Decode(resp.Data, &entries) if err != nil { return err diff --git a/builtin/logical/pki/ca_util.go b/builtin/logical/pki/ca_util.go index 80b2676f00..8692486d87 100644 --- a/builtin/logical/pki/ca_util.go +++ b/builtin/logical/pki/ca_util.go @@ -4,6 +4,7 @@ import ( "time" "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/logical" ) @@ -53,7 +54,9 @@ func (b *backend) getGenerationParams( return } - errorResp = validateKeyTypeLength(role.KeyType, role.KeyBits) + if err := certutil.ValidateKeyTypeLength(role.KeyType, role.KeyBits); err != nil { + errorResp = logical.ErrorResponse(err.Error()) + } return } diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index 90c78d388d..b82c28539f 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -1,13 +1,10 @@ package pki import ( - "bytes" "context" "crypto" "crypto/ecdsa" - "crypto/rand" "crypto/rsa" - "crypto/sha1" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -17,7 +14,6 @@ import ( "net" "net/url" "regexp" - "strconv" "strings" "time" @@ -27,95 +23,14 @@ import ( "github.com/hashicorp/vault/sdk/helper/errutil" "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/hashicorp/vault/sdk/logical" - glob "github.com/ryanuber/go-glob" - "golang.org/x/crypto/cryptobyte" - cbbasn1 "golang.org/x/crypto/cryptobyte/asn1" + "github.com/ryanuber/go-glob" "golang.org/x/net/idna" ) -type certExtKeyUsage int - -const ( - anyExtKeyUsage certExtKeyUsage = 1 << iota - serverAuthExtKeyUsage - clientAuthExtKeyUsage - codeSigningExtKeyUsage - emailProtectionExtKeyUsage - ipsecEndSystemExtKeyUsage - ipsecTunnelExtKeyUsage - ipsecUserExtKeyUsage - timeStampingExtKeyUsage - ocspSigningExtKeyUsage - microsoftServerGatedCryptoExtKeyUsage - netscapeServerGatedCryptoExtKeyUsage - microsoftCommercialCodeSigningExtKeyUsage - microsoftKernelCodeSigningExtKeyUsage -) - -type dataBundle struct { - params *creationParameters - signingBundle *caInfoBundle - csr *x509.CertificateRequest - role *roleEntry - req *logical.Request - apiData *framework.FieldData -} - -type creationParameters struct { - Subject pkix.Name - DNSNames []string - EmailAddresses []string - IPAddresses []net.IP - URIs []*url.URL - OtherSANs map[string][]string - IsCA bool - KeyType string - KeyBits int - NotAfter time.Time - KeyUsage x509.KeyUsage - ExtKeyUsage certExtKeyUsage - ExtKeyUsageOIDs []string - PolicyIdentifiers []string - BasicConstraintsValidForNonCA bool - - // Only used when signing a CA cert - UseCSRValues bool - PermittedDNSDomains []string - - // URLs to encode into the certificate - URLs *urlEntries - - // The maximum path length to encode - MaxPathLength int - - // The duration the certificate will use NotBefore - NotBeforeDuration time.Duration -} - -type caInfoBundle struct { - certutil.ParsedCertBundle - URLs *urlEntries -} - -func (b *caInfoBundle) GetCAChain() []*certutil.CertBlock { - chain := []*certutil.CertBlock{} - - // Include issuing CA in Chain, not including Root Authority - if (len(b.Certificate.AuthorityKeyId) > 0 && - !bytes.Equal(b.Certificate.AuthorityKeyId, b.Certificate.SubjectKeyId)) || - (len(b.Certificate.AuthorityKeyId) == 0 && - !bytes.Equal(b.Certificate.RawIssuer, b.Certificate.RawSubject)) { - - chain = append(chain, &certutil.CertBlock{ - Certificate: b.Certificate, - Bytes: b.CertificateBytes, - }) - if b.CAChain != nil && len(b.CAChain) > 0 { - chain = append(chain, b.CAChain...) - } - } - - return chain +type inputBundle struct { + role *roleEntry + req *logical.Request + apiData *framework.FieldData } var ( @@ -123,8 +38,7 @@ var ( // 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(`^(\*\.)?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`) - oidExtensionBasicConstraints = []int{2, 5, 29, 19} + hostnameRegex = regexp.MustCompile(`^(\*\.)?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`) ) func oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) bool { @@ -148,39 +62,9 @@ func getFormat(data *framework.FieldData) string { return format } -func validateKeyTypeLength(keyType string, keyBits int) *logical.Response { - switch keyType { - case "rsa": - switch keyBits { - case 2048: - case 4096: - case 8192: - default: - return logical.ErrorResponse(fmt.Sprintf( - "unsupported bit length for RSA key: %d", keyBits)) - } - case "ec": - switch keyBits { - case 224: - case 256: - case 384: - case 521: - default: - return logical.ErrorResponse(fmt.Sprintf( - "unsupported bit length for EC key: %d", keyBits)) - } - case "any": - default: - return logical.ErrorResponse(fmt.Sprintf( - "unknown key type %s", keyType)) - } - - return nil -} - // Fetches the CA info. Unlike other certificates, the CA info is stored // in the backend as a CertBundle, because we are storing its private key -func fetchCAInfo(ctx context.Context, req *logical.Request) (*caInfoBundle, error) { +func fetchCAInfo(ctx context.Context, req *logical.Request) (*certutil.CAInfoBundle, error) { bundleEntry, err := req.Storage.Get(ctx, "config/ca_bundle") if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch local CA certificate/key: %v", err)} @@ -203,14 +87,14 @@ func fetchCAInfo(ctx context.Context, req *logical.Request) (*caInfoBundle, erro return nil, errutil.InternalError{Err: "stored CA information not able to be parsed"} } - caInfo := &caInfoBundle{*parsedBundle, nil} + caInfo := &certutil.CAInfoBundle{*parsedBundle, nil} entries, err := getURLs(ctx, req) if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} } if entries == nil { - entries = &urlEntries{ + entries = &certutil.URLEntries{ IssuingCertificates: []string{}, CRLDistributionPoints: []string{}, OCSPServers: []string{}, @@ -289,7 +173,7 @@ func fetchCertBySerial(ctx context.Context, req *logical.Request, prefix, serial // 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(data *dataBundle, names []string) string { +func validateNames(data *inputBundle, names []string) string { for _, name := range names { sanitizedName := name emailDomain := name @@ -464,7 +348,7 @@ func validateNames(data *dataBundle, names []string) string { // 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 *dataBundle, requested map[string][]string) (string, string, error) { +func validateOtherSANs(data *inputBundle, requested map[string][]string) (string, string, error) { for _, val := range data.role.AllowedOtherSANs { if val == "*" { // Anything is allowed @@ -522,7 +406,7 @@ func parseOtherSANs(others []string) (map[string][]string, error) { return result, nil } -func validateSerialNumber(data *dataBundle, serialNumber string) string { +func validateSerialNumber(data *inputBundle, serialNumber string) string { valid := false if len(data.role.AllowedSerialNumbers) > 0 { for _, currSerialNumber := range data.role.AllowedSerialNumbers { @@ -547,53 +431,54 @@ func validateSerialNumber(data *dataBundle, serialNumber string) string { func generateCert(ctx context.Context, b *backend, - data *dataBundle, + input *inputBundle, + caSign *certutil.CAInfoBundle, isCA bool) (*certutil.ParsedCertBundle, error) { - if data.role == nil { + if input.role == nil { return nil, errutil.InternalError{Err: "no role found in data bundle"} } - if data.role.KeyType == "rsa" && data.role.KeyBits < 2048 { + if input.role.KeyType == "rsa" && input.role.KeyBits < 2048 { return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} } - err := generateCreationBundle(b, data) + data, err := generateCreationBundle(b, input, caSign, nil) if err != nil { return nil, err } - if data.params == nil { + if data.Params == nil { return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} } if isCA { - data.params.IsCA = isCA - data.params.PermittedDNSDomains = data.apiData.Get("permitted_dns_domains").([]string) + data.Params.IsCA = isCA + data.Params.PermittedDNSDomains = input.apiData.Get("permitted_dns_domains").([]string) - if data.signingBundle == nil { + if data.SigningBundle == nil { // Generating a self-signed root certificate - entries, err := getURLs(ctx, data.req) + entries, err := getURLs(ctx, input.req) if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} } if entries == nil { - entries = &urlEntries{ + entries = &certutil.URLEntries{ IssuingCertificates: []string{}, CRLDistributionPoints: []string{}, OCSPServers: []string{}, } } - data.params.URLs = entries + data.Params.URLs = entries - if data.role.MaxPathLength == nil { - data.params.MaxPathLength = -1 + if input.role.MaxPathLength == nil { + data.Params.MaxPathLength = -1 } else { - data.params.MaxPathLength = *data.role.MaxPathLength + data.Params.MaxPathLength = *input.role.MaxPathLength } } } - parsedBundle, err := createCertificate(data) + parsedBundle, err := certutil.CreateCertificate(data) if err != nil { return nil, err } @@ -603,16 +488,17 @@ func generateCert(ctx context.Context, // N.B.: This is only meant to be used for generating intermediate CAs. // It skips some sanity checks. -func generateIntermediateCSR(b *backend, data *dataBundle) (*certutil.ParsedCSRBundle, error) { - err := generateCreationBundle(b, data) +func generateIntermediateCSR(b *backend, input *inputBundle) (*certutil.ParsedCSRBundle, error) { + creation, err := generateCreationBundle(b, input, nil, nil) if err != nil { return nil, err } - if data.params == nil { + if creation.Params == nil { return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} } - parsedBundle, err := createCSR(data) + addBasicConstraints := input.apiData != nil && input.apiData.Get("add_basic_constraints").(bool) + parsedBundle, err := certutil.CreateCSR(creation, addBasicConstraints) if err != nil { return nil, err } @@ -621,7 +507,8 @@ func generateIntermediateCSR(b *backend, data *dataBundle) (*certutil.ParsedCSRB } func signCert(b *backend, - data *dataBundle, + data *inputBundle, + caSign *certutil.CAInfoBundle, isCA bool, useCSRValues bool) (*certutil.ParsedCertBundle, error) { @@ -708,24 +595,22 @@ func signCert(b *backend, } - data.csr = csr - - err = generateCreationBundle(b, data) + creation, err := generateCreationBundle(b, data, caSign, csr) if err != nil { return nil, err } - if data.params == nil { + if creation.Params == nil { return nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} } - data.params.IsCA = isCA - data.params.UseCSRValues = useCSRValues + creation.Params.IsCA = isCA + creation.Params.UseCSRValues = useCSRValues if isCA { - data.params.PermittedDNSDomains = data.apiData.Get("permitted_dns_domains").([]string) + creation.Params.PermittedDNSDomains = data.apiData.Get("permitted_dns_domains").([]string) } - parsedBundle, err := signCertificate(data) + parsedBundle, err := certutil.SignCertificate(creation) if err != nil { return nil, err } @@ -734,35 +619,35 @@ func signCert(b *backend, } // generateCreationBundle is a shared function that reads parameters supplied -// from the various endpoints and generates a creationParameters with the +// from the various endpoints and generates a CreationParameters with the // parameters that can be used to issue or sign -func generateCreationBundle(b *backend, data *dataBundle) error { +func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAInfoBundle, csr *x509.CertificateRequest) (*certutil.CreationBundle, error) { // Read in names -- CN, DNS and email addresses var cn string var ridSerialNumber string dnsNames := []string{} emailAddresses := []string{} { - if data.csr != nil && data.role.UseCSRCommonName { - cn = data.csr.Subject.CommonName + if csr != nil && data.role.UseCSRCommonName { + cn = csr.Subject.CommonName } if cn == "" { cn = data.apiData.Get("common_name").(string) if cn == "" && data.role.RequireCN { - return errutil.UserError{Err: `the common_name field is required, or must be provided in a CSR with "use_csr_common_name" set to true, unless "require_cn" is set to false`} + return nil, errutil.UserError{Err: `the common_name field is required, or must be provided in a CSR with "use_csr_common_name" set to true, unless "require_cn" is set to false`} } } ridSerialNumber = data.apiData.Get("serial_number").(string) // only take serial number from CSR if one was not supplied via API - if ridSerialNumber == "" && data.csr != nil { - ridSerialNumber = data.csr.Subject.SerialNumber + if ridSerialNumber == "" && csr != nil { + ridSerialNumber = csr.Subject.SerialNumber } - if data.csr != nil && data.role.UseCSRSANs { - dnsNames = data.csr.DNSNames - emailAddresses = data.csr.EmailAddresses + if csr != nil && data.role.UseCSRSANs { + dnsNames = csr.DNSNames + emailAddresses = csr.EmailAddresses } if cn != "" && !data.apiData.Get("exclude_cn_from_sans").(bool) { @@ -782,7 +667,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error { ) converted, err := p.ToASCII(cn) if err != nil { - return errutil.UserError{Err: err.Error()} + return nil, errutil.UserError{Err: err.Error()} } if hostnameRegex.MatchString(converted) { dnsNames = append(dnsNames, converted) @@ -790,7 +675,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error { } } - if data.csr == nil || !data.role.UseCSRSANs { + if csr == nil || !data.role.UseCSRSANs { cnAltRaw, ok := data.apiData.GetOk("alt_names") if ok { cnAlt := strutil.ParseDedupLowercaseAndSortStrings(cnAltRaw.(string), ",") @@ -806,7 +691,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error { ) converted, err := p.ToASCII(v) if err != nil { - return errutil.UserError{Err: err.Error()} + return nil, errutil.UserError{Err: err.Error()} } if hostnameRegex.MatchString(converted) { dnsNames = append(dnsNames, converted) @@ -821,7 +706,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error { if cn != "" { badName := validateNames(data, []string{cn}) if len(badName) != 0 { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "common name %s not allowed by this role", badName)} } } @@ -829,7 +714,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error { if ridSerialNumber != "" { badName := validateSerialNumber(data, ridSerialNumber) if len(badName) != 0 { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "serial_number %s not allowed by this role", badName)} } } @@ -837,13 +722,13 @@ func generateCreationBundle(b *backend, data *dataBundle) error { // Check for bad email and/or DNS names badName := validateNames(data, dnsNames) if len(badName) != 0 { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "subject alternate name %s not allowed by this role", badName)} } badName = validateNames(data, emailAddresses) if len(badName) != 0 { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "email address %s not allowed by this role", badName)} } } @@ -852,17 +737,17 @@ func generateCreationBundle(b *backend, data *dataBundle) error { if sans := data.apiData.Get("other_sans").([]string); len(sans) > 0 { requested, err := parseOtherSANs(sans) if err != nil { - return errutil.UserError{Err: errwrap.Wrapf("could not parse requested other SAN: {{err}}", err).Error()} + return nil, errutil.UserError{Err: errwrap.Wrapf("could not parse requested other SAN: {{err}}", err).Error()} } badOID, badName, err := validateOtherSANs(data, requested) switch { case err != nil: - return errutil.UserError{Err: err.Error()} + return nil, errutil.UserError{Err: err.Error()} case len(badName) > 0: - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "other SAN %s not allowed for OID %s by this role", badName, badOID)} case len(badOID) > 0: - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "other SAN OID %s not allowed by this role", badOID)} default: otherSANs = requested @@ -872,25 +757,25 @@ func generateCreationBundle(b *backend, data *dataBundle) error { // Get and verify any IP SANs ipAddresses := []net.IP{} { - if data.csr != nil && data.role.UseCSRSANs { - if len(data.csr.IPAddresses) > 0 { + if csr != nil && data.role.UseCSRSANs { + if len(csr.IPAddresses) > 0 { if !data.role.AllowIPSANs { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "IP Subject Alternative Names are not allowed in this role, but was provided some via CSR")} } - ipAddresses = data.csr.IPAddresses + ipAddresses = csr.IPAddresses } } else { ipAlt := data.apiData.Get("ip_sans").([]string) if len(ipAlt) > 0 { if !data.role.AllowIPSANs { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)} } for _, v := range ipAlt { parsedIP := net.ParseIP(v) if parsedIP == nil { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "the value '%s' is not a valid IP address", v)} } ipAddresses = append(ipAddresses, parsedIP) @@ -901,16 +786,16 @@ func generateCreationBundle(b *backend, data *dataBundle) error { URIs := []*url.URL{} { - if data.csr != nil && data.role.UseCSRSANs { - if len(data.csr.URIs) > 0 { + if csr != nil && data.role.UseCSRSANs { + if len(csr.URIs) > 0 { if len(data.role.AllowedURISANs) == 0 { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "URI Subject Alternative Names are not allowed in this role, but were provided via CSR"), } } // validate uri sans - for _, uri := range data.csr.URIs { + for _, uri := range csr.URIs { valid := false for _, allowed := range data.role.AllowedURISANs { validURI := glob.Glob(allowed, uri.String()) @@ -921,7 +806,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error { } if !valid { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "URI Subject Alternative Names were provided via CSR which are not valid for this role"), } } @@ -933,7 +818,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error { uriAlt := data.apiData.Get("uri_sans").([]string) if len(uriAlt) > 0 { if len(data.role.AllowedURISANs) == 0 { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "URI Subject Alternative Names are not allowed in this role, but were provided via the API"), } } @@ -949,14 +834,14 @@ func generateCreationBundle(b *backend, data *dataBundle) error { } if !valid { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "URI Subject Alternative Names were provided via CSR which are not valid for this role"), } } parsedURI, err := url.Parse(uri) if parsedURI == nil || err != nil { - return errutil.UserError{Err: fmt.Sprintf( + return nil, errutil.UserError{Err: fmt.Sprintf( "the provided URI Subject Alternative Name '%s' is not a valid URI", uri), } } @@ -1010,471 +895,66 @@ func generateCreationBundle(b *backend, data *dataBundle) error { // If it's not self-signed, verify that the issued certificate won't be // valid past the lifetime of the CA certificate - if data.signingBundle != nil && - notAfter.After(data.signingBundle.Certificate.NotAfter) && !data.role.AllowExpirationPastCA { + if caSign != nil && + notAfter.After(caSign.Certificate.NotAfter) && !data.role.AllowExpirationPastCA { - return errutil.UserError{Err: fmt.Sprintf( - "cannot satisfy request, as TTL would result in notAfter %s that is beyond the expiration of the CA certificate at %s", notAfter.Format(time.RFC3339Nano), data.signingBundle.Certificate.NotAfter.Format(time.RFC3339Nano))} + return nil, errutil.UserError{Err: fmt.Sprintf( + "cannot satisfy request, as TTL would result in notAfter %s that is beyond the expiration of the CA certificate at %s", notAfter.Format(time.RFC3339Nano), caSign.Certificate.NotAfter.Format(time.RFC3339Nano))} } } - data.params = &creationParameters{ - Subject: subject, - DNSNames: dnsNames, - EmailAddresses: emailAddresses, - IPAddresses: ipAddresses, - URIs: URIs, - OtherSANs: otherSANs, - KeyType: data.role.KeyType, - KeyBits: data.role.KeyBits, - NotAfter: notAfter, - KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)), - ExtKeyUsage: parseExtKeyUsages(data.role), - ExtKeyUsageOIDs: data.role.ExtKeyUsageOIDs, - PolicyIdentifiers: data.role.PolicyIdentifiers, - BasicConstraintsValidForNonCA: data.role.BasicConstraintsValidForNonCA, - NotBeforeDuration: data.role.NotBeforeDuration, + creation := &certutil.CreationBundle{ + Params: &certutil.CreationParameters{ + Subject: subject, + DNSNames: dnsNames, + EmailAddresses: emailAddresses, + IPAddresses: ipAddresses, + URIs: URIs, + OtherSANs: otherSANs, + KeyType: data.role.KeyType, + KeyBits: data.role.KeyBits, + NotAfter: notAfter, + KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)), + ExtKeyUsage: parseExtKeyUsages(data.role), + ExtKeyUsageOIDs: data.role.ExtKeyUsageOIDs, + PolicyIdentifiers: data.role.PolicyIdentifiers, + BasicConstraintsValidForNonCA: data.role.BasicConstraintsValidForNonCA, + NotBeforeDuration: data.role.NotBeforeDuration, + }, + SigningBundle: caSign, + CSR: csr, } // Don't deal with URLs or max path length if it's self-signed, as these // normally come from the signing bundle - if data.signingBundle == nil { - return nil + if caSign == nil { + return creation, nil } // This will have been read in from the getURLs function - data.params.URLs = data.signingBundle.URLs + creation.Params.URLs = caSign.URLs // If the max path length in the role is not nil, it was specified at // generation time with the max_path_length parameter; otherwise derive it // from the signing certificate if data.role.MaxPathLength != nil { - data.params.MaxPathLength = *data.role.MaxPathLength + creation.Params.MaxPathLength = *data.role.MaxPathLength } else { switch { - case data.signingBundle.Certificate.MaxPathLen < 0: - data.params.MaxPathLength = -1 - case data.signingBundle.Certificate.MaxPathLen == 0 && - data.signingBundle.Certificate.MaxPathLenZero: + case caSign.Certificate.MaxPathLen < 0: + creation.Params.MaxPathLength = -1 + case caSign.Certificate.MaxPathLen == 0 && + caSign.Certificate.MaxPathLenZero: // The signing function will ensure that we do not issue a CA cert - data.params.MaxPathLength = 0 + creation.Params.MaxPathLength = 0 default: // If this takes it to zero, we handle this case later if // necessary - data.params.MaxPathLength = data.signingBundle.Certificate.MaxPathLen - 1 + creation.Params.MaxPathLength = caSign.Certificate.MaxPathLen - 1 } } - return nil -} - -// addKeyUsages adds appropriate key usages to the template given the creation -// information -func addKeyUsages(data *dataBundle, certTemplate *x509.Certificate) { - if data.params.IsCA { - certTemplate.KeyUsage = x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign) - return - } - - certTemplate.KeyUsage = data.params.KeyUsage - - if data.params.ExtKeyUsage&anyExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageAny) - } - - if data.params.ExtKeyUsage&serverAuthExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) - } - - if data.params.ExtKeyUsage&clientAuthExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) - } - - if data.params.ExtKeyUsage&codeSigningExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) - } - - if data.params.ExtKeyUsage&emailProtectionExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) - } - - if data.params.ExtKeyUsage&ipsecEndSystemExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageIPSECEndSystem) - } - - if data.params.ExtKeyUsage&ipsecTunnelExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageIPSECTunnel) - } - - if data.params.ExtKeyUsage&ipsecUserExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageIPSECUser) - } - - if data.params.ExtKeyUsage&timeStampingExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageTimeStamping) - } - - if data.params.ExtKeyUsage&ocspSigningExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning) - } - - if data.params.ExtKeyUsageµsoftServerGatedCryptoExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageMicrosoftServerGatedCrypto) - } - - if data.params.ExtKeyUsage&netscapeServerGatedCryptoExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageNetscapeServerGatedCrypto) - } - - if data.params.ExtKeyUsageµsoftCommercialCodeSigningExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageMicrosoftCommercialCodeSigning) - } - - if data.params.ExtKeyUsageµsoftKernelCodeSigningExtKeyUsage != 0 { - certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageMicrosoftKernelCodeSigning) - } -} - -// addPolicyIdentifiers adds certificate policies extension -// -func addPolicyIdentifiers(data *dataBundle, certTemplate *x509.Certificate) { - for _, oidstr := range data.params.PolicyIdentifiers { - oid, err := stringToOid(oidstr) - if err == nil { - certTemplate.PolicyIdentifiers = append(certTemplate.PolicyIdentifiers, oid) - } - } -} - -// addExtKeyUsageOids adds custom extended key usage OIDs to certificate -func addExtKeyUsageOids(data *dataBundle, certTemplate *x509.Certificate) { - for _, oidstr := range data.params.ExtKeyUsageOIDs { - oid, err := stringToOid(oidstr) - if err == nil { - certTemplate.UnknownExtKeyUsage = append(certTemplate.UnknownExtKeyUsage, oid) - } - } -} - -// Performs the heavy lifting of creating a certificate. Returns -// a fully-filled-in ParsedCertBundle. -func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) { - var err error - result := &certutil.ParsedCertBundle{} - - serialNumber, err := certutil.GenerateSerialNumber() - if err != nil { - return nil, err - } - - if err := certutil.GeneratePrivateKey(data.params.KeyType, - data.params.KeyBits, - result); err != nil { - return nil, err - } - - subjKeyID, err := certutil.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 { - switch data.signingBundle.PrivateKeyType { - case certutil.RSAPrivateKey: - certTemplate.SignatureAlgorithm = x509.SHA256WithRSA - case certutil.ECPrivateKey: - certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 - } - - caCert := data.signingBundle.Certificate - certTemplate.AuthorityKeyId = caCert.SubjectKeyId - - certBytes, err = x509.CreateCertificate(rand.Reader, 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": - certTemplate.SignatureAlgorithm = x509.SHA256WithRSA - case "ec": - certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 - } - - certTemplate.AuthorityKeyId = subjKeyID - certTemplate.BasicConstraintsValid = true - certBytes, err = x509.CreateCertificate(rand.Reader, 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) { - - result.CAChain = []*certutil.CertBlock{ - &certutil.CertBlock{ - Certificate: data.signingBundle.Certificate, - Bytes: data.signingBundle.CertificateBytes, - }, - } - result.CAChain = append(result.CAChain, data.signingBundle.CAChain...) - } - } - - return result, nil -} - -// Creates a CSR. This is currently only meant for use when -// generating an intermediate certificate. -func createCSR(data *dataBundle) (*certutil.ParsedCSRBundle, error) { - var err error - result := &certutil.ParsedCSRBundle{} - - if err := certutil.GeneratePrivateKey(data.params.KeyType, - data.params.KeyBits, - result); 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 err := handleOtherCSRSANs(csrTemplate, data.params.OtherSANs); err != nil { - return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()} - } - - if data.apiData != nil && data.apiData.Get("add_basic_constraints").(bool) { - 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: oidExtensionBasicConstraints, - Value: val, - Critical: true, - } - csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, ext) - } - - switch data.params.KeyType { - case "rsa": - csrTemplate.SignatureAlgorithm = x509.SHA256WithRSA - case "ec": - csrTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 - } - - csr, err := x509.CreateCertificateRequest(rand.Reader, 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)} - } - - return result, nil -} - -// Performs the heavy lifting of generating a certificate from a CSR. -// Returns a ParsedCertBundle sans private keys. -func signCertificate(data *dataBundle) (*certutil.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"} - } - - err := data.csr.CheckSignature() - if err != nil { - return nil, errutil.UserError{Err: "request signature invalid"} - } - - result := &certutil.ParsedCertBundle{} - - serialNumber, err := certutil.GenerateSerialNumber() - if err != nil { - return nil, err - } - - marshaledKey, err := x509.MarshalPKIXPublicKey(data.csr.PublicKey) - if err != nil { - return nil, errutil.InternalError{Err: fmt.Sprintf("error marshalling public key: %s", err)} - } - subjKeyID := sha1.Sum(marshaledKey) - - 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) - } - - switch data.signingBundle.PrivateKeyType { - case certutil.RSAPrivateKey: - certTemplate.SignatureAlgorithm = x509.SHA256WithRSA - case certutil.ECPrivateKey: - certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 - } - - 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(oidExtensionBasicConstraints) { - 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(rand.Reader, 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.GetCAChain() - - return result, nil + return creation, nil } func convertRespToPKCS8(resp *logical.Response) error { @@ -1539,129 +1019,3 @@ func convertRespToPKCS8(resp *logical.Response) error { 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 { - 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(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: 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 -} diff --git a/builtin/logical/pki/path_config_urls.go b/builtin/logical/pki/path_config_urls.go index 93dd81b7b3..1644f605e1 100644 --- a/builtin/logical/pki/path_config_urls.go +++ b/builtin/logical/pki/path_config_urls.go @@ -7,6 +7,7 @@ import ( "github.com/asaskevich/govalidator" "github.com/fatih/structs" "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/logical" ) @@ -53,7 +54,7 @@ func validateURLs(urls []string) string { return "" } -func getURLs(ctx context.Context, req *logical.Request) (*urlEntries, error) { +func getURLs(ctx context.Context, req *logical.Request) (*certutil.URLEntries, error) { entry, err := req.Storage.Get(ctx, "urls") if err != nil { return nil, err @@ -62,7 +63,7 @@ func getURLs(ctx context.Context, req *logical.Request) (*urlEntries, error) { return nil, nil } - var entries urlEntries + var entries certutil.URLEntries if err := entry.DecodeJSON(&entries); err != nil { return nil, err } @@ -70,7 +71,7 @@ func getURLs(ctx context.Context, req *logical.Request) (*urlEntries, error) { return &entries, nil } -func writeURLs(ctx context.Context, req *logical.Request, entries *urlEntries) error { +func writeURLs(ctx context.Context, req *logical.Request, entries *certutil.URLEntries) error { entry, err := logical.StorageEntryJSON("urls", entries) if err != nil { return err @@ -109,7 +110,7 @@ func (b *backend) pathWriteURL(ctx context.Context, req *logical.Request, data * return nil, err } if entries == nil { - entries = &urlEntries{ + entries = &certutil.URLEntries{ IssuingCertificates: []string{}, CRLDistributionPoints: []string{}, OCSPServers: []string{}, @@ -141,12 +142,6 @@ func (b *backend) pathWriteURL(ctx context.Context, req *logical.Request, data * return nil, writeURLs(ctx, req, entries) } -type urlEntries struct { - IssuingCertificates []string `json:"issuing_certificates" structs:"issuing_certificates" mapstructure:"issuing_certificates"` - CRLDistributionPoints []string `json:"crl_distribution_points" structs:"crl_distribution_points" mapstructure:"crl_distribution_points"` - OCSPServers []string `json:"ocsp_servers" structs:"ocsp_servers" mapstructure:"ocsp_servers"` -} - const pathConfigURLsHelpSyn = ` Set the URLs for the issuing CA, CRL distribution points, and OCSP servers. ` diff --git a/builtin/logical/pki/path_intermediate.go b/builtin/logical/pki/path_intermediate.go index 0f7b57f56c..80788b39a5 100644 --- a/builtin/logical/pki/path_intermediate.go +++ b/builtin/logical/pki/path_intermediate.go @@ -71,7 +71,7 @@ func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Req } var resp *logical.Response - input := &dataBundle{ + input := &inputBundle{ role: role, req: req, apiData: data, diff --git a/builtin/logical/pki/path_issue_sign.go b/builtin/logical/pki/path_issue_sign.go index 1ace8071e7..8df1a7c50c 100644 --- a/builtin/logical/pki/path_issue_sign.go +++ b/builtin/logical/pki/path_issue_sign.go @@ -205,18 +205,17 @@ func (b *backend) pathIssueSignCert(ctx context.Context, req *logical.Request, d "error fetching CA certificate: %s", caErr)} } - input := &dataBundle{ - req: req, - apiData: data, - role: role, - signingBundle: signingBundle, + input := &inputBundle{ + req: req, + apiData: data, + role: role, } var parsedBundle *certutil.ParsedCertBundle var err error if useCSR { - parsedBundle, err = signCert(b, input, false, useCSRValues) + parsedBundle, err = signCert(b, input, signingBundle, false, useCSRValues) } else { - parsedBundle, err = generateCert(ctx, b, input, false) + parsedBundle, err = generateCert(ctx, b, input, signingBundle, false) } if err != nil { switch err.(type) { diff --git a/builtin/logical/pki/path_roles.go b/builtin/logical/pki/path_roles.go index 4885b3f312..6a372b583d 100644 --- a/builtin/logical/pki/path_roles.go +++ b/builtin/logical/pki/path_roles.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/parseutil" "github.com/hashicorp/vault/sdk/logical" @@ -552,13 +553,13 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data ), nil } - if errResp := validateKeyTypeLength(entry.KeyType, entry.KeyBits); errResp != nil { - return errResp, nil + if err := certutil.ValidateKeyTypeLength(entry.KeyType, entry.KeyBits); err != nil { + return logical.ErrorResponse(err.Error()), nil } if len(entry.ExtKeyUsageOIDs) > 0 { for _, oidstr := range entry.ExtKeyUsageOIDs { - _, err := stringToOid(oidstr) + _, err := certutil.StringToOid(oidstr) if err != nil { return logical.ErrorResponse(fmt.Sprintf("%q could not be parsed as a valid oid for an extended key usage", oidstr)), nil } @@ -567,7 +568,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data if len(entry.PolicyIdentifiers) > 0 { for _, oidstr := range entry.PolicyIdentifiers { - _, err := stringToOid(oidstr) + _, err := certutil.StringToOid(oidstr) if err != nil { return logical.ErrorResponse(fmt.Sprintf("%q could not be parsed as a valid oid for a policy identifier", oidstr)), nil } @@ -614,51 +615,51 @@ func parseKeyUsages(input []string) int { return int(parsedKeyUsages) } -func parseExtKeyUsages(role *roleEntry) certExtKeyUsage { - var parsedKeyUsages certExtKeyUsage +func parseExtKeyUsages(role *roleEntry) certutil.CertExtKeyUsage { + var parsedKeyUsages certutil.CertExtKeyUsage if role.ServerFlag { - parsedKeyUsages |= serverAuthExtKeyUsage + parsedKeyUsages |= certutil.ServerAuthExtKeyUsage } if role.ClientFlag { - parsedKeyUsages |= clientAuthExtKeyUsage + parsedKeyUsages |= certutil.ClientAuthExtKeyUsage } if role.CodeSigningFlag { - parsedKeyUsages |= codeSigningExtKeyUsage + parsedKeyUsages |= certutil.CodeSigningExtKeyUsage } if role.EmailProtectionFlag { - parsedKeyUsages |= emailProtectionExtKeyUsage + parsedKeyUsages |= certutil.EmailProtectionExtKeyUsage } for _, k := range role.ExtKeyUsage { switch strings.ToLower(strings.TrimSpace(k)) { case "any": - parsedKeyUsages |= anyExtKeyUsage + parsedKeyUsages |= certutil.AnyExtKeyUsage case "serverauth": - parsedKeyUsages |= serverAuthExtKeyUsage + parsedKeyUsages |= certutil.ServerAuthExtKeyUsage case "clientauth": - parsedKeyUsages |= clientAuthExtKeyUsage + parsedKeyUsages |= certutil.ClientAuthExtKeyUsage case "codesigning": - parsedKeyUsages |= codeSigningExtKeyUsage + parsedKeyUsages |= certutil.CodeSigningExtKeyUsage case "emailprotection": - parsedKeyUsages |= emailProtectionExtKeyUsage + parsedKeyUsages |= certutil.EmailProtectionExtKeyUsage case "ipsecendsystem": - parsedKeyUsages |= ipsecEndSystemExtKeyUsage + parsedKeyUsages |= certutil.IpsecEndSystemExtKeyUsage case "ipsectunnel": - parsedKeyUsages |= ipsecTunnelExtKeyUsage + parsedKeyUsages |= certutil.IpsecTunnelExtKeyUsage case "ipsecuser": - parsedKeyUsages |= ipsecUserExtKeyUsage + parsedKeyUsages |= certutil.IpsecUserExtKeyUsage case "timestamping": - parsedKeyUsages |= timeStampingExtKeyUsage + parsedKeyUsages |= certutil.TimeStampingExtKeyUsage case "ocspsigning": - parsedKeyUsages |= ocspSigningExtKeyUsage + parsedKeyUsages |= certutil.OcspSigningExtKeyUsage case "microsoftservergatedcrypto": - parsedKeyUsages |= microsoftServerGatedCryptoExtKeyUsage + parsedKeyUsages |= certutil.MicrosoftServerGatedCryptoExtKeyUsage case "netscapeservergatedcrypto": - parsedKeyUsages |= netscapeServerGatedCryptoExtKeyUsage + parsedKeyUsages |= certutil.NetscapeServerGatedCryptoExtKeyUsage } } diff --git a/builtin/logical/pki/path_root.go b/builtin/logical/pki/path_root.go index 2c3d7d7251..927aea2f53 100644 --- a/builtin/logical/pki/path_root.go +++ b/builtin/logical/pki/path_root.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/pem" "fmt" + "github.com/hashicorp/vault/sdk/helper/certutil" "reflect" "strings" "time" @@ -139,12 +140,12 @@ func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request, role.MaxPathLength = &maxPathLength } - input := &dataBundle{ + input := &inputBundle{ req: req, apiData: data, role: role, } - parsedBundle, err := generateCert(ctx, b, input, true) + parsedBundle, err := generateCert(ctx, b, input, nil, true) if err != nil { switch err.(type) { case errutil.UserError: @@ -296,13 +297,12 @@ func (b *backend) pathCASignIntermediate(ctx context.Context, req *logical.Reque role.MaxPathLength = &maxPathLength } - input := &dataBundle{ - req: req, - apiData: data, - signingBundle: signingBundle, - role: role, + input := &inputBundle{ + req: req, + apiData: data, + role: role, } - parsedBundle, err := signCert(b, input, true, useCSRValues) + parsedBundle, err := signCert(b, input, signingBundle, true, useCSRValues) if err != nil { switch err.(type) { case errutil.UserError: @@ -420,7 +420,7 @@ func (b *backend) pathCASignSelfIssued(ctx context.Context, req *logical.Request return nil, errwrap.Wrapf("error converting raw signing bundle to cert bundle: {{err}}", err) } - urls := &urlEntries{} + urls := &certutil.URLEntries{} if signingBundle.URLs != nil { urls = signingBundle.URLs } diff --git a/sdk/helper/certutil/helpers.go b/sdk/helper/certutil/helpers.go index d1cc281596..4a35f88dca 100644 --- a/sdk/helper/certutil/helpers.go +++ b/sdk/helper/certutil/helpers.go @@ -9,16 +9,24 @@ import ( "crypto/rsa" "crypto/sha1" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/pem" "errors" "fmt" "math/big" + "net" + "net/url" "strconv" "strings" + "time" + "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" ) // GetHexFormatted returns the byte buffer formatted in hex with @@ -275,7 +283,7 @@ func ComparePublicKeys(key1Iface, key2Iface crypto.PublicKey) (bool, error) { } } -// PasrsePublicKeyPEM is used to parse RSA and ECDSA public keys from PEMs +// 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 { @@ -299,3 +307,500 @@ func ParsePublicKeyPEM(data []byte) (interface{}, error) { return nil, errors.New("data does not contain any valid RSA or ECDSA public keys") } + +// addPolicyIdentifiers adds certificate policies extension +// +func AddPolicyIdentifiers(data *CreationBundle, certTemplate *x509.Certificate) { + for _, oidstr := range data.Params.PolicyIdentifiers { + oid, err := StringToOid(oidstr) + if err == nil { + certTemplate.PolicyIdentifiers = append(certTemplate.PolicyIdentifiers, oid) + } + } +} + +// 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 +} + +func ValidateKeyTypeLength(keyType string, keyBits int) error { + switch keyType { + case "rsa": + switch keyBits { + case 2048: + case 4096: + case 8192: + default: + return fmt.Errorf("unsupported bit length for RSA key: %d", keyBits) + } + case "ec": + switch keyBits { + case 224: + case 256: + case 384: + case 521: + default: + return fmt.Errorf("unsupported bit length for EC key: %d", keyBits) + } + case "any": + default: + return fmt.Errorf("unknown key type %s", keyType) + } + + return nil +} + +// Performs the heavy lifting of creating a certificate. Returns +// a fully-filled-in ParsedCertBundle. +func CreateCertificate(data *CreationBundle) (*ParsedCertBundle, error) { + var err error + result := &ParsedCertBundle{} + + serialNumber, err := GenerateSerialNumber() + if err != nil { + return nil, err + } + + if err := GeneratePrivateKey(data.Params.KeyType, + data.Params.KeyBits, + result); 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 { + switch data.SigningBundle.PrivateKeyType { + case RSAPrivateKey: + certTemplate.SignatureAlgorithm = x509.SHA256WithRSA + case ECPrivateKey: + certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 + } + + caCert := data.SigningBundle.Certificate + certTemplate.AuthorityKeyId = caCert.SubjectKeyId + + certBytes, err = x509.CreateCertificate(rand.Reader, 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": + certTemplate.SignatureAlgorithm = x509.SHA256WithRSA + case "ec": + certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 + } + + certTemplate.AuthorityKeyId = subjKeyID + certTemplate.BasicConstraintsValid = true + certBytes, err = x509.CreateCertificate(rand.Reader, 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) { + + result.CAChain = []*CertBlock{ + &CertBlock{ + Certificate: data.SigningBundle.Certificate, + Bytes: data.SigningBundle.CertificateBytes, + }, + } + result.CAChain = append(result.CAChain, data.SigningBundle.CAChain...) + } + } + + return result, nil +} + +var oidExtensionBasicConstraints = []int{2, 5, 29, 19} + +// Creates a CSR. This is currently only meant for use when +// generating an intermediate certificate. +func CreateCSR(data *CreationBundle, addBasicConstraints bool) (*ParsedCSRBundle, error) { + var err error + result := &ParsedCSRBundle{} + + if err := GeneratePrivateKey(data.Params.KeyType, + data.Params.KeyBits, + result); 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 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: oidExtensionBasicConstraints, + Value: val, + Critical: true, + } + csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, ext) + } + + switch data.Params.KeyType { + case "rsa": + csrTemplate.SignatureAlgorithm = x509.SHA256WithRSA + case "ec": + csrTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 + } + + csr, err := x509.CreateCertificateRequest(rand.Reader, 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)} + } + + return result, nil +} + +// Performs the heavy lifting of generating a certificate from a CSR. +// Returns a ParsedCertBundle sans private keys. +func SignCertificate(data *CreationBundle) (*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"} + } + + err := data.CSR.CheckSignature() + if err != nil { + return nil, errutil.UserError{Err: "request signature invalid"} + } + + result := &ParsedCertBundle{} + + serialNumber, err := GenerateSerialNumber() + if err != nil { + return nil, err + } + + marshaledKey, err := x509.MarshalPKIXPublicKey(data.CSR.PublicKey) + if err != nil { + return nil, errutil.InternalError{Err: fmt.Sprintf("error marshalling public key: %s", err)} + } + subjKeyID := sha1.Sum(marshaledKey) + + 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) + } + + switch data.SigningBundle.PrivateKeyType { + case RSAPrivateKey: + certTemplate.SignatureAlgorithm = x509.SHA256WithRSA + case ECPrivateKey: + certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 + } + + 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(oidExtensionBasicConstraints) { + 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(rand.Reader, 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.GetCAChain() + + return result, nil +} diff --git a/sdk/helper/certutil/types.go b/sdk/helper/certutil/types.go index 62b494b19a..8b62db645a 100644 --- a/sdk/helper/certutil/types.go +++ b/sdk/helper/certutil/types.go @@ -15,10 +15,14 @@ import ( "crypto/rsa" "crypto/tls" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" "fmt" "math/big" + "net" + "net/url" "strings" + "time" "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/sdk/helper/errutil" @@ -598,3 +602,158 @@ type IssueData struct { IPSANs string `json:"ip_sans" structs:"ip_sans" mapstructure:"ip_sans"` CSR string `json:"csr" structs:"csr" mapstructure:"csr"` } + +type URLEntries struct { + IssuingCertificates []string `json:"issuing_certificates" structs:"issuing_certificates" mapstructure:"issuing_certificates"` + CRLDistributionPoints []string `json:"crl_distribution_points" structs:"crl_distribution_points" mapstructure:"crl_distribution_points"` + OCSPServers []string `json:"ocsp_servers" structs:"ocsp_servers" mapstructure:"ocsp_servers"` +} + +type CAInfoBundle struct { + ParsedCertBundle + URLs *URLEntries +} + +func (b *CAInfoBundle) GetCAChain() []*CertBlock { + chain := []*CertBlock{} + + // Include issuing CA in Chain, not including Root Authority + if (len(b.Certificate.AuthorityKeyId) > 0 && + !bytes.Equal(b.Certificate.AuthorityKeyId, b.Certificate.SubjectKeyId)) || + (len(b.Certificate.AuthorityKeyId) == 0 && + !bytes.Equal(b.Certificate.RawIssuer, b.Certificate.RawSubject)) { + + chain = append(chain, &CertBlock{ + Certificate: b.Certificate, + Bytes: b.CertificateBytes, + }) + if b.CAChain != nil && len(b.CAChain) > 0 { + chain = append(chain, b.CAChain...) + } + } + + return chain +} + +type CertExtKeyUsage int + +const ( + AnyExtKeyUsage CertExtKeyUsage = 1 << iota + ServerAuthExtKeyUsage + ClientAuthExtKeyUsage + CodeSigningExtKeyUsage + EmailProtectionExtKeyUsage + IpsecEndSystemExtKeyUsage + IpsecTunnelExtKeyUsage + IpsecUserExtKeyUsage + TimeStampingExtKeyUsage + OcspSigningExtKeyUsage + MicrosoftServerGatedCryptoExtKeyUsage + NetscapeServerGatedCryptoExtKeyUsage + MicrosoftCommercialCodeSigningExtKeyUsage + MicrosoftKernelCodeSigningExtKeyUsage +) + +type CreationParameters struct { + Subject pkix.Name + DNSNames []string + EmailAddresses []string + IPAddresses []net.IP + URIs []*url.URL + OtherSANs map[string][]string + IsCA bool + KeyType string + KeyBits int + NotAfter time.Time + KeyUsage x509.KeyUsage + ExtKeyUsage CertExtKeyUsage + ExtKeyUsageOIDs []string + PolicyIdentifiers []string + BasicConstraintsValidForNonCA bool + + // Only used when signing a CA cert + UseCSRValues bool + PermittedDNSDomains []string + + // URLs to encode into the certificate + URLs *URLEntries + + // The maximum path length to encode + MaxPathLength int + + // The duration the certificate will use NotBefore + NotBeforeDuration time.Duration +} + +type CreationBundle struct { + Params *CreationParameters + SigningBundle *CAInfoBundle + CSR *x509.CertificateRequest +} + +// addKeyUsages adds appropriate key usages to the template given the creation +// information +func AddKeyUsages(data *CreationBundle, certTemplate *x509.Certificate) { + if data.Params.IsCA { + certTemplate.KeyUsage = x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign) + return + } + + certTemplate.KeyUsage = data.Params.KeyUsage + + if data.Params.ExtKeyUsage&AnyExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageAny) + } + + if data.Params.ExtKeyUsage&ServerAuthExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) + } + + if data.Params.ExtKeyUsage&ClientAuthExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) + } + + if data.Params.ExtKeyUsage&CodeSigningExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) + } + + if data.Params.ExtKeyUsage&EmailProtectionExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) + } + + if data.Params.ExtKeyUsage&IpsecEndSystemExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageIPSECEndSystem) + } + + if data.Params.ExtKeyUsage&IpsecTunnelExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageIPSECTunnel) + } + + if data.Params.ExtKeyUsage&IpsecUserExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageIPSECUser) + } + + if data.Params.ExtKeyUsage&TimeStampingExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageTimeStamping) + } + + if data.Params.ExtKeyUsage&OcspSigningExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning) + } + + if data.Params.ExtKeyUsage&MicrosoftServerGatedCryptoExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageMicrosoftServerGatedCrypto) + } + + if data.Params.ExtKeyUsage&NetscapeServerGatedCryptoExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageNetscapeServerGatedCrypto) + } + + if data.Params.ExtKeyUsage&MicrosoftCommercialCodeSigningExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageMicrosoftCommercialCodeSigning) + } + + if data.Params.ExtKeyUsage&MicrosoftKernelCodeSigningExtKeyUsage != 0 { + certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageMicrosoftKernelCodeSigning) + } +}