From a43a854740b07f041018d8aa355f425ff68fe07e Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 16 Feb 2018 17:19:34 -0500 Subject: [PATCH] Support other names in SANs (#3889) --- builtin/logical/pki/backend_test.go | 247 ++++++ builtin/logical/pki/ca_util.go | 7 + builtin/logical/pki/cert_util.go | 656 ++++++++++------ builtin/logical/pki/fields.go | 48 ++ builtin/logical/pki/path_intermediate.go | 7 +- builtin/logical/pki/path_issue_sign.go | 10 +- builtin/logical/pki/path_roles.go | 66 +- builtin/logical/pki/path_root.go | 22 +- helper/parseutil/parseutil.go | 18 + logical/framework/field_data.go | 13 +- vendor/golang.org/x/crypto/cryptobyte/asn1.go | 732 ++++++++++++++++++ .../x/crypto/cryptobyte/asn1/asn1.go | 46 ++ .../golang.org/x/crypto/cryptobyte/builder.go | 309 ++++++++ .../golang.org/x/crypto/cryptobyte/string.go | 167 ++++ vendor/vendor.json | 12 + website/source/api/secret/pki/index.html.md | 146 +++- 16 files changed, 2258 insertions(+), 248 deletions(-) create mode 100644 vendor/golang.org/x/crypto/cryptobyte/asn1.go create mode 100644 vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go create mode 100644 vendor/golang.org/x/crypto/cryptobyte/builder.go create mode 100644 vendor/golang.org/x/crypto/cryptobyte/string.go diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 0684ee5555fa..02088e045184 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -2801,6 +2801,253 @@ func TestBackend_SignSelfIssued(t *testing.T) { } } +// This is a really tricky test because the Go stdlib asn1 package is incapable +// of doing the right thing with custom OID SANs (see comments in the package, +// it's readily admitted that it's too magic) but that means that any +// validation logic written for this test isn't being independently verified, +// as in, if cryptobytes is used to decode it to make the test work, that +// doesn't mean we're encoding and decoding correctly, only that we made the +// test pass. Instead, when run verbosely it will first perform a bunch of +// checks to verify that the OID SAN logic doesn't screw up other SANs, then +// will spit out the PEM. This can be validated independently. +// +// You want the hex dump of the octet string corresponding to the X509v3 +// Subject Alternative Name. There's a nice online utility at +// https://lapo.it/asn1js that can be used to view the structure of an +// openssl-generated other SAN at +// https://lapo.it/asn1js/#3022A020060A2B060104018237140203A0120C106465766F7073406C6F63616C686F7374 +// (openssl asn1parse can also be used with -strparse using an offset of the +// hex blob for the subject alternative names extension). +// +// The structure output from here should match that precisely (even if the OID +// itself doesn't) in the second test. +// +// The test that encodes two should have them be in separate elements in the +// top-level sequence; see +// https://lapo.it/asn1js/#3046A020060A2B060104018237140203A0120C106465766F7073406C6F63616C686F7374A022060A2B060104018237140204A0140C12322D6465766F7073406C6F63616C686F7374 for an openssl-generated example. +// +// The good news is that it's valid to simply copy and paste the PEM ouput from +// here into the form at that site as it will do the right thing so it's pretty +// easy to validate. +func TestBackend_OID_SANs(t *testing.T) { + coreConfig := &vault.CoreConfig{ + LogicalBackends: map[string]logical.Factory{ + "pki": Factory, + }, + } + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: vaulthttp.Handler, + }) + cluster.Start() + defer cluster.Cleanup() + + client := cluster.Cores[0].Client + var err error + err = client.Sys().Mount("root", &api.MountInput{ + Type: "pki", + Config: api.MountConfigInput{ + DefaultLeaseTTL: "16h", + MaxLeaseTTL: "60h", + }, + }) + if err != nil { + t.Fatal(err) + } + + var resp *api.Secret + var certStr string + var block *pem.Block + var cert *x509.Certificate + + _, err = client.Logical().Write("root/root/generate/internal", map[string]interface{}{ + "ttl": "40h", + "common_name": "myvault.com", + }) + if err != nil { + t.Fatal(err) + } + + _, err = client.Logical().Write("root/roles/test", map[string]interface{}{ + "allowed_domains": []string{"foobar.com", "zipzap.com"}, + "allow_bare_domains": true, + "allow_subdomains": true, + "allow_ip_sans": true, + "allowed_other_sans": "1.3.6.1.4.1.311.20.2.3;UTF8:devops@*,1.3.6.1.4.1.311.20.2.4;utf8:d*e@foobar.com", + }) + if err != nil { + t.Fatal(err) + } + + // Get a baseline before adding OID SANs. In the next sections we'll verify + // that the SANs are all added even as the OID SAN inclusion forces other + // adding logic (custom rather than built-in Golang logic) + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + }) + if err != nil { + t.Fatal(err) + } + certStr = resp.Data["certificate"].(string) + block, _ = pem.Decode([]byte(certStr)) + cert, err = x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal(err) + } + if cert.IPAddresses[0].String() != "1.2.3.4" { + t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String()) + } + if cert.DNSNames[0] != "foobar.com" || + cert.DNSNames[1] != "bar.foobar.com" || + cert.DNSNames[2] != "foo.foobar.com" { + t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) + } + + // First test some bad stuff that shouldn't work + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + // Not a valid value for the first possibility + "other_sans": "1.3.6.1.4.1.311.20.2.3;UTF8:devop@nope.com", + }) + if err == nil { + t.Fatal("expected error") + } + + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + // Not a valid OID for the first possibility + "other_sans": "1.3.6.1.4.1.311.20.2.5;UTF8:devops@nope.com", + }) + if err == nil { + t.Fatal("expected error") + } + + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + // Not a valid name for the second possibility + "other_sans": "1.3.6.1.4.1.311.20.2.4;UTF8:d34g@foobar.com", + }) + if err == nil { + t.Fatal("expected error") + } + + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + // Not a valid OID for the second possibility + "other_sans": "1.3.6.1.4.1.311.20.2.5;UTF8:d34e@foobar.com", + }) + if err == nil { + t.Fatal("expected error") + } + + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + // Not a valid type + "other_sans": "1.3.6.1.4.1.311.20.2.5;UTF2:d34e@foobar.com", + }) + if err == nil { + t.Fatal("expected error") + } + + // Valid for first possibility + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + "other_sans": "1.3.6.1.4.1.311.20.2.3;utf8:devops@nope.com", + }) + if err != nil { + t.Fatal(err) + } + certStr = resp.Data["certificate"].(string) + block, _ = pem.Decode([]byte(certStr)) + cert, err = x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal(err) + } + if cert.IPAddresses[0].String() != "1.2.3.4" { + t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String()) + } + if cert.DNSNames[0] != "foobar.com" || + cert.DNSNames[1] != "bar.foobar.com" || + cert.DNSNames[2] != "foo.foobar.com" { + t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) + } + t.Logf("certificate 1 to check:\n%s", certStr) + + // Valid for second possibility + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + "other_sans": "1.3.6.1.4.1.311.20.2.4;UTF8:d234e@foobar.com", + }) + if err != nil { + t.Fatal(err) + } + certStr = resp.Data["certificate"].(string) + block, _ = pem.Decode([]byte(certStr)) + cert, err = x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal(err) + } + if cert.IPAddresses[0].String() != "1.2.3.4" { + t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String()) + } + if cert.DNSNames[0] != "foobar.com" || + cert.DNSNames[1] != "bar.foobar.com" || + cert.DNSNames[2] != "foo.foobar.com" { + t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) + } + t.Logf("certificate 2 to check:\n%s", certStr) + + // Valid for both + resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{ + "common_name": "foobar.com", + "ip_sans": "1.2.3.4", + "alt_names": "foo.foobar.com,bar.foobar.com", + "ttl": "1h", + "other_sans": "1.3.6.1.4.1.311.20.2.3;utf8:devops@nope.com,1.3.6.1.4.1.311.20.2.4;utf8:d234e@foobar.com", + }) + if err != nil { + t.Fatal(err) + } + certStr = resp.Data["certificate"].(string) + block, _ = pem.Decode([]byte(certStr)) + cert, err = x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal(err) + } + if cert.IPAddresses[0].String() != "1.2.3.4" { + t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String()) + } + if cert.DNSNames[0] != "foobar.com" || + cert.DNSNames[1] != "bar.foobar.com" || + cert.DNSNames[2] != "foo.foobar.com" { + t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) + } + t.Logf("certificate 3 to check:\n%s", certStr) +} + const ( rsaCAKey string = `-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAmPQlK7xD5p+E8iLQ8XlVmll5uU2NKMxKY3UF5tbh+0vkc+Fy diff --git a/builtin/logical/pki/ca_util.go b/builtin/logical/pki/ca_util.go index f0024a84cc57..384f440d8bf5 100644 --- a/builtin/logical/pki/ca_util.go +++ b/builtin/logical/pki/ca_util.go @@ -36,6 +36,13 @@ func (b *backend) getGenerationParams( AllowAnyName: true, AllowIPSANs: true, EnforceHostnames: false, + OU: data.Get("ou").([]string), + Organization: data.Get("organization").([]string), + Country: data.Get("country").([]string), + Locality: data.Get("locality").([]string), + Province: data.Get("province").([]string), + StreetAddress: data.Get("street_address").([]string), + PostalCode: data.Get("postal_code").([]string), } if role.KeyType == "rsa" && role.KeyBits < 2048 { diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index a02a4c229231..09b0317b3aa1 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -16,6 +16,7 @@ import ( "fmt" "net" "regexp" + "strconv" "strings" "time" @@ -27,6 +28,8 @@ import ( "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" "github.com/ryanuber/go-glob" + "golang.org/x/crypto/cryptobyte" + cbbasn1 "golang.org/x/crypto/cryptobyte/asn1" "golang.org/x/net/idna" ) @@ -39,17 +42,24 @@ const ( emailProtectionExtKeyUsage ) -type creationBundle struct { - CommonName string - OU []string - Organization []string +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 + OtherSANs map[string][]string IsCA bool KeyType string KeyBits int - SigningBundle *caInfoBundle NotAfter time.Time KeyUsage x509.KeyUsage ExtKeyUsage certExtKeyUsage @@ -261,7 +271,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(req *logical.Request, names []string, role *roleEntry) string { +func validateNames(data *dataBundle, names []string) string { for _, name := range names { sanitizedName := name emailDomain := name @@ -301,7 +311,7 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string // applies when allowing any name. Also, we check the sanitized name to // ensure that we are not either checking a full email address or a // wildcard prefix. - if role.EnforceHostnames { + if data.role.EnforceHostnames { p := idna.New( idna.StrictDomainName(true), idna.VerifyDNSLength(true), @@ -316,7 +326,7 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string } // Self-explanatory - if role.AllowAnyName { + if data.role.AllowAnyName { continue } @@ -336,7 +346,7 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string // // Variances are noted in-line - if role.AllowLocalhost { + if data.role.AllowLocalhost { if name == "localhost" || name == "localdomain" || (isEmail && emailDomain == "localhost") || @@ -344,7 +354,7 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string continue } - if role.AllowSubdomains { + if data.role.AllowSubdomains { // It is possible, if unlikely, to have a subdomain of "localhost" if strings.HasSuffix(sanitizedName, ".localhost") || (isWildcard && sanitizedName == "localhost") { @@ -359,18 +369,18 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string } } - if role.AllowTokenDisplayName { - if name == req.DisplayName { + if data.role.AllowTokenDisplayName { + if name == data.req.DisplayName { continue } - if role.AllowSubdomains { + if data.role.AllowSubdomains { if isEmail { // If it's an email address, we need to parse the token // display name in order to do a proper comparison of the // subdomain - if strings.Contains(req.DisplayName, "@") { - splitDisplay := strings.Split(req.DisplayName, "@") + if strings.Contains(data.req.DisplayName, "@") { + splitDisplay := strings.Split(data.req.DisplayName, "@") if len(splitDisplay) == 2 { // Compare the sanitized name against the hostname // portion of the email address in the roken @@ -382,16 +392,16 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string } } - if strings.HasSuffix(sanitizedName, "."+req.DisplayName) || - (isWildcard && sanitizedName == req.DisplayName) { + if strings.HasSuffix(sanitizedName, "."+data.req.DisplayName) || + (isWildcard && sanitizedName == data.req.DisplayName) { continue } } } - if len(role.AllowedDomains) > 0 { + if len(data.role.AllowedDomains) > 0 { valid := false - for _, currDomain := range role.AllowedDomains { + for _, currDomain := range data.role.AllowedDomains { // If there is, say, a trailing comma, ignore it if currDomain == "" { continue @@ -399,14 +409,14 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string // First, allow an exact match of the base domain if that role flag // is enabled - if role.AllowBareDomains && + if data.role.AllowBareDomains && (name == currDomain || (isEmail && emailDomain == currDomain)) { valid = true break } - if role.AllowSubdomains { + if data.role.AllowSubdomains { if strings.HasSuffix(sanitizedName, "."+currDomain) || (isWildcard && sanitizedName == currDomain) { valid = true @@ -414,7 +424,7 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string } } - if role.AllowGlobDomains && + if data.role.AllowGlobDomains && strings.Contains(currDomain, "*") && glob.Glob(currDomain, name) { valid = true @@ -432,31 +442,88 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) string return "" } +// 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 *dataBundle, requested map[string][]string) (string, string, error) { + allowed, err := parseOtherSANs(data.role.AllowedOtherSANs) + if err != nil { + return "", "", errwrap.Wrapf("error parsing role's allowed SANs: {{err}}", err) + } + for oid, names := range requested { + for _, name := range names { + allowedNames, ok := allowed[oid] + if !ok { + return oid, "", nil + } + + valid := false + for _, allowedName := range allowedNames { + if glob.Glob(allowedName, name) { + valid = true + break + } + } + + if !valid { + return oid, name, nil + } + } + } + + return "", "", nil +} + +func parseOtherSANs(others []string) (map[string][]string, error) { + result := map[string][]string{} + for _, other := range others { + splitOther := strings.SplitN(other, ";", 2) + if len(splitOther) != 2 { + return nil, fmt.Errorf("expected a semicolon in other SAN %q", other) + } + splitType := strings.SplitN(splitOther[1], ":", 2) + if len(splitType) != 2 { + return nil, fmt.Errorf("expected a colon in other SAN %q", other) + } + if strings.ToLower(splitType[0]) != "utf8" { + return nil, fmt.Errorf("only utf8 other SANs are supported; found non-supported type in other SAN %q", other) + } + result[splitOther[0]] = append(result[splitOther[0]], splitType[1]) + } + + return result, nil +} + func generateCert(ctx context.Context, b *backend, - role *roleEntry, - signingBundle *caInfoBundle, - isCA bool, - req *logical.Request, - data *framework.FieldData) (*certutil.ParsedCertBundle, error) { + data *dataBundle, + isCA bool) (*certutil.ParsedCertBundle, error) { - if role.KeyType == "rsa" && role.KeyBits < 2048 { + if data.role == nil { + return nil, errutil.InternalError{Err: "no role found in data bundle"} + } + + if data.role.KeyType == "rsa" && data.role.KeyBits < 2048 { return nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"} } - creationBundle, err := generateCreationBundle(b, role, signingBundle, nil, req, data) + err := generateCreationBundle(b, data) if err != nil { return nil, err } + if data.params == nil { + return nil, errutil.InternalError{Err: "nil paramaters received from parameter bundle generation"} + } if isCA { - creationBundle.IsCA = isCA + data.params.IsCA = isCA - creationBundle.PermittedDNSDomains = data.Get("permitted_dns_domains").([]string) + data.params.PermittedDNSDomains = data.apiData.Get("permitted_dns_domains").([]string) - if signingBundle == nil { + if data.signingBundle == nil { // Generating a self-signed root certificate - entries, err := getURLs(ctx, req) + entries, err := getURLs(ctx, data.req) if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} } @@ -467,17 +534,17 @@ func generateCert(ctx context.Context, OCSPServers: []string{}, } } - creationBundle.URLs = entries + data.params.URLs = entries - if role.MaxPathLength == nil { - creationBundle.MaxPathLength = -1 + if data.role.MaxPathLength == nil { + data.params.MaxPathLength = -1 } else { - creationBundle.MaxPathLength = *role.MaxPathLength + data.params.MaxPathLength = *data.role.MaxPathLength } } } - parsedBundle, err := createCertificate(creationBundle) + parsedBundle, err := createCertificate(data) if err != nil { return nil, err } @@ -487,18 +554,16 @@ 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, - role *roleEntry, - signingBundle *caInfoBundle, - req *logical.Request, - data *framework.FieldData) (*certutil.ParsedCSRBundle, error) { - - creationBundle, err := generateCreationBundle(b, role, signingBundle, nil, req, data) +func generateIntermediateCSR(b *backend, data *dataBundle) (*certutil.ParsedCSRBundle, error) { + err := generateCreationBundle(b, data) if err != nil { return nil, err } + if data.params == nil { + return nil, errutil.InternalError{Err: "nil paramaters received from parameter bundle generation"} + } - parsedBundle, err := createCSR(creationBundle) + parsedBundle, err := createCSR(data) if err != nil { return nil, err } @@ -507,14 +572,15 @@ func generateIntermediateCSR(b *backend, } func signCert(b *backend, - role *roleEntry, - signingBundle *caInfoBundle, + data *dataBundle, isCA bool, - useCSRValues bool, - req *logical.Request, - data *framework.FieldData) (*certutil.ParsedCertBundle, error) { + useCSRValues bool) (*certutil.ParsedCertBundle, error) { + + if data.role == nil { + return nil, errutil.InternalError{Err: "no role found in data bundle"} + } - csrString := data.Get("csr").(string) + csrString := data.apiData.Get("csr").(string) if csrString == "" { return nil, errutil.UserError{Err: fmt.Sprintf("\"csr\" is empty")} } @@ -529,13 +595,13 @@ func signCert(b *backend, return nil, errutil.UserError{Err: fmt.Sprintf("certificate request could not be parsed: %v", err)} } - switch role.KeyType { + switch data.role.KeyType { case "rsa": // Verify that the key matches the role type if csr.PublicKeyAlgorithm != x509.RSA { return nil, errutil.UserError{Err: fmt.Sprintf( "role requires keys of type %s", - role.KeyType)} + data.role.KeyType)} } pubKey, ok := csr.PublicKey.(*rsa.PublicKey) if !ok { @@ -548,10 +614,10 @@ func signCert(b *backend, } // Verify that the bit size is at least the size specified in the role - if pubKey.N.BitLen() < role.KeyBits { + if pubKey.N.BitLen() < data.role.KeyBits { return nil, errutil.UserError{Err: fmt.Sprintf( "role requires a minimum of a %d-bit key, but CSR's key is %d bits", - role.KeyBits, + data.role.KeyBits, pubKey.N.BitLen())} } @@ -560,7 +626,7 @@ func signCert(b *backend, if csr.PublicKeyAlgorithm != x509.ECDSA { return nil, errutil.UserError{Err: fmt.Sprintf( "role requires keys of type %s", - role.KeyType)} + data.role.KeyType)} } pubKey, ok := csr.PublicKey.(*ecdsa.PublicKey) if !ok { @@ -568,10 +634,10 @@ func signCert(b *backend, } // Verify that the bit size is at least the size specified in the role - if pubKey.Params().BitSize < role.KeyBits { + if pubKey.Params().BitSize < data.role.KeyBits { return nil, errutil.UserError{Err: fmt.Sprintf( "role requires a minimum of a %d-bit key, but CSR's key is %d bits", - role.KeyBits, + data.role.KeyBits, pubKey.Params().BitSize)} } @@ -593,19 +659,24 @@ func signCert(b *backend, } - creationBundle, err := generateCreationBundle(b, role, signingBundle, csr, req, data) + data.csr = csr + + err = generateCreationBundle(b, data) if err != nil { return nil, err } + if data.params == nil { + return nil, errutil.InternalError{Err: "nil paramaters received from parameter bundle generation"} + } - creationBundle.IsCA = isCA - creationBundle.UseCSRValues = useCSRValues + data.params.IsCA = isCA + data.params.UseCSRValues = useCSRValues if isCA { - creationBundle.PermittedDNSDomains = data.Get("permitted_dns_domains").([]string) + data.params.PermittedDNSDomains = data.apiData.Get("permitted_dns_domains").([]string) } - parsedBundle, err := signCertificate(creationBundle, csr) + parsedBundle, err := signCertificate(data) if err != nil { return nil, err } @@ -614,14 +685,9 @@ func signCert(b *backend, } // generateCreationBundle is a shared function that reads parameters supplied -// from the various endpoints and generates a creationBundle 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, - role *roleEntry, - signingBundle *caInfoBundle, - csr *x509.CertificateRequest, - req *logical.Request, - data *framework.FieldData) (*creationBundle, error) { +func generateCreationBundle(b *backend, data *dataBundle) error { var err error var ok bool @@ -630,22 +696,22 @@ func generateCreationBundle(b *backend, dnsNames := []string{} emailAddresses := []string{} { - if csr != nil && role.UseCSRCommonName { - cn = csr.Subject.CommonName + if data.csr != nil && data.role.UseCSRCommonName { + cn = data.csr.Subject.CommonName } if cn == "" { - cn = data.Get("common_name").(string) - if cn == "" && role.RequireCN { - 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`} + 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`} } } - if csr != nil && role.UseCSRSANs { - dnsNames = csr.DNSNames - emailAddresses = csr.EmailAddresses + if data.csr != nil && data.role.UseCSRSANs { + dnsNames = data.csr.DNSNames + emailAddresses = data.csr.EmailAddresses } - if cn != "" && !data.Get("exclude_cn_from_sans").(bool) { + if cn != "" && !data.apiData.Get("exclude_cn_from_sans").(bool) { if strings.Contains(cn, "@") { // Note: emails are not disallowed if the role's email protection // flag is false, because they may well be included for @@ -662,7 +728,7 @@ func generateCreationBundle(b *backend, ) converted, err := p.ToASCII(cn) if err != nil { - return nil, errutil.UserError{Err: err.Error()} + return errutil.UserError{Err: err.Error()} } if hostnameRegex.MatchString(converted) { dnsNames = append(dnsNames, converted) @@ -670,8 +736,8 @@ func generateCreationBundle(b *backend, } } - if csr == nil || !role.UseCSRSANs { - cnAltRaw, ok := data.GetOk("alt_names") + if data.csr == nil || !data.role.UseCSRSANs { + cnAltRaw, ok := data.apiData.GetOk("alt_names") if ok { cnAlt := strutil.ParseDedupLowercaseAndSortStrings(cnAltRaw.(string), ",") for _, v := range cnAlt { @@ -686,7 +752,7 @@ func generateCreationBundle(b *backend, ) converted, err := p.ToASCII(v) if err != nil { - return nil, errutil.UserError{Err: err.Error()} + return errutil.UserError{Err: err.Error()} } if hostnameRegex.MatchString(converted) { dnsNames = append(dnsNames, converted) @@ -699,52 +765,73 @@ func generateCreationBundle(b *backend, // Check the CN. This ensures that the CN is checked even if it's // excluded from SANs. if cn != "" { - badName := validateNames(req, []string{cn}, role) + badName := validateNames(data, []string{cn}) if len(badName) != 0 { - return nil, errutil.UserError{Err: fmt.Sprintf( + return errutil.UserError{Err: fmt.Sprintf( "common name %s not allowed by this role", badName)} } } // Check for bad email and/or DNS names - badName := validateNames(req, dnsNames, role) + badName := validateNames(data, dnsNames) if len(badName) != 0 { - return nil, errutil.UserError{Err: fmt.Sprintf( + return errutil.UserError{Err: fmt.Sprintf( "subject alternate name %s not allowed by this role", badName)} } - badName = validateNames(req, emailAddresses, role) + badName = validateNames(data, emailAddresses) if len(badName) != 0 { - return nil, errutil.UserError{Err: fmt.Sprintf( + return errutil.UserError{Err: fmt.Sprintf( "email address %s not allowed by this role", badName)} } } + var otherSANs map[string][]string + 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()} + } + badOID, badName, err := validateOtherSANs(data, requested) + switch { + case err != nil: + return errutil.UserError{Err: err.Error()} + case len(badName) > 0: + return 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( + "other SAN OID %s not allowed by this role", badOID)} + default: + otherSANs = requested + } + } + // Get and verify any IP SANs ipAddresses := []net.IP{} var ipAltInt interface{} { - if csr != nil && role.UseCSRSANs { - if len(csr.IPAddresses) > 0 { - if !role.AllowIPSANs { - return nil, errutil.UserError{Err: fmt.Sprintf( + if data.csr != nil && data.role.UseCSRSANs { + if len(data.csr.IPAddresses) > 0 { + if !data.role.AllowIPSANs { + return errutil.UserError{Err: fmt.Sprintf( "IP Subject Alternative Names are not allowed in this role, but was provided some via CSR")} } - ipAddresses = csr.IPAddresses + ipAddresses = data.csr.IPAddresses } } else { - ipAltInt, ok = data.GetOk("ip_sans") + ipAltInt, ok = data.apiData.GetOk("ip_sans") if ok { ipAlt := ipAltInt.(string) if len(ipAlt) != 0 { - if !role.AllowIPSANs { - return nil, errutil.UserError{Err: fmt.Sprintf( + if !data.role.AllowIPSANs { + return errutil.UserError{Err: fmt.Sprintf( "IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)} } for _, v := range strings.Split(ipAlt, ",") { parsedIP := net.ParseIP(v) if parsedIP == nil { - return nil, errutil.UserError{Err: fmt.Sprintf( + return errutil.UserError{Err: fmt.Sprintf( "the value '%s' is not a valid IP address", v)} } ipAddresses = append(ipAddresses, parsedIP) @@ -754,32 +841,37 @@ func generateCreationBundle(b *backend, } } - // Set OU (organizationalUnit) values if specified in the role - ou := strutil.RemoveDuplicates(role.OU, false) - // Set O (organization) values if specified in the role - organization := strutil.RemoveDuplicates(role.Organization, false) + subject := pkix.Name{ + Country: strutil.RemoveDuplicates(data.role.Country, false), + Organization: strutil.RemoveDuplicates(data.role.Organization, false), + OrganizationalUnit: strutil.RemoveDuplicates(data.role.OU, false), + Locality: strutil.RemoveDuplicates(data.role.Locality, false), + Province: strutil.RemoveDuplicates(data.role.Province, false), + StreetAddress: strutil.RemoveDuplicates(data.role.StreetAddress, false), + PostalCode: strutil.RemoveDuplicates(data.role.PostalCode, false), + } // Get the TTL and verify it against the max allowed var ttl time.Duration var maxTTL time.Duration var notAfter time.Time { - ttl = time.Duration(data.Get("ttl").(int)) * time.Second + ttl = time.Duration(data.apiData.Get("ttl").(int)) * time.Second if ttl == 0 { - if role.TTL != "" { - ttl, err = parseutil.ParseDurationSecond(role.TTL) + if data.role.TTL != "" { + ttl, err = parseutil.ParseDurationSecond(data.role.TTL) if err != nil { - return nil, errutil.UserError{Err: fmt.Sprintf( + return errutil.UserError{Err: fmt.Sprintf( "invalid role ttl: %s", err)} } } } - if role.MaxTTL != "" { - maxTTL, err = parseutil.ParseDurationSecond(role.MaxTTL) + if data.role.MaxTTL != "" { + maxTTL, err = parseutil.ParseDurationSecond(data.role.MaxTTL) if err != nil { - return nil, errutil.UserError{Err: fmt.Sprintf( + return errutil.UserError{Err: fmt.Sprintf( "invalid role max_ttl: %s", err)} } } @@ -798,10 +890,10 @@ func generateCreationBundle(b *backend, // If it's not self-signed, verify that the issued certificate won't be // valid past the lifetime of the CA certificate - if signingBundle != nil && - notAfter.After(signingBundle.Certificate.NotAfter) && !role.AllowExpirationPastCA { + if data.signingBundle != nil && + notAfter.After(data.signingBundle.Certificate.NotAfter) && !data.role.AllowExpirationPastCA { - return nil, errutil.UserError{Err: fmt.Sprintf( + return errutil.UserError{Err: fmt.Sprintf( "cannot satisfy request, as TTL is beyond the expiration of the CA certificate")} } } @@ -809,94 +901,92 @@ func generateCreationBundle(b *backend, // Build up usages var extUsage certExtKeyUsage { - if role.ServerFlag { + if data.role.ServerFlag { extUsage = extUsage | serverExtKeyUsage } - if role.ClientFlag { + if data.role.ClientFlag { extUsage = extUsage | clientExtKeyUsage } - if role.CodeSigningFlag { + if data.role.CodeSigningFlag { extUsage = extUsage | codeSigningExtKeyUsage } - if role.EmailProtectionFlag { + if data.role.EmailProtectionFlag { extUsage = extUsage | emailProtectionExtKeyUsage } } - creationBundle := &creationBundle{ - CommonName: cn, - OU: ou, - Organization: organization, + data.params = &creationParameters{ + Subject: subject, DNSNames: dnsNames, EmailAddresses: emailAddresses, IPAddresses: ipAddresses, - KeyType: role.KeyType, - KeyBits: role.KeyBits, - SigningBundle: signingBundle, + OtherSANs: otherSANs, + KeyType: data.role.KeyType, + KeyBits: data.role.KeyBits, NotAfter: notAfter, - KeyUsage: x509.KeyUsage(parseKeyUsages(role.KeyUsage)), + KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)), ExtKeyUsage: extUsage, } // Don't deal with URLs or max path length if it's self-signed, as these // normally come from the signing bundle - if signingBundle == nil { - return creationBundle, nil + if data.signingBundle == nil { + return nil } // This will have been read in from the getURLs function - creationBundle.URLs = signingBundle.URLs + data.params.URLs = data.signingBundle.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 role.MaxPathLength != nil { - creationBundle.MaxPathLength = *role.MaxPathLength + if data.role.MaxPathLength != nil { + data.params.MaxPathLength = *data.role.MaxPathLength } else { switch { - case signingBundle.Certificate.MaxPathLen < 0: - creationBundle.MaxPathLength = -1 - case signingBundle.Certificate.MaxPathLen == 0 && - signingBundle.Certificate.MaxPathLenZero: + case data.signingBundle.Certificate.MaxPathLen < 0: + data.params.MaxPathLength = -1 + case data.signingBundle.Certificate.MaxPathLen == 0 && + data.signingBundle.Certificate.MaxPathLenZero: // The signing function will ensure that we do not issue a CA cert - creationBundle.MaxPathLength = 0 + data.params.MaxPathLength = 0 default: // If this takes it to zero, we handle this case later if // necessary - creationBundle.MaxPathLength = signingBundle.Certificate.MaxPathLen - 1 + data.params.MaxPathLength = data.signingBundle.Certificate.MaxPathLen - 1 } } - return creationBundle, nil + return nil } // addKeyUsages adds approrpiate key usages to the template given the creation // information -func addKeyUsages(creationInfo *creationBundle, certTemplate *x509.Certificate) { - if creationInfo.IsCA { +func addKeyUsages(data *dataBundle, certTemplate *x509.Certificate) { + if data.params.IsCA { certTemplate.KeyUsage = x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign) return } - certTemplate.KeyUsage = creationInfo.KeyUsage + certTemplate.KeyUsage = data.params.KeyUsage - if creationInfo.ExtKeyUsage&serverExtKeyUsage != 0 { + if data.params.ExtKeyUsage&serverExtKeyUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } - if creationInfo.ExtKeyUsage&clientExtKeyUsage != 0 { + if data.params.ExtKeyUsage&clientExtKeyUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } - if creationInfo.ExtKeyUsage&codeSigningExtKeyUsage != 0 { + if data.params.ExtKeyUsage&codeSigningExtKeyUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) } - if creationInfo.ExtKeyUsage&emailProtectionExtKeyUsage != 0 { + if data.params.ExtKeyUsage&emailProtectionExtKeyUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) } } // Performs the heavy lifting of creating a certificate. Returns // a fully-filled-in ParsedCertBundle. -func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle, error) { +func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) { var err error result := &certutil.ParsedCertBundle{} @@ -905,8 +995,8 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle return nil, err } - if err := certutil.GeneratePrivateKey(creationInfo.KeyType, - creationInfo.KeyBits, + if err := certutil.GeneratePrivateKey(data.params.KeyType, + data.params.KeyBits, result); err != nil { return nil, err } @@ -916,51 +1006,49 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle return nil, errutil.InternalError{Err: fmt.Sprintf("error getting subject key ID: %s", err)} } - subject := pkix.Name{ - CommonName: creationInfo.CommonName, - OrganizationalUnit: creationInfo.OU, - Organization: creationInfo.Organization, - } - certTemplate := &x509.Certificate{ SerialNumber: serialNumber, - Subject: subject, NotBefore: time.Now().Add(-30 * time.Second), - NotAfter: creationInfo.NotAfter, + NotAfter: data.params.NotAfter, IsCA: false, SubjectKeyId: subjKeyID, - DNSNames: creationInfo.DNSNames, - EmailAddresses: creationInfo.EmailAddresses, - IPAddresses: creationInfo.IPAddresses, + Subject: data.params.Subject, + DNSNames: data.params.DNSNames, + EmailAddresses: data.params.EmailAddresses, + IPAddresses: data.params.IPAddresses, + } + + 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 creationInfo.SigningBundle == nil { + if data.signingBundle == nil { certTemplate.IsCA = true } // This will only be filled in from the generation paths - if len(creationInfo.PermittedDNSDomains) > 0 { - certTemplate.PermittedDNSDomains = creationInfo.PermittedDNSDomains + if len(data.params.PermittedDNSDomains) > 0 { + certTemplate.PermittedDNSDomains = data.params.PermittedDNSDomains certTemplate.PermittedDNSDomainsCritical = true } - addKeyUsages(creationInfo, certTemplate) + addKeyUsages(data, certTemplate) - certTemplate.IssuingCertificateURL = creationInfo.URLs.IssuingCertificates - certTemplate.CRLDistributionPoints = creationInfo.URLs.CRLDistributionPoints - certTemplate.OCSPServer = creationInfo.URLs.OCSPServers + certTemplate.IssuingCertificateURL = data.params.URLs.IssuingCertificates + certTemplate.CRLDistributionPoints = data.params.URLs.CRLDistributionPoints + certTemplate.OCSPServer = data.params.URLs.OCSPServers var certBytes []byte - if creationInfo.SigningBundle != nil { - switch creationInfo.SigningBundle.PrivateKeyType { + if data.signingBundle != nil { + switch data.signingBundle.PrivateKeyType { case certutil.RSAPrivateKey: certTemplate.SignatureAlgorithm = x509.SHA256WithRSA case certutil.ECPrivateKey: certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 } - caCert := creationInfo.SigningBundle.Certificate + caCert := data.signingBundle.Certificate certTemplate.AuthorityKeyId = caCert.SubjectKeyId err = checkPermittedDNSDomains(certTemplate, caCert) @@ -968,17 +1056,17 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle return nil, errutil.UserError{Err: err.Error()} } - certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, result.PrivateKey.Public(), creationInfo.SigningBundle.PrivateKey) + certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, result.PrivateKey.Public(), data.signingBundle.PrivateKey) } else { // Creating a self-signed root - if creationInfo.MaxPathLength == 0 { + if data.params.MaxPathLength == 0 { certTemplate.MaxPathLen = 0 certTemplate.MaxPathLenZero = true } else { - certTemplate.MaxPathLen = creationInfo.MaxPathLength + certTemplate.MaxPathLen = data.params.MaxPathLength } - switch creationInfo.KeyType { + switch data.params.KeyType { case "rsa": certTemplate.SignatureAlgorithm = x509.SHA256WithRSA case "ec": @@ -1000,17 +1088,17 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)} } - if creationInfo.SigningBundle != nil { - if len(creationInfo.SigningBundle.Certificate.AuthorityKeyId) > 0 && - !bytes.Equal(creationInfo.SigningBundle.Certificate.AuthorityKeyId, creationInfo.SigningBundle.Certificate.SubjectKeyId) { + 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: creationInfo.SigningBundle.Certificate, - Bytes: creationInfo.SigningBundle.CertificateBytes, + Certificate: data.signingBundle.Certificate, + Bytes: data.signingBundle.CertificateBytes, }, } - result.CAChain = append(result.CAChain, creationInfo.SigningBundle.CAChain...) + result.CAChain = append(result.CAChain, data.signingBundle.CAChain...) } } @@ -1019,29 +1107,29 @@ func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle // Creates a CSR. This is currently only meant for use when // generating an intermediate certificate. -func createCSR(creationInfo *creationBundle) (*certutil.ParsedCSRBundle, error) { +func createCSR(data *dataBundle) (*certutil.ParsedCSRBundle, error) { var err error result := &certutil.ParsedCSRBundle{} - if err := certutil.GeneratePrivateKey(creationInfo.KeyType, - creationInfo.KeyBits, + if err := certutil.GeneratePrivateKey(data.params.KeyType, + data.params.KeyBits, result); err != nil { return nil, err } // Like many root CAs, other information is ignored - subject := pkix.Name{ - CommonName: creationInfo.CommonName, + csrTemplate := &x509.CertificateRequest{ + Subject: data.params.Subject, + DNSNames: data.params.DNSNames, + EmailAddresses: data.params.EmailAddresses, + IPAddresses: data.params.IPAddresses, } - csrTemplate := &x509.CertificateRequest{ - Subject: subject, - DNSNames: creationInfo.DNSNames, - EmailAddresses: creationInfo.EmailAddresses, - IPAddresses: creationInfo.IPAddresses, + if err := handleOtherCSRSANs(csrTemplate, data.params.OtherSANs); err != nil { + return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()} } - switch creationInfo.KeyType { + switch data.params.KeyType { case "rsa": csrTemplate.SignatureAlgorithm = x509.SHA256WithRSA case "ec": @@ -1064,18 +1152,19 @@ func createCSR(creationInfo *creationBundle) (*certutil.ParsedCSRBundle, error) // Performs the heavy lifting of generating a certificate from a CSR. // Returns a ParsedCertBundle sans private keys. -func signCertificate(creationInfo *creationBundle, - csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, error) { +func signCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) { switch { - case creationInfo == nil: - return nil, errutil.UserError{Err: "nil creation info given to signCertificate"} - case creationInfo.SigningBundle == nil: + 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 csr == nil: + case data.csr == nil: return nil, errutil.UserError{Err: "nil csr given to signCertificate"} } - err := csr.CheckSignature() + err := data.csr.CheckSignature() if err != nil { return nil, errutil.UserError{Err: "request signature invalid"} } @@ -1087,75 +1176,73 @@ func signCertificate(creationInfo *creationBundle, return nil, err } - marshaledKey, err := x509.MarshalPKIXPublicKey(csr.PublicKey) + 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 := creationInfo.SigningBundle.Certificate - - subject := pkix.Name{ - CommonName: creationInfo.CommonName, - OrganizationalUnit: creationInfo.OU, - Organization: creationInfo.Organization, - } + caCert := data.signingBundle.Certificate certTemplate := &x509.Certificate{ SerialNumber: serialNumber, - Subject: subject, + Subject: data.params.Subject, NotBefore: time.Now().Add(-30 * time.Second), - NotAfter: creationInfo.NotAfter, + NotAfter: data.params.NotAfter, SubjectKeyId: subjKeyID[:], AuthorityKeyId: caCert.SubjectKeyId, } - switch creationInfo.SigningBundle.PrivateKeyType { + switch data.signingBundle.PrivateKeyType { case certutil.RSAPrivateKey: certTemplate.SignatureAlgorithm = x509.SHA256WithRSA case certutil.ECPrivateKey: certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 } - if creationInfo.UseCSRValues { - certTemplate.Subject = csr.Subject + if data.params.UseCSRValues { + certTemplate.Subject = data.csr.Subject - certTemplate.DNSNames = csr.DNSNames - certTemplate.EmailAddresses = csr.EmailAddresses - certTemplate.IPAddresses = csr.IPAddresses + certTemplate.DNSNames = data.csr.DNSNames + certTemplate.EmailAddresses = data.csr.EmailAddresses + certTemplate.IPAddresses = data.csr.IPAddresses - certTemplate.ExtraExtensions = csr.Extensions + certTemplate.ExtraExtensions = data.csr.Extensions } else { - certTemplate.DNSNames = creationInfo.DNSNames - certTemplate.EmailAddresses = creationInfo.EmailAddresses - certTemplate.IPAddresses = creationInfo.IPAddresses + certTemplate.DNSNames = data.params.DNSNames + certTemplate.EmailAddresses = data.params.EmailAddresses + certTemplate.IPAddresses = data.params.IPAddresses } - addKeyUsages(creationInfo, certTemplate) + if err := handleOtherSANs(certTemplate, data.params.OtherSANs); err != nil { + return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()} + } + + addKeyUsages(data, certTemplate) var certBytes []byte - certTemplate.IssuingCertificateURL = creationInfo.URLs.IssuingCertificates - certTemplate.CRLDistributionPoints = creationInfo.URLs.CRLDistributionPoints - certTemplate.OCSPServer = creationInfo.SigningBundle.URLs.OCSPServers + certTemplate.IssuingCertificateURL = data.params.URLs.IssuingCertificates + certTemplate.CRLDistributionPoints = data.params.URLs.CRLDistributionPoints + certTemplate.OCSPServer = data.signingBundle.URLs.OCSPServers - if creationInfo.IsCA { + if data.params.IsCA { certTemplate.BasicConstraintsValid = true certTemplate.IsCA = true - if creationInfo.SigningBundle.Certificate.MaxPathLen == 0 && - creationInfo.SigningBundle.Certificate.MaxPathLenZero { + 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 = creationInfo.MaxPathLength + certTemplate.MaxPathLen = data.params.MaxPathLength if certTemplate.MaxPathLen == 0 { certTemplate.MaxPathLenZero = true } } - if len(creationInfo.PermittedDNSDomains) > 0 { - certTemplate.PermittedDNSDomains = creationInfo.PermittedDNSDomains + if len(data.params.PermittedDNSDomains) > 0 { + certTemplate.PermittedDNSDomains = data.params.PermittedDNSDomains certTemplate.PermittedDNSDomainsCritical = true } err = checkPermittedDNSDomains(certTemplate, caCert) @@ -1163,7 +1250,7 @@ func signCertificate(creationInfo *creationBundle, return nil, errutil.UserError{Err: err.Error()} } - certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, csr.PublicKey, creationInfo.SigningBundle.PrivateKey) + 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)} @@ -1175,7 +1262,7 @@ func signCertificate(creationInfo *creationBundle, return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)} } - result.CAChain = creationInfo.SigningBundle.GetCAChain() + result.CAChain = data.signingBundle.GetCAChain() return result, nil } @@ -1278,3 +1365,116 @@ 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, + } + 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)...) + + // 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, 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) []asn1.RawValue { + var rawValues []asn1.RawValue + for _, name := range dnsNames { + rawValues = append(rawValues, asn1.RawValue{Tag: 2, Class: 2, Bytes: []byte(name)}) + } + for _, email := range emailAddresses { + rawValues = append(rawValues, asn1.RawValue{Tag: 1, 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: 7, Class: 2, Bytes: ip}) + } + 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/fields.go b/builtin/logical/pki/fields.go index 9aa1d4b556db..a236a1d05389 100644 --- a/builtin/logical/pki/fields.go +++ b/builtin/logical/pki/fields.go @@ -39,6 +39,12 @@ pkcs8 instead. Defaults to "der".`, comma-delimited list`, } + fields["other_sans"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Requested other SANs, in an array with the format +;UTF8: for each entry.`, + } + return fields } @@ -114,6 +120,48 @@ a CA cert or signing a CA cert, not when generating a CSR for an intermediate CA.`, } + fields["ou"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, OU (OrganizationalUnit) will be set to +this value.`, + } + + fields["organization"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, O (Organization) will be set to +this value.`, + } + + fields["country"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Country will be set to +this value.`, + } + + fields["locality"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Locality will be set to +this value.`, + } + + fields["province"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Province will be set to +this value.`, + } + + fields["street_address"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Street Address will be set to +this value.`, + } + + fields["postal_code"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Postal Code will be set to +this value.`, + } + return fields } diff --git a/builtin/logical/pki/path_intermediate.go b/builtin/logical/pki/path_intermediate.go index 3ff1cbb4e6c0..9eb98a06bbb0 100644 --- a/builtin/logical/pki/path_intermediate.go +++ b/builtin/logical/pki/path_intermediate.go @@ -63,7 +63,12 @@ func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Req } var resp *logical.Response - parsedBundle, err := generateIntermediateCSR(b, role, nil, req, data) + input := &dataBundle{ + role: role, + req: req, + apiData: data, + } + parsedBundle, err := generateIntermediateCSR(b, input) if err != nil { switch err.(type) { case errutil.UserError: diff --git a/builtin/logical/pki/path_issue_sign.go b/builtin/logical/pki/path_issue_sign.go index a9daa35a12e2..df249c847ec6 100644 --- a/builtin/logical/pki/path_issue_sign.go +++ b/builtin/logical/pki/path_issue_sign.go @@ -175,12 +175,18 @@ 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, + } var parsedBundle *certutil.ParsedCertBundle var err error if useCSR { - parsedBundle, err = signCert(b, role, signingBundle, false, useCSRValues, req, data) + parsedBundle, err = signCert(b, input, false, useCSRValues) } else { - parsedBundle, err = generateCert(ctx, b, role, signingBundle, false, req, data) + parsedBundle, err = generateCert(ctx, b, input, false) } if err != nil { switch err.(type) { diff --git a/builtin/logical/pki/path_roles.go b/builtin/logical/pki/path_roles.go index d3a32e9983d6..be33be9298c6 100644 --- a/builtin/logical/pki/path_roles.go +++ b/builtin/logical/pki/path_roles.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/logical" @@ -114,6 +115,11 @@ CN and SANs. Defaults to true.`, Any valid IP is accepted.`, }, + "allowed_other_sans": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, an array of allowed other names to put in SANs. These values support globbing.`, + }, + "server_flag": &framework.FieldSchema{ Type: framework.TypeBool, Default: true, @@ -187,13 +193,43 @@ include the Common Name (cn). Defaults to true.`, "ou": &framework.FieldSchema{ Type: framework.TypeCommaStringSlice, - Description: `If set, the OU (OrganizationalUnit) will be set to + Description: `If set, OU (OrganizationalUnit) will be set to this value in certificates issued by this role.`, }, "organization": &framework.FieldSchema{ Type: framework.TypeCommaStringSlice, - Description: `If set, the O (Organization) will be set to + Description: `If set, O (Organization) will be set to +this value in certificates issued by this role.`, + }, + + "country": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Country will be set to +this value in certificates issued by this role.`, + }, + + "locality": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Locality will be set to +this value in certificates issued by this role.`, + }, + + "province": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Province will be set to +this value in certificates issued by this role.`, + }, + + "street_address": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Street Address will be set to +this value in certificates issued by this role.`, + }, + + "postal_code": &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `If set, Postal Code will be set to this value in certificates issued by this role.`, }, @@ -414,11 +450,25 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data KeyUsage: data.Get("key_usage").([]string), OU: data.Get("ou").([]string), Organization: data.Get("organization").([]string), + Country: data.Get("country").([]string), + Locality: data.Get("locality").([]string), + Province: data.Get("province").([]string), + StreetAddress: data.Get("street_address").([]string), + PostalCode: data.Get("postal_code").([]string), GenerateLease: new(bool), NoStore: data.Get("no_store").(bool), RequireCN: data.Get("require_cn").(bool), } + otherSANs := data.Get("allowed_other_sans").([]string) + if len(otherSANs) > 0 { + _, err := parseOtherSANs(otherSANs) + if err != nil { + return logical.ErrorResponse(errwrap.Wrapf("error parsing allowed_other_sans: {{err}}", err).Error()), nil + } + entry.AllowedOtherSANs = otherSANs + } + // no_store implies generate_lease := false if entry.NoStore { *entry.GenerateLease = false @@ -545,9 +595,15 @@ type roleEntry struct { OU []string `json:"ou_list" mapstructure:"ou"` OrganizationOld string `json:"organization,omitempty"` Organization []string `json:"organization_list" mapstructure:"organization"` + Country []string `json:"country" mapstructure:"country"` + Locality []string `json:"locality" mapstructure:"locality"` + Province []string `json:"province" mapstructure:"province"` + StreetAddress []string `json:"street_address" mapstructure:"street_address"` + PostalCode []string `json:"postal_code" mapstructure:"postal_code"` GenerateLease *bool `json:"generate_lease,omitempty"` NoStore bool `json:"no_store" mapstructure:"no_store"` RequireCN bool `json:"require_cn" mapstructure:"require_cn"` + AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"` // Used internally for signing intermediates AllowExpirationPastCA bool @@ -577,7 +633,13 @@ func (r *roleEntry) ToResponseData() map[string]interface{} { "key_usage": r.KeyUsage, "ou": r.OU, "organization": r.Organization, + "country": r.Country, + "locality": r.Locality, + "province": r.Province, + "street_address": r.StreetAddress, + "postal_code": r.PostalCode, "no_store": r.NoStore, + "allowed_other_sans": r.AllowedOtherSANs, } if r.MaxPathLength != nil { responseData["max_path_length"] = r.MaxPathLength diff --git a/builtin/logical/pki/path_root.go b/builtin/logical/pki/path_root.go index b271764c07e7..ce89fd2bfe8f 100644 --- a/builtin/logical/pki/path_root.go +++ b/builtin/logical/pki/path_root.go @@ -136,7 +136,12 @@ func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request, role.MaxPathLength = &maxPathLength } - parsedBundle, err := generateCert(ctx, b, role, nil, true, req, data) + input := &dataBundle{ + req: req, + apiData: data, + role: role, + } + parsedBundle, err := generateCert(ctx, b, input, true) if err != nil { switch err.(type) { case errutil.UserError: @@ -247,6 +252,13 @@ func (b *backend) pathCASignIntermediate(ctx context.Context, req *logical.Reque } role := &roleEntry{ + OU: data.Get("ou").([]string), + Organization: data.Get("organization").([]string), + Country: data.Get("country").([]string), + Locality: data.Get("locality").([]string), + Province: data.Get("province").([]string), + StreetAddress: data.Get("street_address").([]string), + PostalCode: data.Get("postal_code").([]string), TTL: (time.Duration(data.Get("ttl").(int)) * time.Second).String(), AllowLocalhost: true, AllowAnyName: true, @@ -279,7 +291,13 @@ func (b *backend) pathCASignIntermediate(ctx context.Context, req *logical.Reque role.MaxPathLength = &maxPathLength } - parsedBundle, err := signCert(b, role, signingBundle, true, useCSRValues, req, data) + input := &dataBundle{ + req: req, + apiData: data, + signingBundle: signingBundle, + role: role, + } + parsedBundle, err := signCert(b, input, true, useCSRValues) if err != nil { switch err.(type) { case errutil.UserError: diff --git a/helper/parseutil/parseutil.go b/helper/parseutil/parseutil.go index ad5a96f641ae..464b50899cf9 100644 --- a/helper/parseutil/parseutil.go +++ b/helper/parseutil/parseutil.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/hashicorp/vault/helper/strutil" "github.com/mitchellh/mapstructure" ) @@ -100,3 +101,20 @@ func ParseBool(in interface{}) (bool, error) { } return result, nil } + +func ParseCommaStringSlice(in interface{}) ([]string, error) { + var result []string + config := &mapstructure.DecoderConfig{ + Result: &result, + WeaklyTypedInput: true, + DecodeHook: mapstructure.StringToSliceHookFunc(","), + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + if err := decoder.Decode(in); err != nil { + return nil, err + } + return strutil.TrimStrings(result), nil +} diff --git a/logical/framework/field_data.go b/logical/framework/field_data.go index 1d03c335e8d6..ac28a545220a 100644 --- a/logical/framework/field_data.go +++ b/logical/framework/field_data.go @@ -224,20 +224,11 @@ func (d *FieldData) getPrimitive( return strutil.TrimStrings(result), true, nil case TypeCommaStringSlice: - var result []string - config := &mapstructure.DecoderConfig{ - Result: &result, - WeaklyTypedInput: true, - DecodeHook: mapstructure.StringToSliceHookFunc(","), - } - decoder, err := mapstructure.NewDecoder(config) + res, err := parseutil.ParseCommaStringSlice(raw) if err != nil { return nil, false, err } - if err := decoder.Decode(raw); err != nil { - return nil, false, err - } - return strutil.TrimStrings(result), true, nil + return res, true, nil case TypeKVPairs: // First try to parse this as a map diff --git a/vendor/golang.org/x/crypto/cryptobyte/asn1.go b/vendor/golang.org/x/crypto/cryptobyte/asn1.go new file mode 100644 index 000000000000..88ec8b4fbf85 --- /dev/null +++ b/vendor/golang.org/x/crypto/cryptobyte/asn1.go @@ -0,0 +1,732 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cryptobyte + +import ( + encoding_asn1 "encoding/asn1" + "fmt" + "math/big" + "reflect" + "time" + + "golang.org/x/crypto/cryptobyte/asn1" +) + +// This file contains ASN.1-related methods for String and Builder. + +// Builder + +// AddASN1Int64 appends a DER-encoded ASN.1 INTEGER. +func (b *Builder) AddASN1Int64(v int64) { + b.addASN1Signed(asn1.INTEGER, v) +} + +// AddASN1Enum appends a DER-encoded ASN.1 ENUMERATION. +func (b *Builder) AddASN1Enum(v int64) { + b.addASN1Signed(asn1.ENUM, v) +} + +func (b *Builder) addASN1Signed(tag asn1.Tag, v int64) { + b.AddASN1(tag, func(c *Builder) { + length := 1 + for i := v; i >= 0x80 || i < -0x80; i >>= 8 { + length++ + } + + for ; length > 0; length-- { + i := v >> uint((length-1)*8) & 0xff + c.AddUint8(uint8(i)) + } + }) +} + +// AddASN1Uint64 appends a DER-encoded ASN.1 INTEGER. +func (b *Builder) AddASN1Uint64(v uint64) { + b.AddASN1(asn1.INTEGER, func(c *Builder) { + length := 1 + for i := v; i >= 0x80; i >>= 8 { + length++ + } + + for ; length > 0; length-- { + i := v >> uint((length-1)*8) & 0xff + c.AddUint8(uint8(i)) + } + }) +} + +// AddASN1BigInt appends a DER-encoded ASN.1 INTEGER. +func (b *Builder) AddASN1BigInt(n *big.Int) { + if b.err != nil { + return + } + + b.AddASN1(asn1.INTEGER, func(c *Builder) { + if n.Sign() < 0 { + // A negative number has to be converted to two's-complement form. So we + // invert and subtract 1. If the most-significant-bit isn't set then + // we'll need to pad the beginning with 0xff in order to keep the number + // negative. + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bytes := nMinus1.Bytes() + for i := range bytes { + bytes[i] ^= 0xff + } + if bytes[0]&0x80 == 0 { + c.add(0xff) + } + c.add(bytes...) + } else if n.Sign() == 0 { + c.add(0) + } else { + bytes := n.Bytes() + if bytes[0]&0x80 != 0 { + c.add(0) + } + c.add(bytes...) + } + }) +} + +// AddASN1OctetString appends a DER-encoded ASN.1 OCTET STRING. +func (b *Builder) AddASN1OctetString(bytes []byte) { + b.AddASN1(asn1.OCTET_STRING, func(c *Builder) { + c.AddBytes(bytes) + }) +} + +const generalizedTimeFormatStr = "20060102150405Z0700" + +// AddASN1GeneralizedTime appends a DER-encoded ASN.1 GENERALIZEDTIME. +func (b *Builder) AddASN1GeneralizedTime(t time.Time) { + if t.Year() < 0 || t.Year() > 9999 { + b.err = fmt.Errorf("cryptobyte: cannot represent %v as a GeneralizedTime", t) + return + } + b.AddASN1(asn1.GeneralizedTime, func(c *Builder) { + c.AddBytes([]byte(t.Format(generalizedTimeFormatStr))) + }) +} + +// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not +// support BIT STRINGs that are not a whole number of bytes. +func (b *Builder) AddASN1BitString(data []byte) { + b.AddASN1(asn1.BIT_STRING, func(b *Builder) { + b.AddUint8(0) + b.AddBytes(data) + }) +} + +func (b *Builder) addBase128Int(n int64) { + var length int + if n == 0 { + length = 1 + } else { + for i := n; i > 0; i >>= 7 { + length++ + } + } + + for i := length - 1; i >= 0; i-- { + o := byte(n >> uint(i*7)) + o &= 0x7f + if i != 0 { + o |= 0x80 + } + + b.add(o) + } +} + +func isValidOID(oid encoding_asn1.ObjectIdentifier) bool { + if len(oid) < 2 { + return false + } + + if oid[0] > 2 || (oid[0] <= 1 && oid[1] >= 40) { + return false + } + + for _, v := range oid { + if v < 0 { + return false + } + } + + return true +} + +func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) { + b.AddASN1(asn1.OBJECT_IDENTIFIER, func(b *Builder) { + if !isValidOID(oid) { + b.err = fmt.Errorf("cryptobyte: invalid OID: %v", oid) + return + } + + b.addBase128Int(int64(oid[0])*40 + int64(oid[1])) + for _, v := range oid[2:] { + b.addBase128Int(int64(v)) + } + }) +} + +func (b *Builder) AddASN1Boolean(v bool) { + b.AddASN1(asn1.BOOLEAN, func(b *Builder) { + if v { + b.AddUint8(0xff) + } else { + b.AddUint8(0) + } + }) +} + +func (b *Builder) AddASN1NULL() { + b.add(uint8(asn1.NULL), 0) +} + +// MarshalASN1 calls encoding_asn1.Marshal on its input and appends the result if +// successful or records an error if one occurred. +func (b *Builder) MarshalASN1(v interface{}) { + // NOTE(martinkr): This is somewhat of a hack to allow propagation of + // encoding_asn1.Marshal errors into Builder.err. N.B. if you call MarshalASN1 with a + // value embedded into a struct, its tag information is lost. + if b.err != nil { + return + } + bytes, err := encoding_asn1.Marshal(v) + if err != nil { + b.err = err + return + } + b.AddBytes(bytes) +} + +// AddASN1 appends an ASN.1 object. The object is prefixed with the given tag. +// Tags greater than 30 are not supported and result in an error (i.e. +// low-tag-number form only). The child builder passed to the +// BuilderContinuation can be used to build the content of the ASN.1 object. +func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) { + if b.err != nil { + return + } + // Identifiers with the low five bits set indicate high-tag-number format + // (two or more octets), which we don't support. + if tag&0x1f == 0x1f { + b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag) + return + } + b.AddUint8(uint8(tag)) + b.addLengthPrefixed(1, true, f) +} + +// String + +func (s *String) ReadASN1Boolean(out *bool) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.INTEGER) || len(bytes) != 1 { + return false + } + + switch bytes[0] { + case 0: + *out = false + case 0xff: + *out = true + default: + return false + } + + return true +} + +var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem() + +// ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does +// not point to an integer or to a big.Int, it panics. It returns true on +// success and false on error. +func (s *String) ReadASN1Integer(out interface{}) bool { + if reflect.TypeOf(out).Kind() != reflect.Ptr { + panic("out is not a pointer") + } + switch reflect.ValueOf(out).Elem().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var i int64 + if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) { + return false + } + reflect.ValueOf(out).Elem().SetInt(i) + return true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var u uint64 + if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) { + return false + } + reflect.ValueOf(out).Elem().SetUint(u) + return true + case reflect.Struct: + if reflect.TypeOf(out).Elem() == bigIntType { + return s.readASN1BigInt(out.(*big.Int)) + } + } + panic("out does not point to an integer type") +} + +func checkASN1Integer(bytes []byte) bool { + if len(bytes) == 0 { + // An INTEGER is encoded with at least one octet. + return false + } + if len(bytes) == 1 { + return true + } + if bytes[0] == 0 && bytes[1]&0x80 == 0 || bytes[0] == 0xff && bytes[1]&0x80 == 0x80 { + // Value is not minimally encoded. + return false + } + return true +} + +var bigOne = big.NewInt(1) + +func (s *String) readASN1BigInt(out *big.Int) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) { + return false + } + if bytes[0]&0x80 == 0x80 { + // Negative number. + neg := make([]byte, len(bytes)) + for i, b := range bytes { + neg[i] = ^b + } + out.SetBytes(neg) + out.Add(out, bigOne) + out.Neg(out) + } else { + out.SetBytes(bytes) + } + return true +} + +func (s *String) readASN1Int64(out *int64) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) { + return false + } + return true +} + +func asn1Signed(out *int64, n []byte) bool { + length := len(n) + if length > 8 { + return false + } + for i := 0; i < length; i++ { + *out <<= 8 + *out |= int64(n[i]) + } + // Shift up and down in order to sign extend the result. + *out <<= 64 - uint8(length)*8 + *out >>= 64 - uint8(length)*8 + return true +} + +func (s *String) readASN1Uint64(out *uint64) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Unsigned(out, bytes) { + return false + } + return true +} + +func asn1Unsigned(out *uint64, n []byte) bool { + length := len(n) + if length > 9 || length == 9 && n[0] != 0 { + // Too large for uint64. + return false + } + if n[0]&0x80 != 0 { + // Negative number. + return false + } + for i := 0; i < length; i++ { + *out <<= 8 + *out |= uint64(n[i]) + } + return true +} + +// ReadASN1Enum decodes an ASN.1 ENUMERATION into out and advances. It returns +// true on success and false on error. +func (s *String) ReadASN1Enum(out *int) bool { + var bytes String + var i int64 + if !s.ReadASN1(&bytes, asn1.ENUM) || !checkASN1Integer(bytes) || !asn1Signed(&i, bytes) { + return false + } + if int64(int(i)) != i { + return false + } + *out = int(i) + return true +} + +func (s *String) readBase128Int(out *int) bool { + ret := 0 + for i := 0; len(*s) > 0; i++ { + if i == 4 { + return false + } + ret <<= 7 + b := s.read(1)[0] + ret |= int(b & 0x7f) + if b&0x80 == 0 { + *out = ret + return true + } + } + return false // truncated +} + +// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and +// advances. It returns true on success and false on error. +func (s *String) ReadASN1ObjectIdentifier(out *encoding_asn1.ObjectIdentifier) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 { + return false + } + + // In the worst case, we get two elements from the first byte (which is + // encoded differently) and then every varint is a single byte long. + components := make([]int, len(bytes)+1) + + // The first varint is 40*value1 + value2: + // According to this packing, value1 can take the values 0, 1 and 2 only. + // When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2, + // then there are no restrictions on value2. + var v int + if !bytes.readBase128Int(&v) { + return false + } + if v < 80 { + components[0] = v / 40 + components[1] = v % 40 + } else { + components[0] = 2 + components[1] = v - 80 + } + + i := 2 + for ; len(bytes) > 0; i++ { + if !bytes.readBase128Int(&v) { + return false + } + components[i] = v + } + *out = components[:i] + return true +} + +// ReadASN1GeneralizedTime decodes an ASN.1 GENERALIZEDTIME into out and +// advances. It returns true on success and false on error. +func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.GeneralizedTime) { + return false + } + t := string(bytes) + res, err := time.Parse(generalizedTimeFormatStr, t) + if err != nil { + return false + } + if serialized := res.Format(generalizedTimeFormatStr); serialized != t { + return false + } + *out = res + return true +} + +// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances. It +// returns true on success and false on error. +func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 { + return false + } + + paddingBits := uint8(bytes[0]) + bytes = bytes[1:] + if paddingBits > 7 || + len(bytes) == 0 && paddingBits != 0 || + len(bytes) > 0 && bytes[len(bytes)-1]&(1< 4 || len(*s) < int(2+lenLen) { + return false + } + + lenBytes := String((*s)[2 : 2+lenLen]) + if !lenBytes.readUnsigned(&len32, int(lenLen)) { + return false + } + + // ITU-T X.690 section 10.1 (DER length forms) requires encoding the length + // with the minimum number of octets. + if len32 < 128 { + // Length should have used short-form encoding. + return false + } + if len32>>((lenLen-1)*8) == 0 { + // Leading octet is 0. Length should have been at least one byte shorter. + return false + } + + headerLen = 2 + uint32(lenLen) + if headerLen+len32 < len32 { + // Overflow. + return false + } + length = headerLen + len32 + } + + if uint32(int(length)) != length || !s.ReadBytes((*[]byte)(out), int(length)) { + return false + } + if skipHeader && !out.Skip(int(headerLen)) { + panic("cryptobyte: internal error") + } + + return true +} diff --git a/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go b/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go new file mode 100644 index 000000000000..cda8e3edfd5e --- /dev/null +++ b/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go @@ -0,0 +1,46 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package asn1 contains supporting types for parsing and building ASN.1 +// messages with the cryptobyte package. +package asn1 // import "golang.org/x/crypto/cryptobyte/asn1" + +// Tag represents an ASN.1 identifier octet, consisting of a tag number +// (indicating a type) and class (such as context-specific or constructed). +// +// Methods in the cryptobyte package only support the low-tag-number form, i.e. +// a single identifier octet with bits 7-8 encoding the class and bits 1-6 +// encoding the tag number. +type Tag uint8 + +const ( + classConstructed = 0x20 + classContextSpecific = 0x80 +) + +// Constructed returns t with the constructed class bit set. +func (t Tag) Constructed() Tag { return t | classConstructed } + +// ContextSpecific returns t with the context-specific class bit set. +func (t Tag) ContextSpecific() Tag { return t | classContextSpecific } + +// The following is a list of standard tag and class combinations. +const ( + BOOLEAN = Tag(1) + INTEGER = Tag(2) + BIT_STRING = Tag(3) + OCTET_STRING = Tag(4) + NULL = Tag(5) + OBJECT_IDENTIFIER = Tag(6) + ENUM = Tag(10) + UTF8String = Tag(12) + SEQUENCE = Tag(16 | classConstructed) + SET = Tag(17 | classConstructed) + PrintableString = Tag(19) + T61String = Tag(20) + IA5String = Tag(22) + UTCTime = Tag(23) + GeneralizedTime = Tag(24) + GeneralString = Tag(27) +) diff --git a/vendor/golang.org/x/crypto/cryptobyte/builder.go b/vendor/golang.org/x/crypto/cryptobyte/builder.go new file mode 100644 index 000000000000..29b4c7641279 --- /dev/null +++ b/vendor/golang.org/x/crypto/cryptobyte/builder.go @@ -0,0 +1,309 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cryptobyte + +import ( + "errors" + "fmt" +) + +// A Builder builds byte strings from fixed-length and length-prefixed values. +// Builders either allocate space as needed, or are ‘fixed’, which means that +// they write into a given buffer and produce an error if it's exhausted. +// +// The zero value is a usable Builder that allocates space as needed. +// +// Simple values are marshaled and appended to a Builder using methods on the +// Builder. Length-prefixed values are marshaled by providing a +// BuilderContinuation, which is a function that writes the inner contents of +// the value to a given Builder. See the documentation for BuilderContinuation +// for details. +type Builder struct { + err error + result []byte + fixedSize bool + child *Builder + offset int + pendingLenLen int + pendingIsASN1 bool + inContinuation *bool +} + +// NewBuilder creates a Builder that appends its output to the given buffer. +// Like append(), the slice will be reallocated if its capacity is exceeded. +// Use Bytes to get the final buffer. +func NewBuilder(buffer []byte) *Builder { + return &Builder{ + result: buffer, + } +} + +// NewFixedBuilder creates a Builder that appends its output into the given +// buffer. This builder does not reallocate the output buffer. Writes that +// would exceed the buffer's capacity are treated as an error. +func NewFixedBuilder(buffer []byte) *Builder { + return &Builder{ + result: buffer, + fixedSize: true, + } +} + +// Bytes returns the bytes written by the builder or an error if one has +// occurred during during building. +func (b *Builder) Bytes() ([]byte, error) { + if b.err != nil { + return nil, b.err + } + return b.result[b.offset:], nil +} + +// BytesOrPanic returns the bytes written by the builder or panics if an error +// has occurred during building. +func (b *Builder) BytesOrPanic() []byte { + if b.err != nil { + panic(b.err) + } + return b.result[b.offset:] +} + +// AddUint8 appends an 8-bit value to the byte string. +func (b *Builder) AddUint8(v uint8) { + b.add(byte(v)) +} + +// AddUint16 appends a big-endian, 16-bit value to the byte string. +func (b *Builder) AddUint16(v uint16) { + b.add(byte(v>>8), byte(v)) +} + +// AddUint24 appends a big-endian, 24-bit value to the byte string. The highest +// byte of the 32-bit input value is silently truncated. +func (b *Builder) AddUint24(v uint32) { + b.add(byte(v>>16), byte(v>>8), byte(v)) +} + +// AddUint32 appends a big-endian, 32-bit value to the byte string. +func (b *Builder) AddUint32(v uint32) { + b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) +} + +// AddBytes appends a sequence of bytes to the byte string. +func (b *Builder) AddBytes(v []byte) { + b.add(v...) +} + +// BuilderContinuation is continuation-passing interface for building +// length-prefixed byte sequences. Builder methods for length-prefixed +// sequences (AddUint8LengthPrefixed etc) will invoke the BuilderContinuation +// supplied to them. The child builder passed to the continuation can be used +// to build the content of the length-prefixed sequence. For example: +// +// parent := cryptobyte.NewBuilder() +// parent.AddUint8LengthPrefixed(func (child *Builder) { +// child.AddUint8(42) +// child.AddUint8LengthPrefixed(func (grandchild *Builder) { +// grandchild.AddUint8(5) +// }) +// }) +// +// It is an error to write more bytes to the child than allowed by the reserved +// length prefix. After the continuation returns, the child must be considered +// invalid, i.e. users must not store any copies or references of the child +// that outlive the continuation. +// +// If the continuation panics with a value of type BuildError then the inner +// error will be returned as the error from Bytes. If the child panics +// otherwise then Bytes will repanic with the same value. +type BuilderContinuation func(child *Builder) + +// BuildError wraps an error. If a BuilderContinuation panics with this value, +// the panic will be recovered and the inner error will be returned from +// Builder.Bytes. +type BuildError struct { + Err error +} + +// AddUint8LengthPrefixed adds a 8-bit length-prefixed byte sequence. +func (b *Builder) AddUint8LengthPrefixed(f BuilderContinuation) { + b.addLengthPrefixed(1, false, f) +} + +// AddUint16LengthPrefixed adds a big-endian, 16-bit length-prefixed byte sequence. +func (b *Builder) AddUint16LengthPrefixed(f BuilderContinuation) { + b.addLengthPrefixed(2, false, f) +} + +// AddUint24LengthPrefixed adds a big-endian, 24-bit length-prefixed byte sequence. +func (b *Builder) AddUint24LengthPrefixed(f BuilderContinuation) { + b.addLengthPrefixed(3, false, f) +} + +// AddUint32LengthPrefixed adds a big-endian, 32-bit length-prefixed byte sequence. +func (b *Builder) AddUint32LengthPrefixed(f BuilderContinuation) { + b.addLengthPrefixed(4, false, f) +} + +func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) { + if !*b.inContinuation { + *b.inContinuation = true + + defer func() { + *b.inContinuation = false + + r := recover() + if r == nil { + return + } + + if buildError, ok := r.(BuildError); ok { + b.err = buildError.Err + } else { + panic(r) + } + }() + } + + f(arg) +} + +func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuation) { + // Subsequent writes can be ignored if the builder has encountered an error. + if b.err != nil { + return + } + + offset := len(b.result) + b.add(make([]byte, lenLen)...) + + if b.inContinuation == nil { + b.inContinuation = new(bool) + } + + b.child = &Builder{ + result: b.result, + fixedSize: b.fixedSize, + offset: offset, + pendingLenLen: lenLen, + pendingIsASN1: isASN1, + inContinuation: b.inContinuation, + } + + b.callContinuation(f, b.child) + b.flushChild() + if b.child != nil { + panic("cryptobyte: internal error") + } +} + +func (b *Builder) flushChild() { + if b.child == nil { + return + } + b.child.flushChild() + child := b.child + b.child = nil + + if child.err != nil { + b.err = child.err + return + } + + length := len(child.result) - child.pendingLenLen - child.offset + + if length < 0 { + panic("cryptobyte: internal error") // result unexpectedly shrunk + } + + if child.pendingIsASN1 { + // For ASN.1, we reserved a single byte for the length. If that turned out + // to be incorrect, we have to move the contents along in order to make + // space. + if child.pendingLenLen != 1 { + panic("cryptobyte: internal error") + } + var lenLen, lenByte uint8 + if int64(length) > 0xfffffffe { + b.err = errors.New("pending ASN.1 child too long") + return + } else if length > 0xffffff { + lenLen = 5 + lenByte = 0x80 | 4 + } else if length > 0xffff { + lenLen = 4 + lenByte = 0x80 | 3 + } else if length > 0xff { + lenLen = 3 + lenByte = 0x80 | 2 + } else if length > 0x7f { + lenLen = 2 + lenByte = 0x80 | 1 + } else { + lenLen = 1 + lenByte = uint8(length) + length = 0 + } + + // Insert the initial length byte, make space for successive length bytes, + // and adjust the offset. + child.result[child.offset] = lenByte + extraBytes := int(lenLen - 1) + if extraBytes != 0 { + child.add(make([]byte, extraBytes)...) + childStart := child.offset + child.pendingLenLen + copy(child.result[childStart+extraBytes:], child.result[childStart:]) + } + child.offset++ + child.pendingLenLen = extraBytes + } + + l := length + for i := child.pendingLenLen - 1; i >= 0; i-- { + child.result[child.offset+i] = uint8(l) + l >>= 8 + } + if l != 0 { + b.err = fmt.Errorf("cryptobyte: pending child length %d exceeds %d-byte length prefix", length, child.pendingLenLen) + return + } + + if !b.fixedSize { + b.result = child.result // In case child reallocated result. + } +} + +func (b *Builder) add(bytes ...byte) { + if b.err != nil { + return + } + if b.child != nil { + panic("attempted write while child is pending") + } + if len(b.result)+len(bytes) < len(bytes) { + b.err = errors.New("cryptobyte: length overflow") + } + if b.fixedSize && len(b.result)+len(bytes) > cap(b.result) { + b.err = errors.New("cryptobyte: Builder is exceeding its fixed-size buffer") + return + } + b.result = append(b.result, bytes...) +} + +// A MarshalingValue marshals itself into a Builder. +type MarshalingValue interface { + // Marshal is called by Builder.AddValue. It receives a pointer to a builder + // to marshal itself into. It may return an error that occurred during + // marshaling, such as unset or invalid values. + Marshal(b *Builder) error +} + +// AddValue calls Marshal on v, passing a pointer to the builder to append to. +// If Marshal returns an error, it is set on the Builder so that subsequent +// appends don't have an effect. +func (b *Builder) AddValue(v MarshalingValue) { + err := v.Marshal(b) + if err != nil { + b.err = err + } +} diff --git a/vendor/golang.org/x/crypto/cryptobyte/string.go b/vendor/golang.org/x/crypto/cryptobyte/string.go new file mode 100644 index 000000000000..7636fb9c8a87 --- /dev/null +++ b/vendor/golang.org/x/crypto/cryptobyte/string.go @@ -0,0 +1,167 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package cryptobyte contains types that help with parsing and constructing +// length-prefixed, binary messages, including ASN.1 DER. (The asn1 subpackage +// contains useful ASN.1 constants.) +// +// The String type is for parsing. It wraps a []byte slice and provides helper +// functions for consuming structures, value by value. +// +// The Builder type is for constructing messages. It providers helper functions +// for appending values and also for appending length-prefixed submessages – +// without having to worry about calculating the length prefix ahead of time. +// +// See the documentation and examples for the Builder and String types to get +// started. +package cryptobyte // import "golang.org/x/crypto/cryptobyte" + +// String represents a string of bytes. It provides methods for parsing +// fixed-length and length-prefixed values from it. +type String []byte + +// read advances a String by n bytes and returns them. If less than n bytes +// remain, it returns nil. +func (s *String) read(n int) []byte { + if len(*s) < n { + return nil + } + v := (*s)[:n] + *s = (*s)[n:] + return v +} + +// Skip advances the String by n byte and reports whether it was successful. +func (s *String) Skip(n int) bool { + return s.read(n) != nil +} + +// ReadUint8 decodes an 8-bit value into out and advances over it. It +// returns true on success and false on error. +func (s *String) ReadUint8(out *uint8) bool { + v := s.read(1) + if v == nil { + return false + } + *out = uint8(v[0]) + return true +} + +// ReadUint16 decodes a big-endian, 16-bit value into out and advances over it. +// It returns true on success and false on error. +func (s *String) ReadUint16(out *uint16) bool { + v := s.read(2) + if v == nil { + return false + } + *out = uint16(v[0])<<8 | uint16(v[1]) + return true +} + +// ReadUint24 decodes a big-endian, 24-bit value into out and advances over it. +// It returns true on success and false on error. +func (s *String) ReadUint24(out *uint32) bool { + v := s.read(3) + if v == nil { + return false + } + *out = uint32(v[0])<<16 | uint32(v[1])<<8 | uint32(v[2]) + return true +} + +// ReadUint32 decodes a big-endian, 32-bit value into out and advances over it. +// It returns true on success and false on error. +func (s *String) ReadUint32(out *uint32) bool { + v := s.read(4) + if v == nil { + return false + } + *out = uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3]) + return true +} + +func (s *String) readUnsigned(out *uint32, length int) bool { + v := s.read(length) + if v == nil { + return false + } + var result uint32 + for i := 0; i < length; i++ { + result <<= 8 + result |= uint32(v[i]) + } + *out = result + return true +} + +func (s *String) readLengthPrefixed(lenLen int, outChild *String) bool { + lenBytes := s.read(lenLen) + if lenBytes == nil { + return false + } + var length uint32 + for _, b := range lenBytes { + length = length << 8 + length = length | uint32(b) + } + if int(length) < 0 { + // This currently cannot overflow because we read uint24 at most, but check + // anyway in case that changes in the future. + return false + } + v := s.read(int(length)) + if v == nil { + return false + } + *outChild = v + return true +} + +// ReadUint8LengthPrefixed reads the content of an 8-bit length-prefixed value +// into out and advances over it. It returns true on success and false on +// error. +func (s *String) ReadUint8LengthPrefixed(out *String) bool { + return s.readLengthPrefixed(1, out) +} + +// ReadUint16LengthPrefixed reads the content of a big-endian, 16-bit +// length-prefixed value into out and advances over it. It returns true on +// success and false on error. +func (s *String) ReadUint16LengthPrefixed(out *String) bool { + return s.readLengthPrefixed(2, out) +} + +// ReadUint24LengthPrefixed reads the content of a big-endian, 24-bit +// length-prefixed value into out and advances over it. It returns true on +// success and false on error. +func (s *String) ReadUint24LengthPrefixed(out *String) bool { + return s.readLengthPrefixed(3, out) +} + +// ReadBytes reads n bytes into out and advances over them. It returns true on +// success and false and error. +func (s *String) ReadBytes(out *[]byte, n int) bool { + v := s.read(n) + if v == nil { + return false + } + *out = v + return true +} + +// CopyBytes copies len(out) bytes into out and advances over them. It returns +// true on success and false on error. +func (s *String) CopyBytes(out []byte) bool { + n := len(out) + v := s.read(n) + if v == nil { + return false + } + return copy(out, v) == n +} + +// Empty reports whether the string does not contain any bytes. +func (s String) Empty() bool { + return len(s) == 0 +} diff --git a/vendor/vendor.json b/vendor/vendor.json index bd0e0a2525eb..80d06e8b3355 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1716,6 +1716,18 @@ "revision": "5119cf507ed5294cc409c092980c7497ee5d6fd2", "revisionTime": "2018-01-22T10:39:14Z" }, + { + "checksumSHA1": "t3UubYn5RuLw3vTUghV1Q1Q9VEY=", + "path": "golang.org/x/crypto/cryptobyte", + "revision": "1875d0a70c90e57f11972aefd42276df65e895b9", + "revisionTime": "2018-01-27T19:02:20Z" + }, + { + "checksumSHA1": "YEoV2AiZZPDuF7pMVzDt7buS9gc=", + "path": "golang.org/x/crypto/cryptobyte/asn1", + "revision": "1875d0a70c90e57f11972aefd42276df65e895b9", + "revisionTime": "2018-01-27T19:02:20Z" + }, { "checksumSHA1": "1zB843WyoSh8oMdbeDfgByEa2TE=", "path": "golang.org/x/crypto/chacha20poly1305", diff --git a/website/source/api/secret/pki/index.html.md b/website/source/api/secret/pki/index.html.md index 3b704c2af55d..0a660ae296fe 100644 --- a/website/source/api/secret/pki/index.html.md +++ b/website/source/api/secret/pki/index.html.md @@ -435,6 +435,12 @@ can be set in a CSR are supported. - `ip_sans` `(string: "")` – Specifies the requested IP Subject Alternative Names, in a comma-delimited list. +- `other_sans` `(string: "")` – Specifies custom OID/UTF8-string SANs. These + must match values specified on the role in `allowed_other_sans` (globbing + allowed). The format is the same as OpenSSL: `;:` where the + only current valid type is `UTF8`. This can be a comma-delimited list or a + JSON string slice. + - `format` `(string: "")` – Specifies the format for returned data. This can be `pem`, `der`, or `pem_bundle`; defaults to `pem`. If `der`, the output is base64 encoded. If `pem_bundle`, the `csr` field will contain the private key @@ -456,6 +462,34 @@ can be set in a CSR are supported. Useful if the CN is not a hostname or email address, but is instead some human-readable identifier. +- `ou` `(string: "")` – Specifies the OU (OrganizationalUnit) values in the + subject field of the resulting CSR. This is a comma-separated string + or JSON array. + +- `organization` `(string: "")` – Specifies the O (Organization) values in the + subject field of the resulting CSR. This is a comma-separated string + or JSON array. + +- `country` `(string: "")` – Specifies the C (Country) values in the subject + field of the resulting CSR. This is a comma-separated string or JSON + array. + +- `locality` `(string: "")` – Specifies the L (Locality) values in the subject + field of the resulting CSR. This is a comma-separated string or JSON + array. + +- `province` `(string: "")` – Specifies the ST (Province) values in the subject + field of the resulting CSR. This is a comma-separated string or JSON + array. + +- `street_address` `(string: "")` – Specifies the Street Address values in the + subject field of the resulting CSR. This is a comma-separated string + or JSON array. + +- `postal_code` `(string: "")` – Specifies the Postal Code values in the + subject field of the resulting CSR. This is a comma-separated string + or JSON array. + ### Sample Payload ```json @@ -553,6 +587,12 @@ need to request a new certificate.** in a comma-delimited list. Only valid if the role allows IP SANs (which is the default). +- `other_sans` `(string: "")` – Specifies custom OID/UTF8-string SANs. These + must match values specified on the role in `allowed_other_sans` (globbing + allowed). The format is the same as OpenSSL: `;:` where the + only current valid type is `UTF8`. This can be a comma-delimited list or a + JSON string slice. + - `ttl` `(string: "")` – Specifies requested Time To Live. Cannot be greater than the role's `max_ttl` value. If not provided, the role's `ttl` value will be used. Note that the role values default to system values if not explicitly @@ -720,6 +760,11 @@ request is denied. Alternative Names. No authorization checking is performed except to verify that the given values are valid IP addresses. +- `allowed_other_sans` `(string: "")` – Defines allowed custom OID/UTF8-string + SANs. This field supports globbing. The format is the same as OpenSSL: + `;:` where the only current valid type is `UTF8`. This can + be a comma-delimited list or a JSON string slice. + - `server_flag` `(bool: true)` – Specifies if certificates are flagged for server use. @@ -757,10 +802,32 @@ request is denied. `use_csr_common_name` for that. - `ou` `(string: "")` – Specifies the OU (OrganizationalUnit) values in the - subject field of issued certificates. This is a comma-separated string. + subject field of issued certificates. This is a comma-separated string or + JSON array. - `organization` `(string: "")` – Specifies the O (Organization) values in the - subject field of issued certificates. This is a comma-separated string. + subject field of issued certificates. This is a comma-separated string or + JSON array. + +- `country` `(string: "")` – Specifies the C (Country) values in the + subject field of issued certificates. This is a comma-separated string or + JSON array. + +- `locality` `(string: "")` – Specifies the L (Locality) values in the + subject field of issued certificates. This is a comma-separated string or + JSON array. + +- `province` `(string: "")` – Specifies the ST (Province) values in the + subject field of issued certificates. This is a comma-separated string or + JSON array. + +- `street_address` `(string: "")` – Specifies the Street Address values in the + subject field of issued certificates. This is a comma-separated string or + JSON array. + +- `postal_code` `(string: "")` – Specifies the Postal Code values in the + subject field of issued certificates. This is a comma-separated string or + JSON array. - `generate_lease` `(bool: false)` – Specifies if certificates issued/signed against this role will have Vault leases attached to them. Certificates can be @@ -935,6 +1002,12 @@ existing cert/key with new values. - `ip_sans` `(string: "")` – Specifies the requested IP Subject Alternative Names, in a comma-delimited list. +- `other_sans` `(string: "")` – Specifies custom OID/UTF8-string SANs. These + must match values specified on the role in `allowed_other_sans` (globbing + allowed). The format is the same as OpenSSL: `;:` where the + only current valid type is `UTF8`. This can be a comma-delimited list or a + JSON string slice. + - `ttl` `(string: "")` – Specifies the requested Time To Live (after which the certificate will be expired). This cannot be larger than the engine's max (or, if not set, the system max). @@ -973,6 +1046,34 @@ existing cert/key with new values. the domain, as per [RFC](https://tools.ietf.org/html/rfc5280#section-4.2.1.10). +- `ou` `(string: "")` – Specifies the OU (OrganizationalUnit) values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + +- `organization` `(string: "")` – Specifies the O (Organization) values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + +- `country` `(string: "")` – Specifies the C (Country) values in the subject + field of the resulting certificate. This is a comma-separated string or JSON + array. + +- `locality` `(string: "")` – Specifies the L (Locality) values in the subject + field of the resulting certificate. This is a comma-separated string or JSON + array. + +- `province` `(string: "")` – Specifies the ST (Province) values in the subject + field of the resulting certificate. This is a comma-separated string or JSON + array. + +- `street_address` `(string: "")` – Specifies the Street Address values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + +- `postal_code` `(string: "")` – Specifies the Postal Code values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + ### Sample Payload ```json @@ -1053,6 +1154,12 @@ verbatim. - `ip_sans` `(string: "")` – Specifies the requested IP Subject Alternative Names, in a comma-delimited list. +- `other_sans` `(string: "")` – Specifies custom OID/UTF8-string SANs. These + must match values specified on the role in `allowed_other_sans` (globbing + allowed). The format is the same as OpenSSL: `;:` where the + only current valid type is `UTF8`. This can be a comma-delimited list or a + JSON string slice. + - `ttl` `(string: "")` – Specifies the requested Time To Live (after which the certificate will be expired). This cannot be larger than the engine's max (or, if not set, the system max). However, this can be after the expiration of the @@ -1089,6 +1196,35 @@ verbatim. the domain, as per [RFC](https://tools.ietf.org/html/rfc5280#section-4.2.1.10). +- `ou` `(string: "")` – Specifies the OU (OrganizationalUnit) values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + +- `organization` `(string: "")` – Specifies the O (Organization) values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + +- `country` `(string: "")` – Specifies the C (Country) values in the subject + field of the resulting certificate. This is a comma-separated string or JSON + array. + +- `locality` `(string: "")` – Specifies the L (Locality) values in the subject + field of the resulting certificate. This is a comma-separated string or JSON + array. + +- `province` `(string: "")` – Specifies the ST (Province) values in the subject + field of the resulting certificate. This is a comma-separated string or JSON + array. + +- `street_address` `(string: "")` – Specifies the Street Address values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + +- `postal_code` `(string: "")` – Specifies the Postal Code values in the + subject field of the resulting certificate. This is a comma-separated string + or JSON array. + + ### Sample Payload ```json @@ -1207,6 +1343,12 @@ root CA need be in a client's trust store. they will be parsed into their respective fields. If any requested names do not match role policy, the entire request will be denied. +- `other_sans` `(string: "")` – Specifies custom OID/UTF8-string SANs. These + must match values specified on the role in `allowed_other_sans` (globbing + allowed). The format is the same as OpenSSL: `;:` where the + only current valid type is `UTF8`. This can be a comma-delimited list or a + JSON string slice. + - `ip_sans` `(string: "")` – Specifies the requested IP Subject Alternative Names, in a comma-delimited list. Only valid if the role allows IP SANs (which is the default).