From f75217ae7b1a12d97fc02025219c20f29c83bb98 Mon Sep 17 00:00:00 2001 From: Steven Clark Date: Fri, 14 Jul 2023 09:34:51 -0400 Subject: [PATCH] Remove accidently committed merge failure file (#21858) --- builtin/logical/pki/backend_test.go.orig | 7007 ---------------------- 1 file changed, 7007 deletions(-) delete mode 100644 builtin/logical/pki/backend_test.go.orig diff --git a/builtin/logical/pki/backend_test.go.orig b/builtin/logical/pki/backend_test.go.orig deleted file mode 100644 index 64fd8a5a80..0000000000 --- a/builtin/logical/pki/backend_test.go.orig +++ /dev/null @@ -1,7007 +0,0 @@ -package pki - -import ( - "bytes" - "context" - "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/base64" - "encoding/hex" - "encoding/json" - "encoding/pem" - "fmt" - "math" - "math/big" - mathrand "math/rand" - "net" - "net/url" - "os" - "reflect" - "sort" - "strconv" - "strings" - "sync" - "testing" - "time" - - "github.com/hashicorp/vault/helper/testhelpers/teststorage" - - "github.com/hashicorp/vault/helper/testhelpers" - - "github.com/stretchr/testify/require" - - "github.com/armon/go-metrics" - "github.com/fatih/structs" - "github.com/go-test/deep" - "github.com/hashicorp/go-secure-stdlib/strutil" - "github.com/hashicorp/vault/api" - auth "github.com/hashicorp/vault/api/auth/userpass" - "github.com/hashicorp/vault/builtin/credential/userpass" - logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" - vaulthttp "github.com/hashicorp/vault/http" - "github.com/hashicorp/vault/sdk/helper/certutil" - "github.com/hashicorp/vault/sdk/logical" - "github.com/hashicorp/vault/vault" - "github.com/mitchellh/mapstructure" - "golang.org/x/net/idna" -) - -var stepCount = 0 - -// From builtin/credential/cert/test-fixtures/root/rootcacert.pem -const ( - rootCACertPEM = `-----BEGIN CERTIFICATE----- -MIIDPDCCAiSgAwIBAgIUb5id+GcaMeMnYBv3MvdTGWigyJ0wDQYJKoZIhvcNAQEL -BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzI5WhcNMjYw -MjI2MDIyNzU5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAOxTMvhTuIRc2YhxZpmPwegP86cgnqfT1mXxi1A7 -Q7qax24Nqbf00I3oDMQtAJlj2RB3hvRSCb0/lkF7i1Bub+TGxuM7NtZqp2F8FgG0 -z2md+W6adwW26rlxbQKjmRvMn66G9YPTkoJmPmxt2Tccb9+apmwW7lslL5j8H48x -AHJTMb+PMP9kbOHV5Abr3PT4jXUPUr/mWBvBiKiHG0Xd/HEmlyOEPeAThxK+I5tb -6m+eB+7cL9BsvQpy135+2bRAxUphvFi5NhryJ2vlAvoJ8UqigsNK3E28ut60FAoH -SWRfFUFFYtfPgTDS1yOKU/z/XMU2giQv2HrleWt0mp4jqBUCAwEAAaOBgTB/MA4G -A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdxLNP/ocx -7HK6JT3/sSAe76iTmzAfBgNVHSMEGDAWgBSdxLNP/ocx7HK6JT3/sSAe76iTmzAc -BgNVHREEFTATggtleGFtcGxlLmNvbYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA -wHThDRsXJunKbAapxmQ6bDxSvTvkLA6m97TXlsFgL+Q3Jrg9HoJCNowJ0pUTwhP2 -U946dCnSCkZck0fqkwVi4vJ5EQnkvyEbfN4W5qVsQKOFaFVzep6Qid4rZT6owWPa -cNNzNcXAee3/j6hgr6OQ/i3J6fYR4YouYxYkjojYyg+CMdn6q8BoV0BTsHdnw1/N -ScbnBHQIvIZMBDAmQueQZolgJcdOuBLYHe/kRy167z8nGg+PUFKIYOL8NaOU1+CJ -t2YaEibVq5MRqCbRgnd9a2vG0jr5a3Mn4CUUYv+5qIjP3hUusYenW1/EWtn1s/gk -zehNe5dFTjFpylg1o6b8Ow== ------END CERTIFICATE-----` - rootCAKeyPEM = `-----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA7FMy+FO4hFzZiHFmmY/B6A/zpyCep9PWZfGLUDtDuprHbg2p -t/TQjegMxC0AmWPZEHeG9FIJvT+WQXuLUG5v5MbG4zs21mqnYXwWAbTPaZ35bpp3 -BbbquXFtAqOZG8yfrob1g9OSgmY+bG3ZNxxv35qmbBbuWyUvmPwfjzEAclMxv48w -/2Rs4dXkBuvc9PiNdQ9Sv+ZYG8GIqIcbRd38cSaXI4Q94BOHEr4jm1vqb54H7twv -0Gy9CnLXfn7ZtEDFSmG8WLk2GvIna+UC+gnxSqKCw0rcTby63rQUCgdJZF8VQUVi -18+BMNLXI4pT/P9cxTaCJC/YeuV5a3SaniOoFQIDAQABAoIBAQCoGZJC84JnnIgb -ttZNWuWKBXbCJcDVDikOQJ9hBZbqsFg1X0CfGmQS3MHf9Ubc1Ro8zVjQh15oIEfn -8lIpdzTeXcpxLdiW8ix3ekVJF20F6pnXY8ZP6UnTeOwamXY6QPZAtb0D9UXcvY+f -nw+IVRD6082XS0Rmzu+peYWVXDy+FDN+HJRANBcdJZz8gOmNBIe0qDWx1b85d/s8 -2Kk1Wwdss1IwAGeSddTSwzBNaaHdItZaMZOqPW1gRyBfVSkcUQIE6zn2RKw2b70t -grkIvyRcTdfmiKbqkkJ+eR+ITOUt0cBZSH4cDjlQA+r7hulvoBpQBRj068Toxkcc -bTagHaPBAoGBAPWPGVkHqhTbJ/DjmqDIStxby2M1fhhHt4xUGHinhUYjQjGOtDQ9 -0mfaB7HObudRiSLydRAVGAHGyNJdQcTeFxeQbovwGiYKfZSA1IGpea7dTxPpGEdN -ksA0pzSp9MfKzX/MdLuAkEtO58aAg5YzsgX9hDNxo4MhH/gremZhEGZlAoGBAPZf -lqdYvAL0fjHGJ1FUEalhzGCGE9PH2iOqsxqLCXK7bDbzYSjvuiHkhYJHAOgVdiW1 -lB34UHHYAqZ1VVoFqJ05gax6DE2+r7K5VV3FUCaC0Zm3pavxchU9R/TKP82xRrBj -AFWwdgDTxUyvQEmgPR9sqorftO71Iz2tiwyTpIfxAoGBAIhEMLzHFAse0rtKkrRG -ccR27BbRyHeQ1Lp6sFnEHKEfT8xQdI/I/snCpCJ3e/PBu2g5Q9z416mktiyGs8ib -thTNgYsGYnxZtfaCx2pssanoBcn2wBJRae5fSapf5gY49HDG9MBYR7qCvvvYtSzU -4yWP2ZzyotpRt3vwJKxLkN5BAoGAORHpZvhiDNkvxj3da7Rqpu7VleJZA2y+9hYb -iOF+HcqWhaAY+I+XcTRrTMM/zYLzLEcEeXDEyao86uwxCjpXVZw1kotvAC9UqbTO -tnr3VwRkoxPsV4kFYTAh0+1pnC8dbcxxDmhi3Uww3tOVs7hfkEDuvF6XnebA9A+Y -LyCgMzECgYEA6cCU8QODOivIKWFRXucvWckgE6MYDBaAwe6qcLsd1Q/gpE2e3yQc -4RB3bcyiPROLzMLlXFxf1vSNJQdIaVfrRv+zJeGIiivLPU8+Eq4Lrb+tl1LepcOX -OzQeADTSCn5VidOfjDkIst9UXjMlrFfV9/oJEw5Eiqa6lkNPCGDhfA8= ------END RSA PRIVATE KEY-----` -) - -func TestPKI_RequireCN(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected ca info") - } - - // Create a role which does require CN (default) - _, err = CBWrite(b, s, "roles/example", map[string]interface{}{ - "allowed_domains": "foobar.com,zipzap.com,abc.com,xyz.com", - "allow_bare_domains": true, - "allow_subdomains": true, - "max_ttl": "2h", - }) - if err != nil { - t.Fatal(err) - } - - // Issue a cert with require_cn set to true and with common name supplied. - // It should succeed. - _, err = CBWrite(b, s, "issue/example", map[string]interface{}{ - "common_name": "foobar.com", - }) - if err != nil { - t.Fatal(err) - } - - // Issue a cert with require_cn set to true and with out supplying the - // common name. It should error out. - _, err = CBWrite(b, s, "issue/example", map[string]interface{}{}) - if err == nil { - t.Fatalf("expected an error due to missing common_name") - } - - // Modify the role to make the common name optional - _, err = CBWrite(b, s, "roles/example", map[string]interface{}{ - "allowed_domains": "foobar.com,zipzap.com,abc.com,xyz.com", - "allow_bare_domains": true, - "allow_subdomains": true, - "max_ttl": "2h", - "require_cn": false, - }) - if err != nil { - t.Fatal(err) - } - - // Issue a cert with require_cn set to false and without supplying the - // common name. It should succeed. - resp, err = CBWrite(b, s, "issue/example", map[string]interface{}{}) - if err != nil { - t.Fatal(err) - } - - if resp.Data["certificate"] == "" { - t.Fatalf("expected a cert to be generated") - } - - // Issue a cert with require_cn set to false and with a common name. It - // should succeed. - resp, err = CBWrite(b, s, "issue/example", map[string]interface{}{}) - if err != nil { - t.Fatal(err) - } - - if resp.Data["certificate"] == "" { - t.Fatalf("expected a cert to be generated") - } -} - -func TestPKI_DeviceCert(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - "not_after": "9999-12-31T23:59:59Z", - "not_before_duration": "2h", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected ca info") - } - var certBundle certutil.CertBundle - err = mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - t.Fatal(err) - } - - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - t.Fatal(err) - } - cert := parsedCertBundle.Certificate - notAfter := cert.NotAfter.Format(time.RFC3339) - if notAfter != "9999-12-31T23:59:59Z" { - t.Fatalf("not after from certificate: %v is not matching with input parameter: %v", cert.NotAfter, "9999-12-31T23:59:59Z") - } - if math.Abs(float64(time.Now().Add(-2*time.Hour).Unix()-cert.NotBefore.Unix())) > 10 { - t.Fatalf("root/generate/internal did not properly set validity period (notBefore): was %v vs expected %v", cert.NotBefore, time.Now().Add(-2*time.Hour)) - } - - // Create a role which does require CN (default) - _, err = CBWrite(b, s, "roles/example", map[string]interface{}{ - "allowed_domains": "foobar.com,zipzap.com,abc.com,xyz.com", - "allow_bare_domains": true, - "allow_subdomains": true, - "not_after": "9999-12-31T23:59:59Z", - }) - if err != nil { - t.Fatal(err) - } - - // Issue a cert with require_cn set to true and with common name supplied. - // It should succeed. - resp, err = CBWrite(b, s, "issue/example", map[string]interface{}{ - "common_name": "foobar.com", - }) - if err != nil { - t.Fatal(err) - } - err = mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - t.Fatal(err) - } - - parsedCertBundle, err = certBundle.ToParsedCertBundle() - if err != nil { - t.Fatal(err) - } - cert = parsedCertBundle.Certificate - notAfter = cert.NotAfter.Format(time.RFC3339) - if notAfter != "9999-12-31T23:59:59Z" { - t.Fatal(fmt.Errorf("not after from certificate is not matching with input parameter")) - } -} - -func TestBackend_InvalidParameter(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - _, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - "not_after": "9999-12-31T23:59:59Z", - "ttl": "25h", - }) - if err == nil { - t.Fatal(err) - } - - _, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - "not_after": "9999-12-31T23:59:59", - }) - if err == nil { - t.Fatal(err) - } -} - -func TestBackend_CSRValues(t *testing.T) { - t.Parallel() - initTest.Do(setCerts) - b, _ := CreateBackendWithStorage(t) - - testCase := logicaltest.TestCase{ - LogicalBackend: b, - Steps: []logicaltest.TestStep{}, - } - - intdata := map[string]interface{}{} - reqdata := map[string]interface{}{} - testCase.Steps = append(testCase.Steps, generateCSRSteps(t, ecCACert, ecCAKey, intdata, reqdata)...) - - logicaltest.Test(t, testCase) -} - -func TestBackend_URLsCRUD(t *testing.T) { - t.Parallel() - initTest.Do(setCerts) - b, _ := CreateBackendWithStorage(t) - - testCase := logicaltest.TestCase{ - LogicalBackend: b, - Steps: []logicaltest.TestStep{}, - } - - intdata := map[string]interface{}{} - reqdata := map[string]interface{}{} - testCase.Steps = append(testCase.Steps, generateURLSteps(t, ecCACert, ecCAKey, intdata, reqdata)...) - - logicaltest.Test(t, testCase) -} - -// Generates and tests steps that walk through the various possibilities -// of role flags to ensure that they are properly restricted -func TestBackend_Roles(t *testing.T) { - t.Parallel() - cases := []struct { - name string - key, cert *string - useCSR bool - }{ - {"RSA", &rsaCAKey, &rsaCACert, false}, - {"RSACSR", &rsaCAKey, &rsaCACert, true}, - {"EC", &ecCAKey, &ecCACert, false}, - {"ECCSR", &ecCAKey, &ecCACert, true}, - {"ED", &edCAKey, &edCACert, false}, - {"EDCSR", &edCAKey, &edCACert, true}, - } - - for _, tc := range cases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - initTest.Do(setCerts) - b, _ := CreateBackendWithStorage(t) - - testCase := logicaltest.TestCase{ - LogicalBackend: b, - Steps: []logicaltest.TestStep{ - { - Operation: logical.UpdateOperation, - Path: "config/ca", - Data: map[string]interface{}{ - "pem_bundle": *tc.key + "\n" + *tc.cert, - }, - }, - }, - } - - testCase.Steps = append(testCase.Steps, generateRoleSteps(t, tc.useCSR)...) - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - for i, v := range testCase.Steps { - data := map[string]interface{}{} - var keys []string - for k := range v.Data { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - interf := v.Data[k] - switch v := interf.(type) { - case bool: - if !v { - continue - } - case int: - if v == 0 { - continue - } - case []string: - if len(v) == 0 { - continue - } - case string: - if v == "" { - continue - } - lines := strings.Split(v, "\n") - if len(lines) > 1 { - data[k] = lines[0] + " ... (truncated)" - continue - } - } - data[k] = interf - - } - t.Logf("Step %d:\n%s %s err=%v %+v\n\n", i+1, v.Operation, v.Path, v.ErrorOk, data) - } - } - - logicaltest.Test(t, testCase) - }) - } -} - -// Performs some validity checking on the returned bundles -func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUsage, extUsage x509.ExtKeyUsage, validity time.Duration, certBundle *certutil.CertBundle) (*certutil.ParsedCertBundle, error) { - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return nil, fmt.Errorf("error parsing cert bundle: %s", err) - } - - if key != nil { - switch keyType { - case "rsa": - parsedCertBundle.PrivateKeyType = certutil.RSAPrivateKey - parsedCertBundle.PrivateKey = key - parsedCertBundle.PrivateKeyBytes = x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey)) - case "ec": - parsedCertBundle.PrivateKeyType = certutil.ECPrivateKey - parsedCertBundle.PrivateKey = key - parsedCertBundle.PrivateKeyBytes, err = x509.MarshalECPrivateKey(key.(*ecdsa.PrivateKey)) - if err != nil { - return nil, fmt.Errorf("error parsing EC key: %s", err) - } - case "ed25519": - parsedCertBundle.PrivateKeyType = certutil.Ed25519PrivateKey - parsedCertBundle.PrivateKey = key - parsedCertBundle.PrivateKeyBytes, err = x509.MarshalPKCS8PrivateKey(key.(ed25519.PrivateKey)) - if err != nil { - return nil, fmt.Errorf("error parsing Ed25519 key: %s", err) - } - } - } - - switch { - case parsedCertBundle.Certificate == nil: - return nil, fmt.Errorf("did not find a certificate in the cert bundle") - case len(parsedCertBundle.CAChain) == 0 || parsedCertBundle.CAChain[0].Certificate == nil: - return nil, fmt.Errorf("did not find a CA in the cert bundle") - case parsedCertBundle.PrivateKey == nil: - return nil, fmt.Errorf("did not find a private key in the cert bundle") - case parsedCertBundle.PrivateKeyType == certutil.UnknownPrivateKey: - return nil, fmt.Errorf("could not figure out type of private key") - } - - switch { - case parsedCertBundle.PrivateKeyType == certutil.Ed25519PrivateKey && keyType != "ed25519": - fallthrough - case parsedCertBundle.PrivateKeyType == certutil.RSAPrivateKey && keyType != "rsa": - fallthrough - case parsedCertBundle.PrivateKeyType == certutil.ECPrivateKey && keyType != "ec": - return nil, fmt.Errorf("given key type does not match type found in bundle") - } - - cert := parsedCertBundle.Certificate - - if usage != cert.KeyUsage { - return nil, fmt.Errorf("expected usage of %#v, got %#v; ext usage is %#v", usage, cert.KeyUsage, cert.ExtKeyUsage) - } - - // There should only be one ext usage type, because only one is requested - // in the tests - if len(cert.ExtKeyUsage) != 1 { - return nil, fmt.Errorf("got wrong size key usage in generated cert; expected 1, values are %#v", cert.ExtKeyUsage) - } - switch extUsage { - case x509.ExtKeyUsageEmailProtection: - if cert.ExtKeyUsage[0] != x509.ExtKeyUsageEmailProtection { - return nil, fmt.Errorf("bad extended key usage") - } - case x509.ExtKeyUsageServerAuth: - if cert.ExtKeyUsage[0] != x509.ExtKeyUsageServerAuth { - return nil, fmt.Errorf("bad extended key usage") - } - case x509.ExtKeyUsageClientAuth: - if cert.ExtKeyUsage[0] != x509.ExtKeyUsageClientAuth { - return nil, fmt.Errorf("bad extended key usage") - } - case x509.ExtKeyUsageCodeSigning: - if cert.ExtKeyUsage[0] != x509.ExtKeyUsageCodeSigning { - return nil, fmt.Errorf("bad extended key usage") - } - } - - // TODO: We incremented 20->25 due to CircleCI execution - // being slow and pausing this test. We might consider recording the - // actual issuance time of the cert and calculating the expected - // validity period +/- fuzz, but that'd require recording and passing - // through more information. - if math.Abs(float64(time.Now().Add(validity).Unix()-cert.NotAfter.Unix())) > 25 { - return nil, fmt.Errorf("certificate validity end: %s; expected within 25 seconds of %s", cert.NotAfter.Format(time.RFC3339), time.Now().Add(validity).Format(time.RFC3339)) - } - - return parsedCertBundle, nil -} - -func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep { - expected := certutil.URLEntries{ - IssuingCertificates: []string{ - "http://example.com/ca1", - "http://example.com/ca2", - }, - CRLDistributionPoints: []string{ - "http://example.com/crl1", - "http://example.com/crl2", - }, - OCSPServers: []string{ - "http://example.com/ocsp1", - "http://example.com/ocsp2", - }, - } - csrTemplate := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "my@example.com", - }, - } - - priv1024, _ := rsa.GenerateKey(rand.Reader, 1024) - csr1024, _ := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, priv1024) - csrPem1024 := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", - Bytes: csr1024, - }))) - - priv2048, _ := rsa.GenerateKey(rand.Reader, 2048) - csr2048, _ := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, priv2048) - csrPem2048 := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", - Bytes: csr2048, - }))) - - ret := []logicaltest.TestStep{ - { - Operation: logical.UpdateOperation, - Path: "root/generate/exported", - Data: map[string]interface{}{ - "common_name": "Root Cert", - "ttl": "180h", - }, - Check: func(resp *logical.Response) error { - if resp.Secret != nil && resp.Secret.LeaseID != "" { - return fmt.Errorf("root returned with a lease") - } - return nil - }, - }, - - { - Operation: logical.UpdateOperation, - Path: "config/urls", - Data: map[string]interface{}{ - "issuing_certificates": strings.Join(expected.IssuingCertificates, ","), - "crl_distribution_points": strings.Join(expected.CRLDistributionPoints, ","), - "ocsp_servers": strings.Join(expected.OCSPServers, ","), - }, - }, - - { - Operation: logical.ReadOperation, - Path: "config/urls", - Check: func(resp *logical.Response) error { - if resp.Data == nil { - return fmt.Errorf("no data returned") - } - var entries certutil.URLEntries - err := mapstructure.Decode(resp.Data, &entries) - if err != nil { - return err - } - - if !reflect.DeepEqual(entries, expected) { - return fmt.Errorf("expected urls\n%#v\ndoes not match provided\n%#v\n", expected, entries) - } - - return nil - }, - }, - - { - Operation: logical.UpdateOperation, - Path: "root/sign-intermediate", - Data: map[string]interface{}{ - "common_name": "intermediate.cert.com", - "csr": csrPem1024, - "format": "der", - }, - ErrorOk: true, - Check: func(resp *logical.Response) error { - if !resp.IsError() { - return fmt.Errorf("expected an error response but did not get one") - } - if !strings.Contains(resp.Data["error"].(string), "2048") { - return fmt.Errorf("received an error but not about a 1024-bit key, error was: %s", resp.Data["error"].(string)) - } - - return nil - }, - }, - - { - Operation: logical.UpdateOperation, - Path: "root/sign-intermediate", - Data: map[string]interface{}{ - "common_name": "intermediate.cert.com", - "csr": csrPem2048, - "signature_bits": 512, - "format": "der", - "not_before_duration": "2h", - // Let's Encrypt -- R3 SKID - "skid": "14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6", - }, - Check: func(resp *logical.Response) error { - certString := resp.Data["certificate"].(string) - if certString == "" { - return fmt.Errorf("no certificate returned") - } - if resp.Secret != nil && resp.Secret.LeaseID != "" { - return fmt.Errorf("signed intermediate returned with a lease") - } - certBytes, _ := base64.StdEncoding.DecodeString(certString) - certs, err := x509.ParseCertificates(certBytes) - if err != nil { - return fmt.Errorf("returned cert cannot be parsed: %w", err) - } - if len(certs) != 1 { - return fmt.Errorf("unexpected returned length of certificates: %d", len(certs)) - } - cert := certs[0] - - skid, _ := hex.DecodeString("142EB317B75856CBAE500940E61FAF9D8B14C2C6") - - switch { - case !reflect.DeepEqual(expected.IssuingCertificates, cert.IssuingCertificateURL): - return fmt.Errorf("IssuingCertificateURL:\nexpected\n%#v\ngot\n%#v\n", expected.IssuingCertificates, cert.IssuingCertificateURL) - case !reflect.DeepEqual(expected.CRLDistributionPoints, cert.CRLDistributionPoints): - return fmt.Errorf("CRLDistributionPoints:\nexpected\n%#v\ngot\n%#v\n", expected.CRLDistributionPoints, cert.CRLDistributionPoints) - case !reflect.DeepEqual(expected.OCSPServers, cert.OCSPServer): - return fmt.Errorf("OCSPServer:\nexpected\n%#v\ngot\n%#v\n", expected.OCSPServers, cert.OCSPServer) - case !reflect.DeepEqual([]string{"intermediate.cert.com"}, cert.DNSNames): - return fmt.Errorf("DNSNames\nexpected\n%#v\ngot\n%#v\n", []string{"intermediate.cert.com"}, cert.DNSNames) - case !reflect.DeepEqual(x509.SHA512WithRSA, cert.SignatureAlgorithm): - return fmt.Errorf("Signature Algorithm:\nexpected\n%#v\ngot\n%#v\n", x509.SHA512WithRSA, cert.SignatureAlgorithm) - case !reflect.DeepEqual(skid, cert.SubjectKeyId): - return fmt.Errorf("SKID:\nexpected\n%#v\ngot\n%#v\n", skid, cert.SubjectKeyId) - } - - if math.Abs(float64(time.Now().Add(-2*time.Hour).Unix()-cert.NotBefore.Unix())) > 10 { - t.Fatalf("root/sign-intermediate did not properly set validity period (notBefore): was %v vs expected %v", cert.NotBefore, time.Now().Add(-2*time.Hour)) - } - - return nil - }, - }, - - // Same as above but exclude adding to sans - { - Operation: logical.UpdateOperation, - Path: "root/sign-intermediate", - Data: map[string]interface{}{ - "common_name": "intermediate.cert.com", - "csr": csrPem2048, - "format": "der", - "exclude_cn_from_sans": true, - }, - Check: func(resp *logical.Response) error { - certString := resp.Data["certificate"].(string) - if certString == "" { - return fmt.Errorf("no certificate returned") - } - if resp.Secret != nil && resp.Secret.LeaseID != "" { - return fmt.Errorf("signed intermediate returned with a lease") - } - certBytes, _ := base64.StdEncoding.DecodeString(certString) - certs, err := x509.ParseCertificates(certBytes) - if err != nil { - return fmt.Errorf("returned cert cannot be parsed: %w", err) - } - if len(certs) != 1 { - return fmt.Errorf("unexpected returned length of certificates: %d", len(certs)) - } - cert := certs[0] - - switch { - case !reflect.DeepEqual(expected.IssuingCertificates, cert.IssuingCertificateURL): - return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.IssuingCertificates, cert.IssuingCertificateURL) - case !reflect.DeepEqual(expected.CRLDistributionPoints, cert.CRLDistributionPoints): - return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.CRLDistributionPoints, cert.CRLDistributionPoints) - case !reflect.DeepEqual(expected.OCSPServers, cert.OCSPServer): - return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.OCSPServers, cert.OCSPServer) - case !reflect.DeepEqual([]string(nil), cert.DNSNames): - return fmt.Errorf("expected\n%#v\ngot\n%#v\n", []string(nil), cert.DNSNames) - } - - return nil - }, - }, - } - return ret -} - -func generateCSR(t *testing.T, csrTemplate *x509.CertificateRequest, keyType string, keyBits int) (interface{}, []byte, string) { - t.Helper() - - var priv interface{} - var err error - switch keyType { - case "rsa": - priv, err = rsa.GenerateKey(rand.Reader, keyBits) - case "ec": - switch keyBits { - case 224: - priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) - case 256: - priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - case 384: - priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - case 521: - priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) - default: - t.Fatalf("Got unknown ec< key bits: %v", keyBits) - } - case "ed25519": - _, priv, err = ed25519.GenerateKey(rand.Reader) - } - - if err != nil { - t.Fatalf("Got error generating private key for CSR: %v", err) - } - - csr, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, priv) - if err != nil { - t.Fatalf("Got error generating CSR: %v", err) - } - - csrPem := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", - Bytes: csr, - }))) - - return priv, csr, csrPem -} - -func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep { - csrTemplate, csrPem := generateTestCsr(t, certutil.RSAPrivateKey, 2048) - - ret := []logicaltest.TestStep{ - { - Operation: logical.UpdateOperation, - Path: "root/generate/exported", - Data: map[string]interface{}{ - "common_name": "Root Cert", - "ttl": "180h", - "max_path_length": 0, - }, - }, - - { - Operation: logical.UpdateOperation, - Path: "root/sign-intermediate", - Data: map[string]interface{}{ - "use_csr_values": true, - "csr": csrPem, - "format": "der", - }, - ErrorOk: true, - }, - - { - Operation: logical.DeleteOperation, - Path: "root", - }, - - { - Operation: logical.UpdateOperation, - Path: "root/generate/exported", - Data: map[string]interface{}{ - "common_name": "Root Cert", - "ttl": "180h", - "max_path_length": 1, - }, - }, - - { - Operation: logical.UpdateOperation, - Path: "root/sign-intermediate", - Data: map[string]interface{}{ - "use_csr_values": true, - "csr": csrPem, - "format": "der", - }, - Check: func(resp *logical.Response) error { - certString := resp.Data["certificate"].(string) - if certString == "" { - return fmt.Errorf("no certificate returned") - } - certBytes, _ := base64.StdEncoding.DecodeString(certString) - certs, err := x509.ParseCertificates(certBytes) - if err != nil { - return fmt.Errorf("returned cert cannot be parsed: %w", err) - } - if len(certs) != 1 { - return fmt.Errorf("unexpected returned length of certificates: %d", len(certs)) - } - cert := certs[0] - - if cert.MaxPathLen != 0 { - return fmt.Errorf("max path length of %d does not match the requested of 3", cert.MaxPathLen) - } - if !cert.MaxPathLenZero { - return fmt.Errorf("max path length zero is not set") - } - - // We need to set these as they are filled in with unparsed values in the final cert - csrTemplate.Subject.Names = cert.Subject.Names - csrTemplate.Subject.ExtraNames = cert.Subject.ExtraNames - - switch { - case !reflect.DeepEqual(cert.Subject, csrTemplate.Subject): - return fmt.Errorf("cert subject\n%#v\ndoes not match csr subject\n%#v\n", cert.Subject, csrTemplate.Subject) - case !reflect.DeepEqual(cert.DNSNames, csrTemplate.DNSNames): - return fmt.Errorf("cert dns names\n%#v\ndoes not match csr dns names\n%#v\n", cert.DNSNames, csrTemplate.DNSNames) - case !reflect.DeepEqual(cert.EmailAddresses, csrTemplate.EmailAddresses): - return fmt.Errorf("cert email addresses\n%#v\ndoes not match csr email addresses\n%#v\n", cert.EmailAddresses, csrTemplate.EmailAddresses) - case !reflect.DeepEqual(cert.IPAddresses, csrTemplate.IPAddresses): - return fmt.Errorf("cert ip addresses\n%#v\ndoes not match csr ip addresses\n%#v\n", cert.IPAddresses, csrTemplate.IPAddresses) - } - return nil - }, - }, - } - return ret -} - -func generateTestCsr(t *testing.T, keyType certutil.PrivateKeyType, keyBits int) (x509.CertificateRequest, string) { - t.Helper() - - csrTemplate := x509.CertificateRequest{ - Subject: pkix.Name{ - Country: []string{"MyCountry"}, - PostalCode: []string{"MyPostalCode"}, - SerialNumber: "MySerialNumber", - CommonName: "my@example.com", - }, - DNSNames: []string{ - "name1.example.com", - "name2.example.com", - "name3.example.com", - }, - EmailAddresses: []string{ - "name1@example.com", - "name2@example.com", - "name3@example.com", - }, - IPAddresses: []net.IP{ - net.ParseIP("::ff:1:2:3:4"), - net.ParseIP("::ff:5:6:7:8"), - }, - } - - _, _, csrPem := generateCSR(t, &csrTemplate, string(keyType), keyBits) - return csrTemplate, csrPem -} - -// Generates steps to test out various role permutations -func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep { - roleVals := roleEntry{ - MaxTTL: 12 * time.Hour, - KeyType: "rsa", - KeyBits: 2048, - RequireCN: true, - AllowWildcardCertificates: new(bool), - } - *roleVals.AllowWildcardCertificates = true - - issueVals := certutil.IssueData{} - ret := []logicaltest.TestStep{} - - roleTestStep := logicaltest.TestStep{ - Operation: logical.UpdateOperation, - Path: "roles/test", - } - var issueTestStep logicaltest.TestStep - if useCSRs { - issueTestStep = logicaltest.TestStep{ - Operation: logical.UpdateOperation, - Path: "sign/test", - } - } else { - issueTestStep = logicaltest.TestStep{ - Operation: logical.UpdateOperation, - Path: "issue/test", - } - } - - generatedRSAKeys := map[int]crypto.Signer{} - generatedECKeys := map[int]crypto.Signer{} - generatedEdKeys := map[int]crypto.Signer{} - /* - // For the number of tests being run, a seed of 1 has been tested - // to hit all of the various values below. However, for normal - // testing we use a randomized time for maximum fuzziness. - */ - var seed int64 = 1 - fixedSeed := os.Getenv("VAULT_PKITESTS_FIXED_SEED") - if len(fixedSeed) == 0 { - seed = time.Now().UnixNano() - } else { - var err error - seed, err = strconv.ParseInt(fixedSeed, 10, 64) - if err != nil { - t.Fatalf("error parsing fixed seed of %s: %v", fixedSeed, err) - } - } - mathRand := mathrand.New(mathrand.NewSource(seed)) - // t.Logf("seed under test: %v", seed) - - // Used by tests not toggling common names to turn off the behavior of random key bit fuzziness - keybitSizeRandOff := false - - genericErrorOkCheck := func(resp *logical.Response) error { - if resp.IsError() { - return nil - } - return fmt.Errorf("expected an error, but did not seem to get one") - } - - // Adds tests with the currently configured issue/role information - addTests := func(testCheck logicaltest.TestCheckFunc) { - stepCount++ - // t.Logf("test step %d\nrole vals: %#v\n", stepCount, roleVals) - stepCount++ - // t.Logf("test step %d\nissue vals: %#v\n", stepCount, issueTestStep) - roleTestStep.Data = roleVals.ToResponseData() - roleTestStep.Data["generate_lease"] = false - ret = append(ret, roleTestStep) - issueTestStep.Data = structs.New(issueVals).Map() - switch { - case issueTestStep.ErrorOk: - issueTestStep.Check = genericErrorOkCheck - case testCheck != nil: - issueTestStep.Check = testCheck - default: - issueTestStep.Check = nil - } - ret = append(ret, issueTestStep) - } - - getCountryCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - expected := strutil.RemoveDuplicates(role.Country, true) - if !reflect.DeepEqual(cert.Subject.Country, expected) { - return fmt.Errorf("error: returned certificate has Country of %s but %s was specified in the role", cert.Subject.Country, expected) - } - return nil - } - } - - getOuCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - expected := strutil.RemoveDuplicatesStable(role.OU, true) - if !reflect.DeepEqual(cert.Subject.OrganizationalUnit, expected) { - return fmt.Errorf("error: returned certificate has OU of %s but %s was specified in the role", cert.Subject.OrganizationalUnit, expected) - } - return nil - } - } - - getOrganizationCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - expected := strutil.RemoveDuplicates(role.Organization, true) - if !reflect.DeepEqual(cert.Subject.Organization, expected) { - return fmt.Errorf("error: returned certificate has Organization of %s but %s was specified in the role", cert.Subject.Organization, expected) - } - return nil - } - } - - getLocalityCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - expected := strutil.RemoveDuplicates(role.Locality, true) - if !reflect.DeepEqual(cert.Subject.Locality, expected) { - return fmt.Errorf("error: returned certificate has Locality of %s but %s was specified in the role", cert.Subject.Locality, expected) - } - return nil - } - } - - getProvinceCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - expected := strutil.RemoveDuplicates(role.Province, true) - if !reflect.DeepEqual(cert.Subject.Province, expected) { - return fmt.Errorf("error: returned certificate has Province of %s but %s was specified in the role", cert.Subject.Province, expected) - } - return nil - } - } - - getStreetAddressCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - expected := strutil.RemoveDuplicates(role.StreetAddress, true) - if !reflect.DeepEqual(cert.Subject.StreetAddress, expected) { - return fmt.Errorf("error: returned certificate has StreetAddress of %s but %s was specified in the role", cert.Subject.StreetAddress, expected) - } - return nil - } - } - - getPostalCodeCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - expected := strutil.RemoveDuplicates(role.PostalCode, true) - if !reflect.DeepEqual(cert.Subject.PostalCode, expected) { - return fmt.Errorf("error: returned certificate has PostalCode of %s but %s was specified in the role", cert.Subject.PostalCode, expected) - } - return nil - } - } - - getNotBeforeCheck := func(role roleEntry) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - - actualDiff := time.Since(cert.NotBefore) - certRoleDiff := (role.NotBeforeDuration - actualDiff).Truncate(time.Second) - // These times get truncated, so give a 1 second buffer on each side - if certRoleDiff >= -1*time.Second && certRoleDiff <= 1*time.Second { - return nil - } - return fmt.Errorf("validity period out of range diff: %v", certRoleDiff) - } - } - - // Returns a TestCheckFunc that performs various validity checks on the - // returned certificate information, mostly within checkCertsAndPrivateKey - getCnCheck := func(name string, role roleEntry, key crypto.Signer, usage x509.KeyUsage, extUsage x509.ExtKeyUsage, validity time.Duration) logicaltest.TestCheckFunc { - var certBundle certutil.CertBundle - return func(resp *logical.Response) error { - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := checkCertsAndPrivateKey(role.KeyType, key, usage, extUsage, validity, &certBundle) - if err != nil { - return fmt.Errorf("error checking generated certificate: %s", err) - } - cert := parsedCertBundle.Certificate - if cert.Subject.CommonName != name { - return fmt.Errorf("error: returned certificate has CN of %s but %s was requested", cert.Subject.CommonName, name) - } - if strings.Contains(cert.Subject.CommonName, "@") { - if len(cert.DNSNames) != 0 || len(cert.EmailAddresses) != 1 { - return fmt.Errorf("error: found more than one DNS SAN or not one Email SAN but only one was requested, cert.DNSNames = %#v, cert.EmailAddresses = %#v", cert.DNSNames, cert.EmailAddresses) - } - } else { - if len(cert.DNSNames) != 1 || len(cert.EmailAddresses) != 0 { - return fmt.Errorf("error: found more than one Email SAN or not one DNS SAN but only one was requested, cert.DNSNames = %#v, cert.EmailAddresses = %#v", cert.DNSNames, cert.EmailAddresses) - } - } - var retName string - if len(cert.DNSNames) > 0 { - retName = cert.DNSNames[0] - } - if len(cert.EmailAddresses) > 0 { - retName = cert.EmailAddresses[0] - } - if retName != name { - // Check IDNA - p := idna.New( - idna.StrictDomainName(true), - idna.VerifyDNSLength(true), - ) - converted, err := p.ToUnicode(retName) - if err != nil { - t.Fatal(err) - } - if converted != name { - return fmt.Errorf("error: returned certificate has a DNS SAN of %s (from idna: %s) but %s was requested", retName, converted, name) - } - } - return nil - } - } - - type csrPlan struct { - errorOk bool - roleKeyBits int - cert string - privKey crypto.Signer - } - - getCsr := func(keyType string, keyBits int, csrTemplate *x509.CertificateRequest) (*pem.Block, crypto.Signer) { - var privKey crypto.Signer - var ok bool - switch keyType { - case "rsa": - privKey, ok = generatedRSAKeys[keyBits] - if !ok { - privKey, _ = rsa.GenerateKey(rand.Reader, keyBits) - generatedRSAKeys[keyBits] = privKey - } - - case "ec": - var curve elliptic.Curve - - switch keyBits { - case 224: - curve = elliptic.P224() - case 256: - curve = elliptic.P256() - case 384: - curve = elliptic.P384() - case 521: - curve = elliptic.P521() - } - - privKey, ok = generatedECKeys[keyBits] - if !ok { - privKey, _ = ecdsa.GenerateKey(curve, rand.Reader) - generatedECKeys[keyBits] = privKey - } - - case "ed25519": - privKey, ok = generatedEdKeys[keyBits] - if !ok { - _, privKey, _ = ed25519.GenerateKey(rand.Reader) - generatedEdKeys[keyBits] = privKey - } - - default: - panic("invalid key type: " + keyType) - } - - csr, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privKey) - if err != nil { - t.Fatalf("Error creating certificate request: %s", err) - } - block := pem.Block{ - Type: "CERTIFICATE REQUEST", - Bytes: csr, - } - return &block, privKey - } - - getRandCsr := func(keyType string, errorOk bool, csrTemplate *x509.CertificateRequest) csrPlan { - rsaKeyBits := []int{2048, 3072, 4096} - ecKeyBits := []int{224, 256, 384, 521} - plan := csrPlan{errorOk: errorOk} - - var testBitSize int - switch keyType { - case "rsa": - plan.roleKeyBits = rsaKeyBits[mathRand.Int()%len(rsaKeyBits)] - testBitSize = plan.roleKeyBits - - // If we don't expect an error already, randomly choose a - // key size and expect an error if it's less than the role - // setting - if !keybitSizeRandOff && !errorOk { - testBitSize = rsaKeyBits[mathRand.Int()%len(rsaKeyBits)] - } - - if testBitSize < plan.roleKeyBits { - plan.errorOk = true - } - - case "ec": - plan.roleKeyBits = ecKeyBits[mathRand.Int()%len(ecKeyBits)] - testBitSize = plan.roleKeyBits - - // If we don't expect an error already, randomly choose a - // key size and expect an error if it's less than the role - // setting - if !keybitSizeRandOff && !errorOk { - testBitSize = ecKeyBits[mathRand.Int()%len(ecKeyBits)] - } - - if testBitSize < plan.roleKeyBits { - plan.errorOk = true - } - - default: - panic("invalid key type: " + keyType) - } - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - t.Logf("roleKeyBits=%d testBitSize=%d errorOk=%v", plan.roleKeyBits, testBitSize, plan.errorOk) - } - - block, privKey := getCsr(keyType, testBitSize, csrTemplate) - plan.cert = strings.TrimSpace(string(pem.EncodeToMemory(block))) - plan.privKey = privKey - return plan - } - - // Common names to test with the various role flags toggled - var commonNames struct { - Localhost bool `structs:"localhost"` - BareDomain bool `structs:"example.com"` - SecondDomain bool `structs:"foobar.com"` - SubDomain bool `structs:"foo.example.com"` - Wildcard bool `structs:"*.example.com"` - SubSubdomain bool `structs:"foo.bar.example.com"` - SubSubdomainWildcard bool `structs:"*.bar.example.com"` - GlobDomain bool `structs:"fooexample.com"` - IDN bool `structs:"daɪˈɛrɨsɨs"` - AnyHost bool `structs:"porkslap.beer"` - } - - // Adds a series of tests based on the current selection of - // allowed common names; contains some (seeded) randomness - // - // This allows for a variety of common names to be tested in various - // combinations with allowed toggles of the role - addCnTests := func() { - cnMap := structs.New(commonNames).Map() - for name, allowedInt := range cnMap { - roleVals.KeyType = "rsa" - roleVals.KeyBits = 2048 - if mathRand.Int()%3 == 1 { - roleVals.KeyType = "ec" - roleVals.KeyBits = 224 - } - - roleVals.ServerFlag = false - roleVals.ClientFlag = false - roleVals.CodeSigningFlag = false - roleVals.EmailProtectionFlag = false - - var usage []string - if mathRand.Int()%2 == 1 { - usage = append(usage, "DigitalSignature") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "ContentCoMmitment") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "KeyEncipherment") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "DataEncipherment") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "KeyAgreemEnt") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "CertSign") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "CRLSign") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "EncipherOnly") - } - if mathRand.Int()%2 == 1 { - usage = append(usage, "DecipherOnly") - } - - roleVals.KeyUsage = usage - parsedKeyUsage := parseKeyUsages(roleVals.KeyUsage) - if parsedKeyUsage == 0 && len(usage) != 0 { - panic("parsed key usages was zero") - } - - var extUsage x509.ExtKeyUsage - i := mathRand.Int() % 4 - switch { - case i == 0: - // Punt on this for now since I'm not clear the actual proper - // way to format these - if name != "daɪˈɛrɨsɨs" { - extUsage = x509.ExtKeyUsageEmailProtection - roleVals.EmailProtectionFlag = true - break - } - fallthrough - case i == 1: - extUsage = x509.ExtKeyUsageServerAuth - roleVals.ServerFlag = true - case i == 2: - extUsage = x509.ExtKeyUsageClientAuth - roleVals.ClientFlag = true - default: - extUsage = x509.ExtKeyUsageCodeSigning - roleVals.CodeSigningFlag = true - } - - allowed := allowedInt.(bool) - issueVals.CommonName = name - if roleVals.EmailProtectionFlag { - if !strings.HasPrefix(name, "*") { - issueVals.CommonName = "user@" + issueVals.CommonName - } - } - - issueTestStep.ErrorOk = !allowed - - validity := roleVals.MaxTTL - - if useCSRs { - templ := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: issueVals.CommonName, - }, - } - plan := getRandCsr(roleVals.KeyType, issueTestStep.ErrorOk, templ) - issueVals.CSR = plan.cert - roleVals.KeyBits = plan.roleKeyBits - issueTestStep.ErrorOk = plan.errorOk - - addTests(getCnCheck(issueVals.CommonName, roleVals, plan.privKey, x509.KeyUsage(parsedKeyUsage), extUsage, validity)) - } else { - addTests(getCnCheck(issueVals.CommonName, roleVals, nil, x509.KeyUsage(parsedKeyUsage), extUsage, validity)) - } - } - } - - funcs := []interface{}{ - addCnTests, getCnCheck, getCountryCheck, getLocalityCheck, getNotBeforeCheck, - getOrganizationCheck, getOuCheck, getPostalCodeCheck, getRandCsr, getStreetAddressCheck, - getProvinceCheck, - } - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - t.Logf("funcs=%d", len(funcs)) - } - - // Common Name tests - { - // common_name not provided - issueVals.CommonName = "" - issueTestStep.ErrorOk = true - addTests(nil) - - // Nothing is allowed - addCnTests() - - roleVals.AllowLocalhost = true - commonNames.Localhost = true - addCnTests() - - roleVals.AllowedDomains = []string{"foobar.com"} - addCnTests() - - roleVals.AllowedDomains = []string{"example.com"} - roleVals.AllowSubdomains = true - commonNames.SubDomain = true - commonNames.Wildcard = true - commonNames.SubSubdomain = true - commonNames.SubSubdomainWildcard = true - addCnTests() - - roleVals.AllowedDomains = []string{"foobar.com", "example.com"} - commonNames.SecondDomain = true - roleVals.AllowBareDomains = true - commonNames.BareDomain = true - addCnTests() - - roleVals.AllowedDomains = []string{"foobar.com", "*example.com"} - roleVals.AllowGlobDomains = true - commonNames.GlobDomain = true - addCnTests() - - roleVals.AllowAnyName = true - roleVals.EnforceHostnames = true - commonNames.AnyHost = true - commonNames.IDN = true - addCnTests() - - roleVals.EnforceHostnames = false - addCnTests() - - // Ensure that we end up with acceptable key sizes since they won't be - // toggled any longer - keybitSizeRandOff = true - addCnTests() - } - // Country tests - { - roleVals.Country = []string{"foo"} - addTests(getCountryCheck(roleVals)) - - roleVals.Country = []string{"foo", "bar"} - addTests(getCountryCheck(roleVals)) - } - // OU tests - { - roleVals.OU = []string{"foo"} - addTests(getOuCheck(roleVals)) - - roleVals.OU = []string{"bar", "foo"} - addTests(getOuCheck(roleVals)) - } - // Organization tests - { - roleVals.Organization = []string{"system:masters"} - addTests(getOrganizationCheck(roleVals)) - - roleVals.Organization = []string{"foo", "bar"} - addTests(getOrganizationCheck(roleVals)) - } - // Locality tests - { - roleVals.Locality = []string{"foo"} - addTests(getLocalityCheck(roleVals)) - - roleVals.Locality = []string{"foo", "bar"} - addTests(getLocalityCheck(roleVals)) - } - // Province tests - { - roleVals.Province = []string{"foo"} - addTests(getProvinceCheck(roleVals)) - - roleVals.Province = []string{"foo", "bar"} - addTests(getProvinceCheck(roleVals)) - } - // StreetAddress tests - { - roleVals.StreetAddress = []string{"123 foo street"} - addTests(getStreetAddressCheck(roleVals)) - - roleVals.StreetAddress = []string{"123 foo street", "456 bar avenue"} - addTests(getStreetAddressCheck(roleVals)) - } - // PostalCode tests - { - roleVals.PostalCode = []string{"f00"} - addTests(getPostalCodeCheck(roleVals)) - - roleVals.PostalCode = []string{"f00", "b4r"} - addTests(getPostalCodeCheck(roleVals)) - } - // NotBefore tests - { - roleVals.NotBeforeDuration = 10 * time.Second - addTests(getNotBeforeCheck(roleVals)) - - roleVals.NotBeforeDuration = 30 * time.Second - addTests(getNotBeforeCheck(roleVals)) - - roleVals.NotBeforeDuration = 0 - } - - // IP SAN tests - { - getIpCheck := func(expectedIp ...net.IP) logicaltest.TestCheckFunc { - return func(resp *logical.Response) error { - var certBundle certutil.CertBundle - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error parsing cert bundle: %s", err) - } - cert := parsedCertBundle.Certificate - var expected []net.IP - expected = append(expected, expectedIp...) - if diff := deep.Equal(cert.IPAddresses, expected); len(diff) > 0 { - return fmt.Errorf("wrong SAN IPs, diff: %v", diff) - } - return nil - } - } - addIPSANTests := func(useCSRs, useCSRSANs, allowIPSANs, errorOk bool, ipSANs string, csrIPSANs []net.IP, check logicaltest.TestCheckFunc) { - if useCSRs { - csrTemplate := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: issueVals.CommonName, - }, - IPAddresses: csrIPSANs, - } - block, _ := getCsr(roleVals.KeyType, roleVals.KeyBits, csrTemplate) - issueVals.CSR = strings.TrimSpace(string(pem.EncodeToMemory(block))) - } - oldRoleVals, oldIssueVals, oldIssueTestStep := roleVals, issueVals, issueTestStep - roleVals.UseCSRSANs = useCSRSANs - roleVals.AllowIPSANs = allowIPSANs - issueVals.CommonName = "someone@example.com" - issueVals.IPSANs = ipSANs - issueTestStep.ErrorOk = errorOk - addTests(check) - roleVals, issueVals, issueTestStep = oldRoleVals, oldIssueVals, oldIssueTestStep - } - roleVals.AllowAnyName = true - roleVals.EnforceHostnames = true - roleVals.AllowLocalhost = true - roleVals.UseCSRCommonName = true - commonNames.Localhost = true - - netip1, netip2 := net.IP{127, 0, 0, 1}, net.IP{170, 171, 172, 173} - textip1, textip3 := "127.0.0.1", "::1" - - // IPSANs not allowed and not provided, should not be an error. - addIPSANTests(useCSRs, false, false, false, "", nil, getIpCheck()) - - // IPSANs not allowed, valid IPSANs provided, should be an error. - addIPSANTests(useCSRs, false, false, true, textip1+","+textip3, nil, nil) - - // IPSANs allowed, bogus IPSANs provided, should be an error. - addIPSANTests(useCSRs, false, true, true, "foobar", nil, nil) - - // Given IPSANs as API argument and useCSRSANs false, CSR arg ignored. - addIPSANTests(useCSRs, false, true, false, textip1, - []net.IP{netip2}, getIpCheck(netip1)) - - if useCSRs { - // IPSANs not allowed, valid IPSANs provided via CSR, should be an error. - addIPSANTests(useCSRs, true, false, true, "", []net.IP{netip1}, nil) - - // Given IPSANs as both API and CSR arguments and useCSRSANs=true, API arg ignored. - addIPSANTests(useCSRs, true, true, false, textip3, - []net.IP{netip1, netip2}, getIpCheck(netip1, netip2)) - } - } - - { - getOtherCheck := func(expectedOthers ...otherNameUtf8) logicaltest.TestCheckFunc { - return func(resp *logical.Response) error { - var certBundle certutil.CertBundle - err := mapstructure.Decode(resp.Data, &certBundle) - if err != nil { - return err - } - parsedCertBundle, err := certBundle.ToParsedCertBundle() - if err != nil { - return fmt.Errorf("error parsing cert bundle: %s", err) - } - cert := parsedCertBundle.Certificate - foundOthers, err := getOtherSANsFromX509Extensions(cert.Extensions) - if err != nil { - return err - } - var expected []otherNameUtf8 - expected = append(expected, expectedOthers...) - if diff := deep.Equal(foundOthers, expected); len(diff) > 0 { - return fmt.Errorf("wrong SAN IPs, diff: %v", diff) - } - return nil - } - } - - addOtherSANTests := func(useCSRs, useCSRSANs bool, allowedOtherSANs []string, errorOk bool, otherSANs []string, csrOtherSANs []otherNameUtf8, check logicaltest.TestCheckFunc) { - otherSansMap := func(os []otherNameUtf8) map[string][]string { - ret := make(map[string][]string) - for _, o := range os { - ret[o.oid] = append(ret[o.oid], o.value) - } - return ret - } - if useCSRs { - csrTemplate := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: issueVals.CommonName, - }, - } - if err := handleOtherCSRSANs(csrTemplate, otherSansMap(csrOtherSANs)); err != nil { - t.Fatal(err) - } - block, _ := getCsr(roleVals.KeyType, roleVals.KeyBits, csrTemplate) - issueVals.CSR = strings.TrimSpace(string(pem.EncodeToMemory(block))) - } - oldRoleVals, oldIssueVals, oldIssueTestStep := roleVals, issueVals, issueTestStep - roleVals.UseCSRSANs = useCSRSANs - roleVals.AllowedOtherSANs = allowedOtherSANs - issueVals.CommonName = "someone@example.com" - issueVals.OtherSANs = strings.Join(otherSANs, ",") - issueTestStep.ErrorOk = errorOk - addTests(check) - roleVals, issueVals, issueTestStep = oldRoleVals, oldIssueVals, oldIssueTestStep - } - roleVals.AllowAnyName = true - roleVals.EnforceHostnames = true - roleVals.AllowLocalhost = true - roleVals.UseCSRCommonName = true - commonNames.Localhost = true - - newOtherNameUtf8 := func(s string) (ret otherNameUtf8) { - pieces := strings.Split(s, ";") - if len(pieces) == 2 { - piecesRest := strings.Split(pieces[1], ":") - if len(piecesRest) == 2 { - switch strings.ToUpper(piecesRest[0]) { - case "UTF-8", "UTF8": - return otherNameUtf8{oid: pieces[0], value: piecesRest[1]} - } - } - } - t.Fatalf("error parsing otherName: %q", s) - return - } - oid1 := "1.3.6.1.4.1.311.20.2.3" - oth1str := oid1 + ";utf8:devops@nope.com" - oth1 := newOtherNameUtf8(oth1str) - oth2 := otherNameUtf8{oid1, "me@example.com"} - // allowNone, allowAll := []string{}, []string{oid1 + ";UTF-8:*"} - allowNone, allowAll := []string{}, []string{"*"} - - // OtherSANs not allowed and not provided, should not be an error. - addOtherSANTests(useCSRs, false, allowNone, false, nil, nil, getOtherCheck()) - - // OtherSANs not allowed, valid OtherSANs provided, should be an error. - addOtherSANTests(useCSRs, false, allowNone, true, []string{oth1str}, nil, nil) - - // OtherSANs allowed, bogus OtherSANs provided, should be an error. - addOtherSANTests(useCSRs, false, allowAll, true, []string{"foobar"}, nil, nil) - - // Given OtherSANs as API argument and useCSRSANs false, CSR arg ignored. - addOtherSANTests(useCSRs, false, allowAll, false, []string{oth1str}, - []otherNameUtf8{oth2}, getOtherCheck(oth1)) - - if useCSRs { - // OtherSANs not allowed, valid OtherSANs provided via CSR, should be an error. - addOtherSANTests(useCSRs, true, allowNone, true, nil, []otherNameUtf8{oth1}, nil) - - // Given OtherSANs as both API and CSR arguments and useCSRSANs=true, API arg ignored. - addOtherSANTests(useCSRs, false, allowAll, false, []string{oth2.String()}, - []otherNameUtf8{oth1}, getOtherCheck(oth2)) - } - } - - // Lease tests - { - roleTestStep.ErrorOk = true - roleVals.Lease = "" - roleVals.MaxTTL = 0 - addTests(nil) - - roleVals.Lease = "12h" - roleVals.MaxTTL = 6 * time.Hour - addTests(nil) - - roleTestStep.ErrorOk = false - roleVals.TTL = 0 - roleVals.MaxTTL = 12 * time.Hour - } - - // Listing test - ret = append(ret, logicaltest.TestStep{ - Operation: logical.ListOperation, - Path: "roles/", - Check: func(resp *logical.Response) error { - if resp.Data == nil { - return fmt.Errorf("nil data") - } - - keysRaw, ok := resp.Data["keys"] - if !ok { - return fmt.Errorf("no keys found") - } - - keys, ok := keysRaw.([]string) - if !ok { - return fmt.Errorf("could not convert keys to a string list") - } - - if len(keys) != 1 { - return fmt.Errorf("unexpected keys length of %d", len(keys)) - } - - if keys[0] != "test" { - return fmt.Errorf("unexpected key value of %s", keys[0]) - } - - return nil - }, - }) - - return ret -} - -func TestRolesAltIssuer(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // Create two issuers. - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "root a - example.com", - "issuer_name": "root-a", - "key_type": "ec", - }) - require.NoError(t, err) - require.NotNil(t, resp) - rootAPem := resp.Data["certificate"].(string) - rootACert := parseCert(t, rootAPem) - - resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "root b - example.com", - "issuer_name": "root-b", - "key_type": "ec", - }) - require.NoError(t, err) - require.NotNil(t, resp) - rootBPem := resp.Data["certificate"].(string) - rootBCert := parseCert(t, rootBPem) - - // Create three roles: one with no assignment, one with explicit root-a, - // one with explicit root-b. - _, err = CBWrite(b, s, "roles/use-default", map[string]interface{}{ - "allow_any_name": true, - "enforce_hostnames": false, - "key_type": "ec", - }) - require.NoError(t, err) - - _, err = CBWrite(b, s, "roles/use-root-a", map[string]interface{}{ - "allow_any_name": true, - "enforce_hostnames": false, - "key_type": "ec", - "issuer_ref": "root-a", - }) - require.NoError(t, err) - - _, err = CBWrite(b, s, "roles/use-root-b", map[string]interface{}{ - "allow_any_name": true, - "enforce_hostnames": false, - "issuer_ref": "root-b", - }) - require.NoError(t, err) - - // Now issue certs against these roles. - resp, err = CBWrite(b, s, "issue/use-default", map[string]interface{}{ - "common_name": "testing", - "ttl": "5s", - }) - require.NoError(t, err) - leafPem := resp.Data["certificate"].(string) - leafCert := parseCert(t, leafPem) - err = leafCert.CheckSignatureFrom(rootACert) - require.NoError(t, err, "should be signed by root-a but wasn't") - - resp, err = CBWrite(b, s, "issue/use-root-a", map[string]interface{}{ - "common_name": "testing", - "ttl": "5s", - }) - require.NoError(t, err) - leafPem = resp.Data["certificate"].(string) - leafCert = parseCert(t, leafPem) - err = leafCert.CheckSignatureFrom(rootACert) - require.NoError(t, err, "should be signed by root-a but wasn't") - - resp, err = CBWrite(b, s, "issue/use-root-b", map[string]interface{}{ - "common_name": "testing", - "ttl": "5s", - }) - require.NoError(t, err) - leafPem = resp.Data["certificate"].(string) - leafCert = parseCert(t, leafPem) - err = leafCert.CheckSignatureFrom(rootBCert) - require.NoError(t, err, "should be signed by root-b but wasn't") - - // Update the default issuer to be root B and make sure that the - // use-default role updates. - _, err = CBWrite(b, s, "config/issuers", map[string]interface{}{ - "default": "root-b", - }) - require.NoError(t, err) - - resp, err = CBWrite(b, s, "issue/use-default", map[string]interface{}{ - "common_name": "testing", - "ttl": "5s", - }) - require.NoError(t, err) - leafPem = resp.Data["certificate"].(string) - leafCert = parseCert(t, leafPem) - err = leafCert.CheckSignatureFrom(rootBCert) - require.NoError(t, err, "should be signed by root-b but wasn't") -} - -func TestBackend_PathFetchValidRaw(t *testing.T) { - t.Parallel() - b, storage := CreateBackendWithStorage(t) - - resp, err := b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/generate/internal", - Storage: storage, - Data: map[string]interface{}{ - "common_name": "test.com", - "ttl": "6h", - }, - MountPoint: "pki/", - }) - require.NoError(t, err) - if resp != nil && resp.IsError() { - t.Fatalf("failed to generate root, %#v", resp) - } - rootCaAsPem := resp.Data["certificate"].(string) - - // Chain should contain the root. - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.ReadOperation, - Path: "ca_chain", - Storage: storage, - Data: map[string]interface{}{}, - MountPoint: "pki/", - }) - require.NoError(t, err) - if resp != nil && resp.IsError() { - t.Fatalf("failed read ca_chain, %#v", resp) - } - if strings.Count(string(resp.Data[logical.HTTPRawBody].([]byte)), rootCaAsPem) != 1 { - t.Fatalf("expected raw chain to contain the root cert") - } - - // The ca/pem should return us the actual CA... - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.ReadOperation, - Path: "ca/pem", - Storage: storage, - Data: map[string]interface{}{}, - MountPoint: "pki/", - }) - require.NoError(t, err) - if resp != nil && resp.IsError() { - t.Fatalf("failed read ca/pem, %#v", resp) - } - // check the raw cert matches the response body - if !bytes.Equal(resp.Data[logical.HTTPRawBody].([]byte), []byte(rootCaAsPem)) { - t.Fatalf("failed to get raw cert") - } - - _, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "roles/example", - Storage: storage, - Data: map[string]interface{}{ - "allowed_domains": "example.com", - "allow_subdomains": "true", - "max_ttl": "1h", - "no_store": "false", - }, - MountPoint: "pki/", - }) - require.NoError(t, err, "error setting up pki role: %v", err) - - // Now issue a short-lived certificate from our pki-external. - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "issue/example", - Storage: storage, - Data: map[string]interface{}{ - "common_name": "test.example.com", - "ttl": "5m", - }, - MountPoint: "pki/", - }) - require.NoError(t, err, "error issuing certificate: %v", err) - require.NotNil(t, resp, "got nil response from issuing request") - - issueCrtAsPem := resp.Data["certificate"].(string) - issuedCrt := parseCert(t, issueCrtAsPem) - expectedSerial := serialFromCert(issuedCrt) - expectedCert := []byte(issueCrtAsPem) - - // get der cert - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.ReadOperation, - Path: fmt.Sprintf("cert/%s/raw", expectedSerial), - Storage: storage, - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to get raw cert, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - - // check the raw cert matches the response body - rawBody := resp.Data[logical.HTTPRawBody].([]byte) - bodyAsPem := []byte(strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rawBody})))) - if !bytes.Equal(bodyAsPem, expectedCert) { - t.Fatalf("failed to get raw cert for serial number: %s", expectedSerial) - } - if resp.Data[logical.HTTPContentType] != "application/pkix-cert" { - t.Fatalf("failed to get raw cert content-type") - } - - // get pem - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.ReadOperation, - Path: fmt.Sprintf("cert/%s/raw/pem", expectedSerial), - Storage: storage, - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to get raw, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - - // check the pem cert matches the response body - if !bytes.Equal(resp.Data[logical.HTTPRawBody].([]byte), expectedCert) { - t.Fatalf("failed to get pem cert") - } - if resp.Data[logical.HTTPContentType] != "application/pem-certificate-chain" { - t.Fatalf("failed to get raw cert content-type") - } -} - -func TestBackend_PathFetchCertList(t *testing.T) { - t.Parallel() - // create the backend - b, storage := CreateBackendWithStorage(t) - - // generate root - rootData := map[string]interface{}{ - "common_name": "test.com", - "ttl": "6h", - } - - resp, err := b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/generate/internal", - Storage: storage, - Data: rootData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to generate root, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - - // config urls - urlsData := map[string]interface{}{ - "issuing_certificates": "http://127.0.0.1:8200/v1/pki/ca", - "crl_distribution_points": "http://127.0.0.1:8200/v1/pki/crl", - } - - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "config/urls", - Storage: storage, - Data: urlsData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to config urls, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - - // create a role entry - roleData := map[string]interface{}{ - "allowed_domains": "test.com", - "allow_subdomains": "true", - "max_ttl": "4h", - } - - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "roles/test-example", - Storage: storage, - Data: roleData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to create a role, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - - // issue some certs - i := 1 - for i < 10 { - certData := map[string]interface{}{ - "common_name": "example.test.com", - } - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "issue/test-example", - Storage: storage, - Data: certData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to issue a cert, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - - i = i + 1 - } - - // list certs - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.ListOperation, - Path: "certs", - Storage: storage, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to list certs, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - // check that the root and 9 additional certs are all listed - if len(resp.Data["keys"].([]string)) != 10 { - t.Fatalf("failed to list all 10 certs") - } - - // list certs/ - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.ListOperation, - Path: "certs/", - Storage: storage, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to list certs, %#v", resp) - } - if err != nil { - t.Fatal(err) - } - // check that the root and 9 additional certs are all listed - if len(resp.Data["keys"].([]string)) != 10 { - t.Fatalf("failed to list all 10 certs") - } -} - -func TestBackend_SignVerbatim(t *testing.T) { - t.Parallel() - testCases := []struct { - testName string - keyType string - }{ - {testName: "RSA", keyType: "rsa"}, - {testName: "ED25519", keyType: "ed25519"}, - {testName: "EC", keyType: "ec"}, - {testName: "Any", keyType: "any"}, - } - for _, tc := range testCases { - tc := tc - t.Run(tc.testName, func(t *testing.T) { - runTestSignVerbatim(t, tc.keyType) - }) - } -} - -func runTestSignVerbatim(t *testing.T, keyType string) { - // create the backend - b, storage := CreateBackendWithStorage(t) - - // generate root - rootData := map[string]interface{}{ - "common_name": "test.com", - "not_after": "9999-12-31T23:59:59Z", - } - - resp, err := b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/generate/internal", - Storage: storage, - Data: rootData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to generate root, %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - - // create a CSR and key - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatal(err) - } - csrReq := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "foo.bar.com", - }, - // Check that otherName extensions are not duplicated (see hashicorp/vault#16700). - // If these extensions are duplicated, sign-verbatim will fail when parsing the signed certificate on Go 1.19+ (see golang/go#50988). - // On older versions of Go this test will fail due to an explicit check for duplicate otherNames later in this test. - ExtraExtensions: []pkix.Extension{ - { - Id: oidExtensionSubjectAltName, - Critical: false, - Value: []byte{0x30, 0x26, 0xA0, 0x24, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x03, 0xA0, 0x16, 0x0C, 0x14, 0x75, 0x73, 0x65, 0x72, 0x6E, 0x61, 0x6D, 0x65, 0x40, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D}, - }, - }, - } - csr, err := x509.CreateCertificateRequest(rand.Reader, csrReq, key) - if err != nil { - t.Fatal(err) - } - if len(csr) == 0 { - t.Fatal("generated csr is empty") - } - pemCSR := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", - Bytes: csr, - }))) - if len(pemCSR) == 0 { - t.Fatal("pem csr is empty") - } - - signVerbatimData := map[string]interface{}{ - "csr": pemCSR, - } - if keyType == "rsa" { - signVerbatimData["signature_bits"] = 512 - } - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "sign-verbatim", - Storage: storage, - Data: signVerbatimData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to sign-verbatim basic CSR: %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - if resp.Secret != nil { - t.Fatal("secret is not nil") - } - - // create a role entry; we use this to check that sign-verbatim when used with a role is still honoring TTLs - roleData := map[string]interface{}{ - "ttl": "4h", - "max_ttl": "8h", - "key_type": keyType, - "not_before_duration": "2h", - } - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "roles/test", - Storage: storage, - Data: roleData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to create a role, %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "sign-verbatim/test", - Storage: storage, - Data: map[string]interface{}{ - "csr": pemCSR, - "ttl": "5h", - }, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to sign-verbatim ttl'd CSR: %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - if resp.Secret != nil { - t.Fatal("got a lease when we should not have") - } - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "sign-verbatim/test", - Storage: storage, - Data: map[string]interface{}{ - "csr": pemCSR, - "ttl": "12h", - }, - MountPoint: "pki/", - }) - if err != nil { - t.Fatal(err) - } - if resp != nil && resp.IsError() { - t.Fatalf(resp.Error().Error()) - } - if resp.Data == nil || resp.Data["certificate"] == nil { - t.Fatal("did not get expected data") - } - certString := resp.Data["certificate"].(string) - block, _ := pem.Decode([]byte(certString)) - if block == nil { - t.Fatal("nil pem block") - } - certs, err := x509.ParseCertificates(block.Bytes) - if err != nil { - t.Fatal(err) - } - if len(certs) != 1 { - t.Fatalf("expected a single cert, got %d", len(certs)) - } - cert := certs[0] - if math.Abs(float64(time.Now().Add(12*time.Hour).Unix()-cert.NotAfter.Unix())) < 10 { - t.Fatalf("sign-verbatim did not properly cap validity period (notAfter) on signed CSR: was %v vs requested %v but should've been %v", cert.NotAfter, time.Now().Add(12*time.Hour), time.Now().Add(8*time.Hour)) - } - if math.Abs(float64(time.Now().Add(-2*time.Hour).Unix()-cert.NotBefore.Unix())) > 10 { - t.Fatalf("sign-verbatim did not properly cap validity period (notBefore) on signed CSR: was %v vs expected %v", cert.NotBefore, time.Now().Add(-2*time.Hour)) - } - - // Now check signing a certificate using the not_after input using the Y10K value - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "sign-verbatim/test", - Storage: storage, - Data: map[string]interface{}{ - "csr": pemCSR, - "not_after": "9999-12-31T23:59:59Z", - }, - MountPoint: "pki/", - }) - if err != nil { - t.Fatal(err) - } - if resp != nil && resp.IsError() { - t.Fatalf(resp.Error().Error()) - } - if resp.Data == nil || resp.Data["certificate"] == nil { - t.Fatal("did not get expected data") - } - certString = resp.Data["certificate"].(string) - block, _ = pem.Decode([]byte(certString)) - if block == nil { - t.Fatal("nil pem block") - } - certs, err = x509.ParseCertificates(block.Bytes) - if err != nil { - t.Fatal(err) - } - if len(certs) != 1 { - t.Fatalf("expected a single cert, got %d", len(certs)) - } - cert = certs[0] - - // Fallback check for duplicate otherName, necessary on Go versions before 1.19. - // We assume that there is only one SAN in the original CSR and that it is an otherName. - san_count := 0 - for _, ext := range cert.Extensions { - if ext.Id.Equal(oidExtensionSubjectAltName) { - san_count += 1 - } - } - if san_count != 1 { - t.Fatalf("expected one SAN extension, got %d", san_count) - } - - notAfter := cert.NotAfter.Format(time.RFC3339) - if notAfter != "9999-12-31T23:59:59Z" { - t.Fatal(fmt.Errorf("not after from certificate is not matching with input parameter")) - } - - // now check that if we set generate-lease it takes it from the role and the TTLs match - roleData = map[string]interface{}{ - "ttl": "4h", - "max_ttl": "8h", - "generate_lease": true, - "key_type": keyType, - } - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "roles/test", - Storage: storage, - Data: roleData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to create a role, %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "sign-verbatim/test", - Storage: storage, - Data: map[string]interface{}{ - "csr": pemCSR, - "ttl": "5h", - }, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to sign-verbatim role-leased CSR: %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - if resp.Secret == nil { - t.Fatalf("secret is nil, response is %#v", *resp) - } - if math.Abs(float64(resp.Secret.TTL-(5*time.Hour))) > float64(5*time.Hour) { - t.Fatalf("ttl not default; wanted %v, got %v", b.System().DefaultLeaseTTL(), resp.Secret.TTL) - } -} - -func TestBackend_Root_Idempotency(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // This is a change within 1.11, we are no longer idempotent across generate/internal calls. - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - }) - require.NoError(t, err) - require.NotNil(t, resp, "expected ca info") - keyId1 := resp.Data["key_id"] - issuerId1 := resp.Data["issuer_id"] - cert := parseCert(t, resp.Data["certificate"].(string)) - certSkid := certutil.GetHexFormatted(cert.SubjectKeyId, ":") - - // -> Validate the SKID matches between the root cert and the key - resp, err = CBRead(b, s, "key/"+keyId1.(keyID).String()) - require.NoError(t, err) - require.NotNil(t, resp, "expected a response") - require.Equal(t, resp.Data["subject_key_id"], certSkid) - - resp, err = CBRead(b, s, "cert/ca_chain") - require.NoError(t, err, "error reading ca_chain: %v", err) - - r1Data := resp.Data - - // Calling generate/internal should generate a new CA as well. - resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - }) - require.NoError(t, err) - require.NotNil(t, resp, "expected ca info") - keyId2 := resp.Data["key_id"] - issuerId2 := resp.Data["issuer_id"] - cert = parseCert(t, resp.Data["certificate"].(string)) - certSkid = certutil.GetHexFormatted(cert.SubjectKeyId, ":") - - // -> Validate the SKID matches between the root cert and the key - resp, err = CBRead(b, s, "key/"+keyId2.(keyID).String()) - require.NoError(t, err) - require.NotNil(t, resp, "expected a response") - require.Equal(t, resp.Data["subject_key_id"], certSkid) - - // Make sure that we actually generated different issuer and key values - require.NotEqual(t, keyId1, keyId2) - require.NotEqual(t, issuerId1, issuerId2) - - // Now because the issued CA's have no links, the call to ca_chain should return the same data (ca chain from default) - resp, err = CBRead(b, s, "cert/ca_chain") - require.NoError(t, err, "error reading ca_chain: %v", err) - - r2Data := resp.Data - if !reflect.DeepEqual(r1Data, r2Data) { - t.Fatal("got different ca certs") - } - - // Now let's validate that the import bundle is idempotent. - pemBundleRootCA := rootCACertPEM + "\n" + rootCAKeyPEM - resp, err = CBWrite(b, s, "config/ca", map[string]interface{}{ - "pem_bundle": pemBundleRootCA, - }) - require.NoError(t, err) - require.NotNil(t, resp, "expected ca info") - firstImportedKeys := resp.Data["imported_keys"].([]string) - firstImportedIssuers := resp.Data["imported_issuers"].([]string) - - require.NotContains(t, firstImportedKeys, keyId1) - require.NotContains(t, firstImportedKeys, keyId2) - require.NotContains(t, firstImportedIssuers, issuerId1) - require.NotContains(t, firstImportedIssuers, issuerId2) - - // Performing this again should result in no key/issuer ids being imported/generated. - resp, err = CBWrite(b, s, "config/ca", map[string]interface{}{ - "pem_bundle": pemBundleRootCA, - }) - require.NoError(t, err) - require.NotNil(t, resp, "expected ca info") - secondImportedKeys := resp.Data["imported_keys"] - secondImportedIssuers := resp.Data["imported_issuers"] - - require.Nil(t, secondImportedKeys) - require.Nil(t, secondImportedIssuers) - - resp, err = CBDelete(b, s, "root") - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, 1, len(resp.Warnings)) - - // Make sure we can delete twice... - resp, err = CBDelete(b, s, "root") - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, 1, len(resp.Warnings)) - - _, err = CBRead(b, s, "cert/ca_chain") - require.Error(t, err, "expected an error fetching deleted ca_chain") - - // We should be able to import the same ca bundle as before and get a different key/issuer ids - resp, err = CBWrite(b, s, "config/ca", map[string]interface{}{ - "pem_bundle": pemBundleRootCA, - }) - require.NoError(t, err) - require.NotNil(t, resp, "expected ca info") - postDeleteImportedKeys := resp.Data["imported_keys"] - postDeleteImportedIssuers := resp.Data["imported_issuers"] - - // Make sure that we actually generated different issuer and key values, then the previous import - require.NotNil(t, postDeleteImportedKeys) - require.NotNil(t, postDeleteImportedIssuers) - require.NotEqual(t, postDeleteImportedKeys, firstImportedKeys) - require.NotEqual(t, postDeleteImportedIssuers, firstImportedIssuers) - - resp, err = CBRead(b, s, "cert/ca_chain") - require.NoError(t, err) - - caChainPostDelete := resp.Data - if reflect.DeepEqual(r1Data, caChainPostDelete) { - t.Fatal("ca certs from ca_chain were the same post delete, should have changed.") - } -} - -func TestBackend_SignIntermediate_AllowedPastCA(t *testing.T) { - t.Parallel() - b_root, s_root := CreateBackendWithStorage(t) - b_int, s_int := CreateBackendWithStorage(t) - var err error - - // Direct issuing from root - _, err = CBWrite(b_root, s_root, "root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - - _, err = CBWrite(b_root, s_root, "roles/test", map[string]interface{}{ - "allow_bare_domains": true, - "allow_subdomains": true, - }) - if err != nil { - t.Fatal(err) - } - - resp, err := CBWrite(b_int, s_int, "intermediate/generate/internal", map[string]interface{}{ - "common_name": "myint.com", - }) - require.Contains(t, resp.Data, "key_id") - intKeyId := resp.Data["key_id"].(keyID) - csr := resp.Data["csr"] - - resp, err = CBRead(b_int, s_int, "key/"+intKeyId.String()) - require.NoError(t, err) - require.NotNil(t, resp, "expected a response") - intSkid := resp.Data["subject_key_id"].(string) - - if err != nil { - t.Fatal(err) - } - - _, err = CBWrite(b_root, s_root, "sign/test", map[string]interface{}{ - "common_name": "myint.com", - "csr": csr, - "ttl": "60h", - }) - if err == nil { - t.Fatal("expected error") - } - - _, err = CBWrite(b_root, s_root, "sign-verbatim/test", map[string]interface{}{ - "common_name": "myint.com", - "other_sans": "1.3.6.1.4.1.311.20.2.3;utf8:caadmin@example.com", - "csr": csr, - "ttl": "60h", - }) - if err == nil { - t.Fatal("expected error") - } - - resp, err = CBWrite(b_root, s_root, "root/sign-intermediate", map[string]interface{}{ - "common_name": "myint.com", - "other_sans": "1.3.6.1.4.1.311.20.2.3;utf8:caadmin@example.com", - "csr": csr, - "ttl": "60h", - }) - if err != nil { - t.Fatalf("got error: %v", err) - } - if resp == nil { - t.Fatal("got nil response") - } - if len(resp.Warnings) == 0 { - t.Fatalf("expected warnings, got %#v", *resp) - } - - cert := parseCert(t, resp.Data["certificate"].(string)) - certSkid := certutil.GetHexFormatted(cert.SubjectKeyId, ":") - require.Equal(t, intSkid, certSkid) -} - -func TestBackend_ConsulSignLeafWithLegacyRole(t *testing.T) { - t.Parallel() - // create the backend - b, s := CreateBackendWithStorage(t) - - // generate root - data, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - require.NoError(t, err, "failed generating internal root cert") - rootCaPem := data.Data["certificate"].(string) - - // Create a signing role like Consul did with the default args prior to Vault 1.10 - _, err = CBWrite(b, s, "roles/test", map[string]interface{}{ - "allow_any_name": true, - "allowed_serial_numbers": []string{"MySerialNumber"}, - "key_type": "any", - "key_bits": "2048", - "signature_bits": "256", - }) - require.NoError(t, err, "failed creating legacy role") - - _, csrPem := generateTestCsr(t, certutil.ECPrivateKey, 256) - data, err = CBWrite(b, s, "sign/test", map[string]interface{}{ - "csr": csrPem, - }) - require.NoError(t, err, "failed signing csr") - certAsPem := data.Data["certificate"].(string) - - signedCert := parseCert(t, certAsPem) - rootCert := parseCert(t, rootCaPem) - requireSignedBy(t, signedCert, rootCert) -} - -func TestBackend_SignSelfIssued(t *testing.T) { - t.Parallel() - // create the backend - b, storage := CreateBackendWithStorage(t) - - // generate root - rootData := map[string]interface{}{ - "common_name": "test.com", - "ttl": "172800", - } - - resp, err := b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/generate/internal", - Storage: storage, - Data: rootData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to generate root, %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatal(err) - } - - template := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "foo.bar.com", - }, - SerialNumber: big.NewInt(1234), - IsCA: false, - BasicConstraintsValid: true, - } - - ss, _ := getSelfSigned(t, template, template, key) - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/sign-self-issued", - Storage: storage, - Data: map[string]interface{}{ - "certificate": ss, - }, - MountPoint: "pki/", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("got nil response") - } - if !resp.IsError() { - t.Fatalf("expected error due to non-CA; got: %#v", *resp) - } - - // Set CA to true, but leave issuer alone - template.IsCA = true - - issuer := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "bar.foo.com", - }, - SerialNumber: big.NewInt(2345), - IsCA: true, - BasicConstraintsValid: true, - } - ss, ssCert := getSelfSigned(t, template, issuer, key) - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/sign-self-issued", - Storage: storage, - Data: map[string]interface{}{ - "certificate": ss, - }, - MountPoint: "pki/", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("got nil response") - } - if !resp.IsError() { - t.Fatalf("expected error due to different issuer; cert info is\nIssuer\n%#v\nSubject\n%#v\n", ssCert.Issuer, ssCert.Subject) - } - - ss, _ = getSelfSigned(t, template, template, key) - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/sign-self-issued", - Storage: storage, - Data: map[string]interface{}{ - "certificate": ss, - }, - MountPoint: "pki/", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("got nil response") - } - if resp.IsError() { - t.Fatalf("error in response: %s", resp.Error().Error()) - } - - newCertString := resp.Data["certificate"].(string) - block, _ := pem.Decode([]byte(newCertString)) - newCert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - t.Fatal(err) - } - - sc := b.makeStorageContext(context.Background(), storage) - signingBundle, err := sc.fetchCAInfo(defaultRef, ReadOnlyUsage) - if err != nil { - t.Fatal(err) - } - if reflect.DeepEqual(newCert.Subject, newCert.Issuer) { - t.Fatal("expected different subject/issuer") - } - if !reflect.DeepEqual(newCert.Issuer, signingBundle.Certificate.Subject) { - t.Fatalf("expected matching issuer/CA subject\n\nIssuer:\n%#v\nSubject:\n%#v\n", newCert.Issuer, signingBundle.Certificate.Subject) - } - if bytes.Equal(newCert.AuthorityKeyId, newCert.SubjectKeyId) { - t.Fatal("expected different authority/subject") - } - if !bytes.Equal(newCert.AuthorityKeyId, signingBundle.Certificate.SubjectKeyId) { - t.Fatal("expected authority on new cert to be same as signing subject") - } - if newCert.Subject.CommonName != "foo.bar.com" { - t.Fatalf("unexpected common name on new cert: %s", newCert.Subject.CommonName) - } -} - -// TestBackend_SignSelfIssued_DifferentTypes tests the functionality of the -// require_matching_certificate_algorithms flag. -func TestBackend_SignSelfIssued_DifferentTypes(t *testing.T) { - t.Parallel() - // create the backend - b, storage := CreateBackendWithStorage(t) - - // generate root - rootData := map[string]interface{}{ - "common_name": "test.com", - "ttl": "172800", - "key_type": "ec", - "key_bits": "521", - } - - resp, err := b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/generate/internal", - Storage: storage, - Data: rootData, - MountPoint: "pki/", - }) - if resp != nil && resp.IsError() { - t.Fatalf("failed to generate root, %#v", *resp) - } - if err != nil { - t.Fatal(err) - } - - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatal(err) - } - - template := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "foo.bar.com", - }, - SerialNumber: big.NewInt(1234), - IsCA: true, - BasicConstraintsValid: true, - } - - // Tests absent the flag - ss, _ := getSelfSigned(t, template, template, key) - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/sign-self-issued", - Storage: storage, - Data: map[string]interface{}{ - "certificate": ss, - }, - MountPoint: "pki/", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("got nil response") - } - - // Set CA to true, but leave issuer alone - template.IsCA = true - - // Tests with flag present but false - ss, _ = getSelfSigned(t, template, template, key) - resp, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/sign-self-issued", - Storage: storage, - Data: map[string]interface{}{ - "certificate": ss, - "require_matching_certificate_algorithms": false, - }, - MountPoint: "pki/", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("got nil response") - } - - // Test with flag present and true - ss, _ = getSelfSigned(t, template, template, key) - _, err = b.HandleRequest(context.Background(), &logical.Request{ - Operation: logical.UpdateOperation, - Path: "root/sign-self-issued", - Storage: storage, - Data: map[string]interface{}{ - "certificate": ss, - "require_matching_certificate_algorithms": true, - }, - MountPoint: "pki/", - }) - if err == nil { - t.Fatal("expected error due to mismatched algorithms") - } -} - -// 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 output 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) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - var err error - var resp *logical.Response - var certStr string - var block *pem.Block - var cert *x509.Certificate - - _, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - - _, err = CBWrite(b, s, "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 = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "foobar.com", - "ip_sans": "1.2.3.4", - "alt_names": "foobar.com,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 len(cert.DNSNames) != 3 || - cert.DNSNames[0] != "bar.foobar.com" || - cert.DNSNames[1] != "foo.foobar.com" || - cert.DNSNames[2] != "foobar.com" { - t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) - } - - // First test some bad stuff that shouldn't work - _, err = CBWrite(b, s, "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") - } - - _, err = CBWrite(b, s, "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") - } - - _, err = CBWrite(b, s, "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") - } - - _, err = CBWrite(b, s, "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") - } - - _, err = CBWrite(b, s, "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 = CBWrite(b, s, "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 len(cert.DNSNames) != 3 || - cert.DNSNames[0] != "bar.foobar.com" || - cert.DNSNames[1] != "foo.foobar.com" || - cert.DNSNames[2] != "foobar.com" { - t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) - } - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - t.Logf("certificate 1 to check:\n%s", certStr) - } - - // Valid for second possibility - resp, err = CBWrite(b, s, "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 len(cert.DNSNames) != 3 || - cert.DNSNames[0] != "bar.foobar.com" || - cert.DNSNames[1] != "foo.foobar.com" || - cert.DNSNames[2] != "foobar.com" { - t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) - } - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - t.Logf("certificate 2 to check:\n%s", certStr) - } - - // Valid for both - oid1, type1, val1 := "1.3.6.1.4.1.311.20.2.3", "utf8", "devops@nope.com" - oid2, type2, val2 := "1.3.6.1.4.1.311.20.2.4", "utf-8", "d234e@foobar.com" - otherNames := []string{ - fmt.Sprintf("%s;%s:%s", oid1, type1, val1), - fmt.Sprintf("%s;%s:%s", oid2, type2, val2), - } - resp, err = CBWrite(b, s, "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": strings.Join(otherNames, ","), - }) - 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 len(cert.DNSNames) != 3 || - cert.DNSNames[0] != "bar.foobar.com" || - cert.DNSNames[1] != "foo.foobar.com" || - cert.DNSNames[2] != "foobar.com" { - t.Fatalf("unexpected DNS SANs %v", cert.DNSNames) - } - expectedOtherNames := []otherNameUtf8{{oid1, val1}, {oid2, val2}} - foundOtherNames, err := getOtherSANsFromX509Extensions(cert.Extensions) - if err != nil { - t.Fatal(err) - } - if diff := deep.Equal(expectedOtherNames, foundOtherNames); len(diff) != 0 { - t.Errorf("unexpected otherNames: %v", diff) - } - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - t.Logf("certificate 3 to check:\n%s", certStr) - } -} - -func TestBackend_AllowedSerialNumbers(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - var err error - var resp *logical.Response - var certStr string - var block *pem.Block - var cert *x509.Certificate - - _, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - - // First test that Serial Numbers are not allowed - _, err = CBWrite(b, s, "roles/test", map[string]interface{}{ - "allow_any_name": true, - "enforce_hostnames": false, - }) - if err != nil { - t.Fatal(err) - } - - _, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "foobar", - "ttl": "1h", - }) - if err != nil { - t.Fatal(err) - } - - _, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "foobar", - "ttl": "1h", - "serial_number": "foobar", - }) - if err == nil { - t.Fatal("expected error") - } - - // Update the role to allow serial numbers - _, err = CBWrite(b, s, "roles/test", map[string]interface{}{ - "allow_any_name": true, - "enforce_hostnames": false, - "allowed_serial_numbers": "f00*,b4r*", - }) - if err != nil { - t.Fatal(err) - } - - _, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "foobar", - "ttl": "1h", - // Not a valid serial number - "serial_number": "foobar", - }) - if err == nil { - t.Fatal("expected error") - } - - // Valid for first possibility - resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "foobar", - "serial_number": "f00bar", - }) - 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.Subject.SerialNumber != "f00bar" { - t.Fatalf("unexpected Subject SerialNumber %s", cert.Subject.SerialNumber) - } - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - t.Logf("certificate 1 to check:\n%s", certStr) - } - - // Valid for second possibility - resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "foobar", - "serial_number": "b4rf00", - }) - 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.Subject.SerialNumber != "b4rf00" { - t.Fatalf("unexpected Subject SerialNumber %s", cert.Subject.SerialNumber) - } - if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 { - t.Logf("certificate 2 to check:\n%s", certStr) - } -} - -func TestBackend_URI_SANs(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - var err error - - _, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - - _, err = CBWrite(b, s, "roles/test", map[string]interface{}{ - "allowed_domains": []string{"foobar.com", "zipzap.com"}, - "allow_bare_domains": true, - "allow_subdomains": true, - "allow_ip_sans": true, - "allowed_uri_sans": []string{"http://someuri/abc", "spiffe://host.com/*"}, - }) - if err != nil { - t.Fatal(err) - } - - // First test some bad stuff that shouldn't work - _, err = CBWrite(b, s, "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", - "uri_sans": "http://www.mydomain.com/zxf", - }) - if err == nil { - t.Fatal("expected error") - } - - // Test valid single entry - _, err = CBWrite(b, s, "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", - "uri_sans": "http://someuri/abc", - }) - if err != nil { - t.Fatal(err) - } - - // Test globed entry - _, err = CBWrite(b, s, "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", - "uri_sans": "spiffe://host.com/something", - }) - if err != nil { - t.Fatal(err) - } - - // Test multiple entries - resp, err := CBWrite(b, s, "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", - "uri_sans": "spiffe://host.com/something,http://someuri/abc", - }) - 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) - } - - URI0, _ := url.Parse("spiffe://host.com/something") - URI1, _ := url.Parse("http://someuri/abc") - - if len(cert.URIs) != 2 { - t.Fatalf("expected 2 valid URIs SANs %v", cert.URIs) - } - - if cert.URIs[0].String() != URI0.String() || cert.URIs[1].String() != URI1.String() { - t.Fatalf( - "expected URIs SANs %v to equal provided values spiffe://host.com/something, http://someuri/abc", - cert.URIs) - } -} - -func TestBackend_AllowedURISANsTemplate(t *testing.T) { - t.Parallel() - coreConfig := &vault.CoreConfig{ - CredentialBackends: map[string]logical.Factory{ - "userpass": userpass.Factory, - }, - 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 - - // Write test policy for userpass auth method. - err := client.Sys().PutPolicy("test", ` - path "pki/*" { - capabilities = ["update"] - }`) - if err != nil { - t.Fatal(err) - } - - // Enable userpass auth method. - if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { - t.Fatal(err) - } - - // Configure test role for userpass. - if _, err := client.Logical().Write("auth/userpass/users/userpassname", map[string]interface{}{ - "password": "test", - "policies": "test", - }); err != nil { - t.Fatal(err) - } - - // Login userpass for test role and keep client token. - secret, err := client.Logical().Write("auth/userpass/login/userpassname", map[string]interface{}{ - "password": "test", - }) - if err != nil || secret == nil { - t.Fatal(err) - } - userpassToken := secret.Auth.ClientToken - - // Get auth accessor for identity template. - auths, err := client.Sys().ListAuth() - if err != nil { - t.Fatal(err) - } - userpassAccessor := auths["userpass/"].Accessor - - // Mount PKI. - err = client.Sys().Mount("pki", &api.MountInput{ - Type: "pki", - Config: api.MountConfigInput{ - DefaultLeaseTTL: "16h", - MaxLeaseTTL: "60h", - }, - }) - if err != nil { - t.Fatal(err) - } - - // Generate internal CA. - _, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - - // Write role PKI. - _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{ - "allowed_uri_sans": []string{ - "spiffe://domain/{{identity.entity.aliases." + userpassAccessor + ".name}}", - "spiffe://domain/{{identity.entity.aliases." + userpassAccessor + ".name}}/*", "spiffe://domain/foo", - }, - "allowed_uri_sans_template": true, - "require_cn": false, - }) - if err != nil { - t.Fatal(err) - } - - // Issue certificate with identity templating - client.SetToken(userpassToken) - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"uri_sans": "spiffe://domain/userpassname, spiffe://domain/foo"}) - if err != nil { - t.Fatal(err) - } - - // Issue certificate with identity templating and glob - client.SetToken(userpassToken) - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"uri_sans": "spiffe://domain/userpassname/bar"}) - if err != nil { - t.Fatal(err) - } - - // Issue certificate with non-matching identity template parameter - client.SetToken(userpassToken) - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"uri_sans": "spiffe://domain/unknownuser"}) - if err == nil { - t.Fatal(err) - } - - // Set allowed_uri_sans_template to false. - _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{ - "allowed_uri_sans_template": false, - }) - if err != nil { - t.Fatal(err) - } - - // Issue certificate with userpassToken. - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"uri_sans": "spiffe://domain/users/userpassname"}) - if err == nil { - t.Fatal("expected error") - } -} - -func TestBackend_AllowedDomainsTemplate(t *testing.T) { - t.Parallel() - coreConfig := &vault.CoreConfig{ - CredentialBackends: map[string]logical.Factory{ - "userpass": userpass.Factory, - }, - 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 - - // Write test policy for userpass auth method. - err := client.Sys().PutPolicy("test", ` - path "pki/*" { - capabilities = ["update"] - }`) - if err != nil { - t.Fatal(err) - } - - // Enable userpass auth method. - if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { - t.Fatal(err) - } - - // Configure test role for userpass. - if _, err := client.Logical().Write("auth/userpass/users/userpassname", map[string]interface{}{ - "password": "test", - "policies": "test", - }); err != nil { - t.Fatal(err) - } - - // Login userpass for test role and set client token - userpassAuth, err := auth.NewUserpassAuth("userpassname", &auth.Password{FromString: "test"}) - if err != nil { - t.Fatal(err) - } - - // Get auth accessor for identity template. - auths, err := client.Sys().ListAuth() - if err != nil { - t.Fatal(err) - } - userpassAccessor := auths["userpass/"].Accessor - - // Mount PKI. - err = client.Sys().Mount("pki", &api.MountInput{ - Type: "pki", - Config: api.MountConfigInput{ - DefaultLeaseTTL: "16h", - MaxLeaseTTL: "60h", - }, - }) - if err != nil { - t.Fatal(err) - } - - // Generate internal CA. - _, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - - // Write role PKI. - _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{ - "allowed_domains": []string{ - "foobar.com", "zipzap.com", "{{identity.entity.aliases." + userpassAccessor + ".name}}", - "foo.{{identity.entity.aliases." + userpassAccessor + ".name}}.example.com", - }, - "allowed_domains_template": true, - "allow_bare_domains": true, - }) - if err != nil { - t.Fatal(err) - } - - // Issue certificate with userpassToken. - secret, err := client.Auth().Login(context.TODO(), userpassAuth) - if err != nil { - t.Fatal(err) - } - if err != nil || secret == nil { - t.Fatal(err) - } - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"common_name": "userpassname"}) - if err != nil { - t.Fatal(err) - } - - // Issue certificate for foobar.com to verify allowed_domain_template doesn't break plain domains. - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"common_name": "foobar.com"}) - if err != nil { - t.Fatal(err) - } - - // Issue certificate for unknown userpassname. - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"common_name": "unknownuserpassname"}) - if err == nil { - t.Fatal("expected error") - } - - // Issue certificate for foo.userpassname.domain. - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"common_name": "foo.userpassname.example.com"}) - if err != nil { - t.Fatal("expected error") - } - - // Set allowed_domains_template to false. - _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{ - "allowed_domains_template": false, - }) - if err != nil { - t.Fatal(err) - } - - // Issue certificate with userpassToken. - _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"common_name": "userpassname"}) - if err == nil { - t.Fatal("expected error") - } -} - -func TestReadWriteDeleteRoles(t *testing.T) { - t.Parallel() - ctx := context.Background() - coreConfig := &vault.CoreConfig{ - CredentialBackends: map[string]logical.Factory{ - "userpass": userpass.Factory, - }, - 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 - - // Mount PKI. - err := client.Sys().MountWithContext(ctx, "pki", &api.MountInput{ - Type: "pki", - Config: api.MountConfigInput{ - DefaultLeaseTTL: "16h", - MaxLeaseTTL: "60h", - }, - }) - if err != nil { - t.Fatal(err) - } - - resp, err := client.Logical().ReadWithContext(ctx, "pki/roles/test") - if err != nil { - t.Fatal(err) - } - - if resp != nil { - t.Fatalf("response should have been emtpy but was:\n%#v", resp) - } - - // Write role PKI. - _, err = client.Logical().WriteWithContext(ctx, "pki/roles/test", map[string]interface{}{}) - if err != nil { - t.Fatal(err) - } - - // Read the role. - resp, err = client.Logical().ReadWithContext(ctx, "pki/roles/test") - if err != nil { - t.Fatal(err) - } - - if resp.Data == nil { - t.Fatal("default data within response was nil when it should have contained data") - } - - // Validate that we have not changed any defaults unknowingly - expectedData := map[string]interface{}{ - "key_type": "rsa", - "use_csr_sans": true, - "client_flag": true, - "allowed_serial_numbers": []interface{}{}, - "generate_lease": false, - "signature_bits": json.Number("256"), - "use_pss": false, - "allowed_domains": []interface{}{}, - "allowed_uri_sans_template": false, - "enforce_hostnames": true, - "policy_identifiers": []interface{}{}, - "require_cn": true, - "allowed_domains_template": false, - "allow_token_displayname": false, - "country": []interface{}{}, - "not_after": "", - "postal_code": []interface{}{}, - "use_csr_common_name": true, - "allow_localhost": true, - "allow_subdomains": false, - "allow_wildcard_certificates": true, - "allowed_other_sans": []interface{}{}, - "allowed_uri_sans": []interface{}{}, - "basic_constraints_valid_for_non_ca": false, - "key_usage": []interface{}{"DigitalSignature", "KeyAgreement", "KeyEncipherment"}, - "not_before_duration": json.Number("30"), - "allow_glob_domains": false, - "ttl": json.Number("0"), - "ou": []interface{}{}, - "email_protection_flag": false, - "locality": []interface{}{}, - "server_flag": true, - "allow_bare_domains": false, - "allow_ip_sans": true, - "ext_key_usage_oids": []interface{}{}, - "allow_any_name": false, - "ext_key_usage": []interface{}{}, - "key_bits": json.Number("2048"), - "max_ttl": json.Number("0"), - "no_store": false, - "organization": []interface{}{}, - "province": []interface{}{}, - "street_address": []interface{}{}, - "code_signing_flag": false, - "issuer_ref": "default", - "cn_validations": []interface{}{"email", "hostname"}, - "allowed_user_ids": []interface{}{}, - } - - if diff := deep.Equal(expectedData, resp.Data); len(diff) > 0 { - t.Fatalf("pki role default values have changed, diff: %v", diff) - } - - _, err = client.Logical().DeleteWithContext(ctx, "pki/roles/test") - if err != nil { - t.Fatal(err) - } - - resp, err = client.Logical().ReadWithContext(ctx, "pki/roles/test") - if err != nil { - t.Fatal(err) - } - - if resp != nil { - t.Fatalf("response should have been empty but was:\n%#v", resp) - } -} - -func setCerts() { - cak, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - panic(err) - } - marshaledKey, err := x509.MarshalECPrivateKey(cak) - if err != nil { - panic(err) - } - keyPEMBlock := &pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: marshaledKey, - } - ecCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock))) - if err != nil { - panic(err) - } - subjKeyID, err := certutil.GetSubjKeyID(cak) - if err != nil { - panic(err) - } - caCertTemplate := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "root.localhost", - }, - SubjectKeyId: subjKeyID, - DNSNames: []string{"root.localhost"}, - KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign), - SerialNumber: big.NewInt(mathrand.Int63()), - NotAfter: time.Now().Add(262980 * time.Hour), - BasicConstraintsValid: true, - IsCA: true, - } - caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, cak.Public(), cak) - if err != nil { - panic(err) - } - caCertPEMBlock := &pem.Block{ - Type: "CERTIFICATE", - Bytes: caBytes, - } - ecCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock))) - - rak, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - panic(err) - } - marshaledKey = x509.MarshalPKCS1PrivateKey(rak) - keyPEMBlock = &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: marshaledKey, - } - rsaCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock))) - if err != nil { - panic(err) - } - _, err = certutil.GetSubjKeyID(rak) - if err != nil { - panic(err) - } - caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, rak.Public(), rak) - if err != nil { - panic(err) - } - caCertPEMBlock = &pem.Block{ - Type: "CERTIFICATE", - Bytes: caBytes, - } - rsaCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock))) - - _, edk, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - panic(err) - } - marshaledKey, err = x509.MarshalPKCS8PrivateKey(edk) - if err != nil { - panic(err) - } - keyPEMBlock = &pem.Block{ - Type: "PRIVATE KEY", - Bytes: marshaledKey, - } - edCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock))) - if err != nil { - panic(err) - } - _, err = certutil.GetSubjKeyID(edk) - if err != nil { - panic(err) - } - caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, edk.Public(), edk) - if err != nil { - panic(err) - } - caCertPEMBlock = &pem.Block{ - Type: "CERTIFICATE", - Bytes: caBytes, - } - edCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock))) -} - -func TestBackend_RevokePlusTidy_Intermediate(t *testing.T) { - // Use a ridiculously long time to minimize the chance - // that we have to deal with more than one interval. - // InMemSink rounds down to an interval boundary rather than - // starting one at the time of initialization. - // - // This test is not parallelizable. - inmemSink := metrics.NewInmemSink( - 1000000*time.Hour, - 2000000*time.Hour) - - metricsConf := metrics.DefaultConfig("") - metricsConf.EnableHostname = false - metricsConf.EnableHostnameLabel = false - metricsConf.EnableServiceLabel = false - metricsConf.EnableTypePrefix = false - - _, err := metrics.NewGlobal(metricsConf, inmemSink) - if err != nil { - t.Fatal(err) - } - - // Enable PKI secret engine - 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() - cores := cluster.Cores - vault.TestWaitActive(t, cores[0].Core) - client := cores[0].Client - - // Mount /pki as a root CA - err = client.Sys().Mount("pki", &api.MountInput{ - Type: "pki", - Config: api.MountConfigInput{ - DefaultLeaseTTL: "16h", - MaxLeaseTTL: "32h", - }, - }) - if err != nil { - t.Fatal(err) - } - - // Set up Metric Configuration, then restart to enable it - _, err = client.Logical().Write("pki/config/auto-tidy", map[string]interface{}{ - "maintain_stored_certificate_counts": true, - "publish_stored_certificate_count_metrics": true, - }) - _, err = client.Logical().Write("/sys/plugins/reload/backend", map[string]interface{}{ - "mounts": "pki/", - }) - - // Check the metrics initialized in order to calculate backendUUID for /pki - // BackendUUID not consistent during tests with UUID from /sys/mounts/pki - metricsSuffix := "total_certificates_stored" - backendUUID := "" - mostRecentInterval := inmemSink.Data()[len(inmemSink.Data())-1] - for _, existingGauge := range mostRecentInterval.Gauges { - if strings.HasSuffix(existingGauge.Name, metricsSuffix) { - expandedGaugeName := existingGauge.Name - backendUUID = strings.Split(expandedGaugeName, ".")[2] - break - } - } - if backendUUID == "" { - t.Fatalf("No Gauge Found ending with %s", metricsSuffix) - } - - // Set the cluster's certificate as the root CA in /pki - pemBundleRootCA := string(cluster.CACertPEM) + string(cluster.CAKeyPEM) - _, err = client.Logical().Write("pki/config/ca", map[string]interface{}{ - "pem_bundle": pemBundleRootCA, - }) - if err != nil { - t.Fatal(err) - } - - // Mount /pki2 to operate as an intermediate CA - err = client.Sys().Mount("pki2", &api.MountInput{ - Type: "pki", - Config: api.MountConfigInput{ - DefaultLeaseTTL: "16h", - MaxLeaseTTL: "32h", - }, - }) - if err != nil { - t.Fatal(err) - } - // Set up Metric Configuration, then restart to enable it - _, err = client.Logical().Write("pki2/config/auto-tidy", map[string]interface{}{ - "maintain_stored_certificate_counts": true, - "publish_stored_certificate_count_metrics": true, - }) - _, err = client.Logical().Write("/sys/plugins/reload/backend", map[string]interface{}{ - "mounts": "pki2/", - }) - - // Create a CSR for the intermediate CA - secret, err := client.Logical().Write("pki2/intermediate/generate/internal", nil) - if err != nil { - t.Fatal(err) - } - intermediateCSR := secret.Data["csr"].(string) - - // Sign the intermediate CSR using /pki - secret, err = client.Logical().Write("pki/root/sign-intermediate", map[string]interface{}{ - "permitted_dns_domains": ".myvault.com", - "csr": intermediateCSR, - "ttl": "10s", - }) - if err != nil { - t.Fatal(err) - } - intermediateCertSerial := secret.Data["serial_number"].(string) - intermediateCASerialColon := strings.ReplaceAll(strings.ToLower(intermediateCertSerial), ":", "-") - - // Get the intermediate cert after signing - secret, err = client.Logical().Read("pki/cert/" + intermediateCASerialColon) - if err != nil { - t.Fatal(err) - } - if secret == nil || len(secret.Data) == 0 || len(secret.Data["certificate"].(string)) == 0 { - t.Fatal("expected certificate information from read operation") - } - - // Issue a revoke on on /pki - _, err = client.Logical().Write("pki/revoke", map[string]interface{}{ - "serial_number": intermediateCertSerial, - }) - if err != nil { - t.Fatal(err) - } - - // Check the cert-count metrics - expectedCertCountGaugeMetrics := map[string]float32{ - "secrets.pki." + backendUUID + ".total_revoked_certificates_stored": 1, - "secrets.pki." + backendUUID + ".total_certificates_stored": 1, - } - mostRecentInterval = inmemSink.Data()[len(inmemSink.Data())-1] - for gauge, value := range expectedCertCountGaugeMetrics { - if _, ok := mostRecentInterval.Gauges[gauge]; !ok { - t.Fatalf("Expected metrics to include a value for gauge %s", gauge) - } - if value != mostRecentInterval.Gauges[gauge].Value { - t.Fatalf("Expected value metric %s to be %f but got %f", gauge, value, mostRecentInterval.Gauges[gauge].Value) - } - } - - // Revoke adds a fixed 2s buffer, so we sleep for a bit longer to ensure - // the revocation time is past the current time. - time.Sleep(3 * time.Second) - - // Issue a tidy on /pki - _, err = client.Logical().Write("pki/tidy", map[string]interface{}{ - "tidy_cert_store": true, - "tidy_revoked_certs": true, - "safety_buffer": "1s", - }) - if err != nil { - t.Fatal(err) - } - - // Sleep a bit to make sure we're past the safety buffer - time.Sleep(2 * time.Second) - - // Get CRL and ensure the tidied cert is still in the list after the tidy - // operation since it's not past the NotAfter (ttl) value yet. - crl := getParsedCrl(t, client, "pki") - - revokedCerts := crl.TBSCertList.RevokedCertificates - if len(revokedCerts) == 0 { - t.Fatal("expected CRL to be non-empty") - } - - sn := certutil.GetHexFormatted(revokedCerts[0].SerialNumber.Bytes(), ":") - if sn != intermediateCertSerial { - t.Fatalf("expected: %v, got: %v", intermediateCertSerial, sn) - } - - // Wait for cert to expire - time.Sleep(10 * time.Second) - - // Issue a tidy on /pki - _, err = client.Logical().Write("pki/tidy", map[string]interface{}{ - "tidy_cert_store": true, - "tidy_revoked_certs": true, - "safety_buffer": "1s", - }) - if err != nil { - t.Fatal(err) - } - - // Sleep a bit to make sure we're past the safety buffer - time.Sleep(2 * time.Second) - - // Issue a tidy-status on /pki - { - tidyStatus, err := client.Logical().Read("pki/tidy-status") - if err != nil { - t.Fatal(err) - } - expectedData := map[string]interface{}{ - "safety_buffer": json.Number("1"), - "issuer_safety_buffer": json.Number("31536000"), - "tidy_cert_store": true, - "tidy_revoked_certs": true, - "tidy_revoked_cert_issuer_associations": false, - "tidy_expired_issuers": false, - "tidy_move_legacy_ca_bundle": false, - "tidy_revocation_queue": false, - "tidy_cross_cluster_revoked_certs": false, - "pause_duration": "0s", - "state": "Finished", - "error": nil, - "time_started": nil, - "time_finished": nil, - "message": nil, - "cert_store_deleted_count": json.Number("1"), - "revoked_cert_deleted_count": json.Number("1"), - "missing_issuer_cert_count": json.Number("0"), - "current_cert_store_count": json.Number("0"), - "current_revoked_cert_count": json.Number("0"), - "revocation_queue_deleted_count": json.Number("0"), - "cross_revoked_cert_deleted_count": json.Number("0"), - "internal_backend_uuid": backendUUID, - } - // Let's copy the times from the response so that we can use deep.Equal() - timeStarted, ok := tidyStatus.Data["time_started"] - if !ok || timeStarted == "" { - t.Fatal("Expected tidy status response to include a value for time_started") - } - expectedData["time_started"] = timeStarted - timeFinished, ok := tidyStatus.Data["time_finished"] - if !ok || timeFinished == "" { - t.Fatal("Expected tidy status response to include a value for time_finished") - } - expectedData["time_finished"] = timeFinished - - if diff := deep.Equal(expectedData, tidyStatus.Data); diff != nil { - t.Fatal(diff) - } - } - // Check the tidy metrics - { - // Map of gauges to expected value - expectedGauges := map[string]float32{ - "secrets.pki.tidy.cert_store_current_entry": 0, - "secrets.pki.tidy.cert_store_total_entries": 1, - "secrets.pki.tidy.revoked_cert_current_entry": 0, - "secrets.pki.tidy.revoked_cert_total_entries": 1, - "secrets.pki.tidy.start_time_epoch": 0, - "secrets.pki." + backendUUID + ".total_certificates_stored": 0, - "secrets.pki." + backendUUID + ".total_revoked_certificates_stored": 0, - "secrets.pki.tidy.cert_store_total_entries_remaining": 0, - "secrets.pki.tidy.revoked_cert_total_entries_remaining": 0, - } - // Map of counters to the sum of the metrics for that counter - expectedCounters := map[string]float64{ - "secrets.pki.tidy.cert_store_deleted_count": 1, - "secrets.pki.tidy.revoked_cert_deleted_count": 1, - "secrets.pki.tidy.success": 2, - // Note that "secrets.pki.tidy.failure" won't be in the captured metrics - } - - // If the metrics span more than one interval, skip the checks - intervals := inmemSink.Data() - if len(intervals) == 1 { - interval := inmemSink.Data()[0] - - for gauge, value := range expectedGauges { - if _, ok := interval.Gauges[gauge]; !ok { - t.Fatalf("Expected metrics to include a value for gauge %s", gauge) - } - if value != interval.Gauges[gauge].Value { - t.Fatalf("Expected value metric %s to be %f but got %f", gauge, value, interval.Gauges[gauge].Value) - } - - } - for counter, value := range expectedCounters { - if _, ok := interval.Counters[counter]; !ok { - t.Fatalf("Expected metrics to include a value for couter %s", counter) - } - if value != interval.Counters[counter].Sum { - t.Fatalf("Expected the sum of metric %s to be %f but got %f", counter, value, interval.Counters[counter].Sum) - } - } - - tidyDuration, ok := interval.Samples["secrets.pki.tidy.duration"] - if !ok { - t.Fatal("Expected metrics to include a value for sample secrets.pki.tidy.duration") - } - if tidyDuration.Count <= 0 { - t.Fatalf("Expected metrics to have count > 0 for sample secrets.pki.tidy.duration, but got %d", tidyDuration.Count) - } - } - } - - crl = getParsedCrl(t, client, "pki") - - revokedCerts = crl.TBSCertList.RevokedCertificates - if len(revokedCerts) != 0 { - t.Fatal("expected CRL to be empty") - } -} - -func TestBackend_Root_FullCAChain(t *testing.T) { - t.Parallel() - testCases := []struct { - testName string - keyType string - }{ - {testName: "RSA", keyType: "rsa"}, - {testName: "ED25519", keyType: "ed25519"}, - {testName: "EC", keyType: "ec"}, - } - for _, tc := range testCases { - tc := tc - t.Run(tc.testName, func(t *testing.T) { - runFullCAChainTest(t, tc.keyType) - }) - } -} - -func runFullCAChainTest(t *testing.T, keyType string) { - // Generate a root CA at /pki-root - b_root, s_root := CreateBackendWithStorage(t) - - var err error - - resp, err := CBWrite(b_root, s_root, "root/generate/exported", map[string]interface{}{ - "common_name": "root myvault.com", - "key_type": keyType, - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected ca info") - } - rootData := resp.Data - rootCert := rootData["certificate"].(string) - - // Validate that root's /cert/ca-chain now contains the certificate. - resp, err = CBRead(b_root, s_root, "cert/ca_chain") - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected intermediate chain information") - } - - fullChain := resp.Data["ca_chain"].(string) - requireCertInCaChainString(t, fullChain, rootCert, "expected root cert within root cert/ca_chain") - - // Make sure when we issue a leaf certificate we get the full chain back. - _, err = CBWrite(b_root, s_root, "roles/example", map[string]interface{}{ - "allowed_domains": "example.com", - "allow_subdomains": "true", - "max_ttl": "1h", - }) - require.NoError(t, err, "error setting up pki root role: %v", err) - - resp, err = CBWrite(b_root, s_root, "issue/example", map[string]interface{}{ - "common_name": "test.example.com", - "ttl": "5m", - }) - require.NoError(t, err, "error issuing certificate from pki root: %v", err) - fullChainArray := resp.Data["ca_chain"].([]string) - requireCertInCaChainArray(t, fullChainArray, rootCert, "expected root cert within root issuance pki-root/issue/example") - - // Now generate an intermediate at /pki-intermediate, signed by the root. - b_int, s_int := CreateBackendWithStorage(t) - - resp, err = CBWrite(b_int, s_int, "intermediate/generate/exported", map[string]interface{}{ - "common_name": "intermediate myvault.com", - "key_type": keyType, - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected intermediate CSR info") - } - intermediateData := resp.Data - intermediateKey := intermediateData["private_key"].(string) - - resp, err = CBWrite(b_root, s_root, "root/sign-intermediate", map[string]interface{}{ - "csr": intermediateData["csr"], - "format": "pem", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected signed intermediate info") - } - intermediateSignedData := resp.Data - intermediateCert := intermediateSignedData["certificate"].(string) - - rootCaCert := parseCert(t, rootCert) - intermediaryCaCert := parseCert(t, intermediateCert) - requireSignedBy(t, intermediaryCaCert, rootCaCert) - intermediateCaChain := intermediateSignedData["ca_chain"].([]string) - - require.Equal(t, parseCert(t, intermediateCaChain[0]), intermediaryCaCert, "intermediate signed cert should have been part of ca_chain") - require.Equal(t, parseCert(t, intermediateCaChain[1]), rootCaCert, "root cert should have been part of ca_chain") - - _, err = CBWrite(b_int, s_int, "intermediate/set-signed", map[string]interface{}{ - "certificate": intermediateCert + "\n" + rootCert + "\n", - }) - if err != nil { - t.Fatal(err) - } - - // Validate that intermediate's ca_chain field now includes the full - // chain. - resp, err = CBRead(b_int, s_int, "cert/ca_chain") - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected intermediate chain information") - } - - // Verify we have a proper CRL now - crl := getParsedCrlFromBackend(t, b_int, s_int, "crl") - require.Equal(t, 0, len(crl.TBSCertList.RevokedCertificates)) - - fullChain = resp.Data["ca_chain"].(string) - requireCertInCaChainString(t, fullChain, intermediateCert, "expected full chain to contain intermediate certificate from pki-intermediate/cert/ca_chain") - requireCertInCaChainString(t, fullChain, rootCert, "expected full chain to contain root certificate from pki-intermediate/cert/ca_chain") - - // Make sure when we issue a leaf certificate we get the full chain back. - _, err = CBWrite(b_int, s_int, "roles/example", map[string]interface{}{ - "allowed_domains": "example.com", - "allow_subdomains": "true", - "max_ttl": "1h", - }) - require.NoError(t, err, "error setting up pki intermediate role: %v", err) - - resp, err = CBWrite(b_int, s_int, "issue/example", map[string]interface{}{ - "common_name": "test.example.com", - "ttl": "5m", - }) - require.NoError(t, err, "error issuing certificate from pki intermediate: %v", err) - fullChainArray = resp.Data["ca_chain"].([]string) - requireCertInCaChainArray(t, fullChainArray, intermediateCert, "expected full chain to contain intermediate certificate from pki-intermediate/issue/example") - requireCertInCaChainArray(t, fullChainArray, rootCert, "expected full chain to contain root certificate from pki-intermediate/issue/example") - - // Finally, import this signing cert chain into a new mount to ensure - // "external" CAs behave as expected. - b_ext, s_ext := CreateBackendWithStorage(t) - - _, err = CBWrite(b_ext, s_ext, "config/ca", map[string]interface{}{ - "pem_bundle": intermediateKey + "\n" + intermediateCert + "\n" + rootCert + "\n", - }) - if err != nil { - t.Fatal(err) - } - - // Validate the external chain information was loaded correctly. - resp, err = CBRead(b_ext, s_ext, "cert/ca_chain") - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected intermediate chain information") - } - - fullChain = resp.Data["ca_chain"].(string) - if strings.Count(fullChain, intermediateCert) != 1 { - t.Fatalf("expected full chain to contain intermediate certificate; got %v occurrences", strings.Count(fullChain, intermediateCert)) - } - if strings.Count(fullChain, rootCert) != 1 { - t.Fatalf("expected full chain to contain root certificate; got %v occurrences", strings.Count(fullChain, rootCert)) - } - - // Now issue a short-lived certificate from our pki-external. - _, err = CBWrite(b_ext, s_ext, "roles/example", map[string]interface{}{ - "allowed_domains": "example.com", - "allow_subdomains": "true", - "max_ttl": "1h", - }) - require.NoError(t, err, "error setting up pki role: %v", err) - - resp, err = CBWrite(b_ext, s_ext, "issue/example", map[string]interface{}{ - "common_name": "test.example.com", - "ttl": "5m", - }) - require.NoError(t, err, "error issuing certificate: %v", err) - require.NotNil(t, resp, "got nil response from issuing request") - issueCrtAsPem := resp.Data["certificate"].(string) - issuedCrt := parseCert(t, issueCrtAsPem) - - // Verify that the certificates are signed by the intermediary CA key... - requireSignedBy(t, issuedCrt, intermediaryCaCert) - - // Test that we can request that the root ca certificate not appear in the ca_chain field - resp, err = CBWrite(b_ext, s_ext, "issue/example", map[string]interface{}{ - "common_name": "test.example.com", - "ttl": "5m", - "remove_roots_from_chain": "true", - }) - requireSuccessNonNilResponse(t, resp, err, "error issuing certificate when removing self signed") - fullChain = strings.Join(resp.Data["ca_chain"].([]string), "\n") - if strings.Count(fullChain, intermediateCert) != 1 { - t.Fatalf("expected full chain to contain intermediate certificate; got %v occurrences", strings.Count(fullChain, intermediateCert)) - } - if strings.Count(fullChain, rootCert) != 0 { - t.Fatalf("expected full chain to NOT contain root certificate; got %v occurrences", strings.Count(fullChain, rootCert)) - } -} - -func requireCertInCaChainArray(t *testing.T, chain []string, cert string, msgAndArgs ...interface{}) { - var fullChain string - for _, caCert := range chain { - fullChain = fullChain + "\n" + caCert - } - - requireCertInCaChainString(t, fullChain, cert, msgAndArgs) -} - -func requireCertInCaChainString(t *testing.T, chain string, cert string, msgAndArgs ...interface{}) { - count := strings.Count(chain, cert) - if count != 1 { - failMsg := fmt.Sprintf("Found %d occurrances of the cert in the provided chain", count) - require.FailNow(t, failMsg, msgAndArgs...) - } -} - -type MultiBool int - -const ( - MFalse MultiBool = iota - MTrue MultiBool = iota - MAny MultiBool = iota -) - -func (o MultiBool) ToValues() []bool { - if o == MTrue { - return []bool{true} - } - - if o == MFalse { - return []bool{false} - } - - if o == MAny { - return []bool{true, false} - } - - return []bool{} -} - -type IssuanceRegression struct { - AllowedDomains []string - AllowBareDomains MultiBool - AllowGlobDomains MultiBool - AllowSubdomains MultiBool - AllowLocalhost MultiBool - AllowWildcardCertificates MultiBool - CNValidations []string - CommonName string - Issued bool -} - -func RoleIssuanceRegressionHelper(t *testing.T, b *backend, s logical.Storage, index int, test IssuanceRegression) int { - tested := 0 - for _, AllowBareDomains := range test.AllowBareDomains.ToValues() { - for _, AllowGlobDomains := range test.AllowGlobDomains.ToValues() { - for _, AllowSubdomains := range test.AllowSubdomains.ToValues() { - for _, AllowLocalhost := range test.AllowLocalhost.ToValues() { - for _, AllowWildcardCertificates := range test.AllowWildcardCertificates.ToValues() { - role := fmt.Sprintf("issuance-regression-%d-bare-%v-glob-%v-subdomains-%v-localhost-%v-wildcard-%v", index, AllowBareDomains, AllowGlobDomains, AllowSubdomains, AllowLocalhost, AllowWildcardCertificates) - _, err := CBWrite(b, s, "roles/"+role, map[string]interface{}{ - "allowed_domains": test.AllowedDomains, - "allow_bare_domains": AllowBareDomains, - "allow_glob_domains": AllowGlobDomains, - "allow_subdomains": AllowSubdomains, - "allow_localhost": AllowLocalhost, - "allow_wildcard_certificates": AllowWildcardCertificates, - "cn_validations": test.CNValidations, - // TODO: test across this vector as well. Currently certain wildcard - // matching is broken with it enabled (such as x*x.foo). - "enforce_hostnames": false, - "key_type": "ec", - "key_bits": 256, - "no_store": true, - // With the CN Validations field, ensure we prevent CN from appearing - // in SANs. - }) - if err != nil { - t.Fatal(err) - } - - resp, err := CBWrite(b, s, "issue/"+role, map[string]interface{}{ - "common_name": test.CommonName, - "exclude_cn_from_sans": true, - }) - - haveErr := err != nil || resp == nil - expectErr := !test.Issued - - if haveErr != expectErr { - t.Fatalf("issuance regression test [%d] failed: haveErr: %v, expectErr: %v, err: %v, resp: %v, test case: %v, role: %v", index, haveErr, expectErr, err, resp, test, role) - } - - tested += 1 - } - } - } - } - } - - return tested -} - -func TestBackend_Roles_IssuanceRegression(t *testing.T) { - t.Parallel() - // Regression testing of role's issuance policy. - testCases := []IssuanceRegression{ - // allowed, bare, glob, subdomains, localhost, wildcards, cn, issued - - // === Globs not allowed but used === // - // Allowed contains globs, but globbing not allowed, resulting in all - // issuances failing. Note that tests against issuing a wildcard with - // a bare domain will be covered later. - /* 0 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "baz.fud.bar.foo", false}, - /* 1 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.fud.bar.foo", false}, - /* 2 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "fud.bar.foo", false}, - /* 3 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.bar.foo", false}, - /* 4 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "bar.foo", false}, - /* 5 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.foo", false}, - /* 6 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "foo", false}, - /* 7 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "baz.fud.bar.foo", false}, - /* 8 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.fud.bar.foo", false}, - /* 9 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "fud.bar.foo", false}, - /* 10 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.bar.foo", false}, - /* 11 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "bar.foo", false}, - /* 12 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "foo", false}, - - // === Localhost sanity === // - // Localhost forbidden, not matching allowed domains -> not issued - /* 13 */ {[]string{"*.*.foo"}, MAny, MAny, MAny, MFalse, MAny, nil, "localhost", false}, - // Localhost allowed, not matching allowed domains -> issued - /* 14 */ {[]string{"*.*.foo"}, MAny, MAny, MAny, MTrue, MAny, nil, "localhost", true}, - // Localhost allowed via allowed domains (and bare allowed), not by AllowLocalhost -> issued - /* 15 */ {[]string{"localhost"}, MTrue, MAny, MAny, MFalse, MAny, nil, "localhost", true}, - // Localhost allowed via allowed domains (and bare not allowed), not by AllowLocalhost -> not issued - /* 16 */ {[]string{"localhost"}, MFalse, MAny, MAny, MFalse, MAny, nil, "localhost", false}, - // Localhost allowed via allowed domains (but bare not allowed), and by AllowLocalhost -> issued - /* 17 */ {[]string{"localhost"}, MFalse, MAny, MAny, MTrue, MAny, nil, "localhost", true}, - - // === Bare wildcard issuance == // - // allowed_domains contains one or more wildcards and bare domains allowed, - // resulting in the cert being issued. - /* 18 */ {[]string{"*.foo"}, MTrue, MAny, MAny, MAny, MTrue, nil, "*.foo", true}, - /* 19 */ {[]string{"*.*.foo"}, MTrue, MAny, MAny, MAny, MAny, nil, "*.*.foo", false}, // Does not conform to RFC 6125 - - // === Double Leading Glob Testing === // - // Allowed contains globs, but glob allowed so certain matches work. - // The value of bare and localhost does not impact these results. - /* 20 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "baz.fud.bar.foo", true}, // glob domains allow infinite subdomains - /* 21 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.fud.bar.foo", true}, // glob domain allows wildcard of subdomains - /* 22 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "fud.bar.foo", true}, - /* 23 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.bar.foo", true}, // Regression fix: Vault#13530 - /* 24 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "bar.foo", false}, - /* 25 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "*.foo", false}, - /* 26 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "foo", false}, - - // Allowed contains globs, but glob and subdomain both work, so we expect - // wildcard issuance to work as well. The value of bare and localhost does - // not impact these results. - /* 27 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "baz.fud.bar.foo", true}, - /* 28 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.fud.bar.foo", true}, - /* 29 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "fud.bar.foo", true}, - /* 30 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.bar.foo", true}, // Regression fix: Vault#13530 - /* 31 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "bar.foo", false}, - /* 32 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "*.foo", false}, - /* 33 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "foo", false}, - - // === Single Leading Glob Testing === // - // Allowed contains globs, but glob allowed so certain matches work. - // The value of bare and localhost does not impact these results. - /* 34 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "baz.fud.bar.foo", true}, // glob domains allow infinite subdomains - /* 35 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.fud.bar.foo", true}, // glob domain allows wildcard of subdomains - /* 36 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "fud.bar.foo", true}, // glob domains allow infinite subdomains - /* 37 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.bar.foo", true}, // glob domain allows wildcards of subdomains - /* 38 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "bar.foo", true}, - /* 39 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "foo", false}, - - // Allowed contains globs, but glob and subdomain both work, so we expect - // wildcard issuance to work as well. The value of bare and localhost does - // not impact these results. - /* 40 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "baz.fud.bar.foo", true}, - /* 41 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.fud.bar.foo", true}, - /* 42 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "fud.bar.foo", true}, - /* 43 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.bar.foo", true}, - /* 44 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "bar.foo", true}, - /* 45 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "foo", false}, - - // === Only base domain name === // - // Allowed contains only domain components, but subdomains not allowed. This - // results in most issuances failing unless we allow bare domains, in which - // case only the final issuance for "foo" will succeed. - /* 46 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "baz.fud.bar.foo", false}, - /* 47 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "*.fud.bar.foo", false}, - /* 48 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "fud.bar.foo", false}, - /* 49 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "*.bar.foo", false}, - /* 50 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "bar.foo", false}, - /* 51 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "*.foo", false}, - /* 52 */ {[]string{"foo"}, MFalse, MAny, MFalse, MAny, MAny, nil, "foo", false}, - /* 53 */ {[]string{"foo"}, MTrue, MAny, MFalse, MAny, MAny, nil, "foo", true}, - - // Allowed contains only domain components, and subdomains are now allowed. - // This results in most issuances succeeding, with the exception of the - // base foo, which is still governed by base's value. - /* 54 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, nil, "baz.fud.bar.foo", true}, - /* 55 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*.fud.bar.foo", true}, - /* 56 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, nil, "fud.bar.foo", true}, - /* 57 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*.bar.foo", true}, - /* 58 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, nil, "bar.foo", true}, - /* 59 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*.foo", true}, - /* 60 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "x*x.foo", true}, // internal wildcards should be allowed per RFC 6125/6.4.3 - /* 61 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*x.foo", true}, // prefix wildcards should be allowed per RFC 6125/6.4.3 - /* 62 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "x*.foo", true}, // suffix wildcards should be allowed per RFC 6125/6.4.3 - /* 63 */ {[]string{"foo"}, MFalse, MAny, MTrue, MAny, MAny, nil, "foo", false}, - /* 64 */ {[]string{"foo"}, MTrue, MAny, MTrue, MAny, MAny, nil, "foo", true}, - - // === Internal Glob Matching === // - // Basic glob matching requirements - /* 65 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xerox.foo", true}, - /* 66 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xylophone.files.pyrex.foo", true}, // globs can match across subdomains - /* 67 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xercex.bar.foo", false}, // x.foo isn't matched - /* 68 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "bar.foo", false}, // x*x isn't matched. - /* 69 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.foo", false}, // unrelated wildcard - /* 70 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.x*x.foo", false}, // Does not conform to RFC 6125 - /* 71 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.xyx.foo", false}, // Globs and Subdomains do not layer per docs. - - // Various requirements around x*x.foo wildcard matching. - /* 72 */ {[]string{"x*x.foo"}, MFalse, MFalse, MAny, MAny, MAny, nil, "x*x.foo", false}, // base disabled, shouldn't match wildcard - /* 73 */ {[]string{"x*x.foo"}, MFalse, MTrue, MAny, MAny, MTrue, nil, "x*x.foo", true}, // base disallowed, but globbing allowed and should match - /* 74 */ {[]string{"x*x.foo"}, MTrue, MAny, MAny, MAny, MTrue, nil, "x*x.foo", true}, // base allowed, should match wildcard - - // Basic glob matching requirements with internal dots. - /* 75 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xerox.foo", false}, // missing dots - /* 76 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "x.ero.x.foo", true}, - /* 77 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xylophone.files.pyrex.foo", false}, // missing dots - /* 78 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "x.ylophone.files.pyre.x.foo", true}, // globs can match across subdomains - /* 79 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xercex.bar.foo", false}, // x.foo isn't matched - /* 80 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "bar.foo", false}, // x.*.x isn't matched. - /* 81 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.foo", false}, // unrelated wildcard - /* 82 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.x.*.x.foo", false}, // Does not conform to RFC 6125 - /* 83 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.x.y.x.foo", false}, // Globs and Subdomains do not layer per docs. - - // === Wildcard restriction testing === // - /* 84 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MFalse, nil, "*.fud.bar.foo", false}, // glob domain allows wildcard of subdomains - /* 85 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MFalse, nil, "*.bar.foo", false}, // glob domain allows wildcards of subdomains - /* 86 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*.fud.bar.foo", false}, - /* 87 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*.bar.foo", false}, - /* 88 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*.foo", false}, - /* 89 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "x*x.foo", false}, - /* 90 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*x.foo", false}, - /* 91 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "x*.foo", false}, - /* 92 */ {[]string{"x*x.foo"}, MTrue, MAny, MAny, MAny, MFalse, nil, "x*x.foo", false}, - /* 93 */ {[]string{"*.foo"}, MFalse, MFalse, MAny, MAny, MAny, nil, "*.foo", false}, // Bare and globs forbidden despite (potentially) allowing wildcards. - /* 94 */ {[]string{"x.*.x.foo"}, MAny, MAny, MAny, MAny, MAny, nil, "x.*.x.foo", false}, // Does not conform to RFC 6125 - - // === CN validation allowances === // - /* 95 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "*.fud.bar.foo", true}, - /* 96 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "*.fud.*.foo", true}, - /* 97 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "*.bar.*.bar", true}, - /* 98 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "foo@foo", true}, - /* 99 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "foo@foo@foo", true}, - /* 100 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "bar@bar@bar", true}, - /* 101 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar@bar@bar", false}, - /* 102 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar@bar", false}, - /* 103 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar@foo", true}, - /* 104 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar@foo", false}, - /* 105 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar@bar", false}, - /* 106 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar.foo", true}, - /* 107 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar.bar", false}, - /* 108 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar.foo", false}, - /* 109 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar.bar", false}, - } - - if len(testCases) != 110 { - t.Fatalf("misnumbered test case entries will make it hard to find bugs: %v", len(testCases)) - } - - b, s := CreateBackendWithStorage(t) - - // We need a RSA key so all signature sizes are valid with it. - resp, err := CBWrite(b, s, "root/generate/exported", map[string]interface{}{ - "common_name": "myvault.com", - "ttl": "128h", - "key_type": "rsa", - "key_bits": 2048, - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected ca info") - } - - tested := 0 - for index, test := range testCases { - tested += RoleIssuanceRegressionHelper(t, b, s, index, test) - } - - t.Logf("Issuance regression expanded matrix test scenarios: %d", tested) -} - -type KeySizeRegression struct { - // Values reused for both Role and CA configuration. - RoleKeyType string - RoleKeyBits []int - - // Signature Bits presently is only specified on the role. - RoleSignatureBits []int - RoleUsePSS bool - - // These are tuples; must be of the same length. - TestKeyTypes []string - TestKeyBits []int - - // All of the above key types/sizes must pass or fail together. - ExpectError bool -} - -func (k KeySizeRegression) KeyTypeValues() []string { - if k.RoleKeyType == "any" { - return []string{"rsa", "ec", "ed25519"} - } - - return []string{k.RoleKeyType} -} - -func RoleKeySizeRegressionHelper(t *testing.T, b *backend, s logical.Storage, index int, test KeySizeRegression) int { - tested := 0 - - for _, caKeyType := range test.KeyTypeValues() { - for _, caKeyBits := range test.RoleKeyBits { - // Generate a new CA key. - resp, err := CBWrite(b, s, "root/generate/exported", map[string]interface{}{ - "common_name": "myvault.com", - "ttl": "128h", - "key_type": caKeyType, - "key_bits": caKeyBits, - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected ca info") - } - - for _, roleKeyBits := range test.RoleKeyBits { - for _, roleSignatureBits := range test.RoleSignatureBits { - role := fmt.Sprintf("key-size-regression-%d-keytype-%v-keybits-%d-signature-bits-%d", index, test.RoleKeyType, roleKeyBits, roleSignatureBits) - _, err := CBWrite(b, s, "roles/"+role, map[string]interface{}{ - "key_type": test.RoleKeyType, - "key_bits": roleKeyBits, - "signature_bits": roleSignatureBits, - "use_pss": test.RoleUsePSS, - }) - if err != nil { - t.Fatal(err) - } - - for index, keyType := range test.TestKeyTypes { - keyBits := test.TestKeyBits[index] - - _, _, csrPem := generateCSR(t, &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "localhost", - }, - }, keyType, keyBits) - - resp, err = CBWrite(b, s, "sign/"+role, map[string]interface{}{ - "common_name": "localhost", - "csr": csrPem, - }) - - haveErr := err != nil || resp == nil - - if haveErr != test.ExpectError { - t.Fatalf("key size regression test [%d] failed: haveErr: %v, expectErr: %v, err: %v, resp: %v, test case: %v, caKeyType: %v, caKeyBits: %v, role: %v, keyType: %v, keyBits: %v", index, haveErr, test.ExpectError, err, resp, test, caKeyType, caKeyBits, role, keyType, keyBits) - } - - if resp != nil && test.RoleUsePSS && caKeyType == "rsa" { - leafCert := parseCert(t, resp.Data["certificate"].(string)) - switch leafCert.SignatureAlgorithm { - case x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS: - default: - t.Fatalf("key size regression test [%d] failed on role %v: unexpected signature algorithm; expected RSA-type CA to sign a leaf cert with PSS algorithm; got %v", index, role, leafCert.SignatureAlgorithm.String()) - } - } - - tested += 1 - } - } - } - - _, err = CBDelete(b, s, "root") - if err != nil { - t.Fatal(err) - } - } - } - - return tested -} - -func TestBackend_Roles_KeySizeRegression(t *testing.T) { - t.Parallel() - // Regression testing of role's issuance policy. - testCases := []KeySizeRegression{ - // RSA with default parameters should fail to issue smaller RSA keys - // and any size ECDSA/Ed25519 keys. - /* 0 */ {"rsa", []int{0, 2048}, []int{0, 256, 384, 512}, false, []string{"rsa", "ec", "ec", "ec", "ec", "ed25519"}, []int{1024, 224, 256, 384, 521, 0}, true}, - // But it should work to issue larger RSA keys. - /* 1 */ {"rsa", []int{0, 2048}, []int{0, 256, 384, 512}, false, []string{"rsa", "rsa"}, []int{2048, 3072}, false}, - - // EC with default parameters should fail to issue smaller EC keys - // and any size RSA/Ed25519 keys. - /* 2 */ {"ec", []int{0}, []int{0}, false, []string{"rsa", "ec", "ed25519"}, []int{2048, 224, 0}, true}, - // But it should work to issue larger EC keys. Note that we should be - // independent of signature bits as that's computed from the issuer - // type (for EC based issuers). - /* 3 */ {"ec", []int{224}, []int{0, 256, 384, 521}, false, []string{"ec", "ec", "ec", "ec"}, []int{224, 256, 384, 521}, false}, - /* 4 */ {"ec", []int{0, 256}, []int{0, 256, 384, 521}, false, []string{"ec", "ec", "ec"}, []int{256, 384, 521}, false}, - /* 5 */ {"ec", []int{384}, []int{0, 256, 384, 521}, false, []string{"ec", "ec"}, []int{384, 521}, false}, - /* 6 */ {"ec", []int{521}, []int{0, 256, 384, 512}, false, []string{"ec"}, []int{521}, false}, - - // Ed25519 should reject RSA and EC keys. - /* 7 */ {"ed25519", []int{0}, []int{0}, false, []string{"rsa", "ec", "ec"}, []int{2048, 256, 521}, true}, - // But it should work to issue Ed25519 keys. - /* 8 */ {"ed25519", []int{0}, []int{0}, false, []string{"ed25519"}, []int{0}, false}, - - // Any key type should reject insecure RSA key sizes. - /* 9 */ {"any", []int{0}, []int{0, 256, 384, 512}, false, []string{"rsa", "rsa"}, []int{512, 1024}, true}, - // But work for everything else. - /* 10 */ {"any", []int{0}, []int{0, 256, 384, 512}, false, []string{"rsa", "rsa", "ec", "ec", "ec", "ec", "ed25519"}, []int{2048, 3072, 224, 256, 384, 521, 0}, false}, - - // RSA with larger than default key size should reject smaller ones. - /* 11 */ {"rsa", []int{3072}, []int{0, 256, 384, 512}, false, []string{"rsa"}, []int{2048}, true}, - - // We should be able to sign with PSS with any CA key type. - /* 12 */ {"rsa", []int{0}, []int{0, 256, 384, 512}, true, []string{"rsa"}, []int{2048}, false}, - /* 13 */ {"ec", []int{0}, []int{0}, true, []string{"ec"}, []int{256}, false}, - /* 14 */ {"ed25519", []int{0}, []int{0}, true, []string{"ed25519"}, []int{0}, false}, - } - - if len(testCases) != 15 { - t.Fatalf("misnumbered test case entries will make it hard to find bugs: %v", len(testCases)) - } - - b, s := CreateBackendWithStorage(t) - - tested := 0 - for index, test := range testCases { - tested += RoleKeySizeRegressionHelper(t, b, s, index, test) - } - - t.Logf("Key size regression expanded matrix test scenarios: %d", tested) -} - -func TestRootWithExistingKey(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - var err error - - // Fail requests if type is existing, and we specify the key_type param - _, err = CBWrite(b, s, "root/generate/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "key_type": "rsa", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "key_type nor key_bits arguments can be set in this mode") - - // Fail requests if type is existing, and we specify the key_bits param - _, err = CBWrite(b, s, "root/generate/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "key_bits": "2048", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "key_type nor key_bits arguments can be set in this mode") - - // Fail if the specified key does not exist. - _, err = CBWrite(b, s, "issuers/generate/root/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "issuer_name": "my-issuer1", - "key_ref": "my-key1", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "unable to find PKI key for reference: my-key1") - - // Fail if the specified key name is default. - _, err = CBWrite(b, s, "issuers/generate/root/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "issuer_name": "my-issuer1", - "key_name": "Default", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "reserved keyword 'default' can not be used as key name") - - // Fail if the specified issuer name is default. - _, err = CBWrite(b, s, "issuers/generate/root/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "issuer_name": "DEFAULT", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "reserved keyword 'default' can not be used as issuer name") - - // Create the first CA - resp, err := CBWrite(b, s, "issuers/generate/root/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "key_type": "rsa", - "issuer_name": "my-issuer1", - }) - require.NoError(t, err) - require.NotNil(t, resp.Data["certificate"]) - myIssuerId1 := resp.Data["issuer_id"] - myKeyId1 := resp.Data["key_id"] - require.NotEmpty(t, myIssuerId1) - require.NotEmpty(t, myKeyId1) - - // Fetch the parsed CRL; it should be empty as we've not revoked anything - parsedCrl := getParsedCrlFromBackend(t, b, s, "issuer/my-issuer1/crl/der") - require.Equal(t, len(parsedCrl.TBSCertList.RevokedCertificates), 0, "should have no revoked certificates") - - // Fail if the specified issuer name is re-used. - _, err = CBWrite(b, s, "issuers/generate/root/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "issuer_name": "my-issuer1", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "issuer name already in use") - - // Create the second CA - resp, err = CBWrite(b, s, "issuers/generate/root/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "key_type": "rsa", - "issuer_name": "my-issuer2", - "key_name": "root-key2", - }) - require.NoError(t, err) - require.NotNil(t, resp.Data["certificate"]) - myIssuerId2 := resp.Data["issuer_id"] - myKeyId2 := resp.Data["key_id"] - require.NotEmpty(t, myIssuerId2) - require.NotEmpty(t, myKeyId2) - - // Fetch the parsed CRL; it should be empty as we've not revoked anything - parsedCrl = getParsedCrlFromBackend(t, b, s, "issuer/my-issuer2/crl/der") - require.Equal(t, len(parsedCrl.TBSCertList.RevokedCertificates), 0, "should have no revoked certificates") - - // Fail if the specified key name is re-used. - _, err = CBWrite(b, s, "issuers/generate/root/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "issuer_name": "my-issuer3", - "key_name": "root-key2", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "key name already in use") - - // Create a third CA re-using key from CA 1 - resp, err = CBWrite(b, s, "issuers/generate/root/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "issuer_name": "my-issuer3", - "key_ref": myKeyId1, - }) - require.NoError(t, err) - require.NotNil(t, resp.Data["certificate"]) - myIssuerId3 := resp.Data["issuer_id"] - myKeyId3 := resp.Data["key_id"] - require.NotEmpty(t, myIssuerId3) - require.NotEmpty(t, myKeyId3) - - // Fetch the parsed CRL; it should be empty as we've not revoking anything. - parsedCrl = getParsedCrlFromBackend(t, b, s, "issuer/my-issuer3/crl/der") - require.Equal(t, len(parsedCrl.TBSCertList.RevokedCertificates), 0, "should have no revoked certificates") - // Signatures should be the same since this is just a reissued cert. We - // use signature as a proxy for "these two CRLs are equal". - firstCrl := getParsedCrlFromBackend(t, b, s, "issuer/my-issuer1/crl/der") - require.Equal(t, parsedCrl.SignatureValue, firstCrl.SignatureValue) - - require.NotEqual(t, myIssuerId1, myIssuerId2) - require.NotEqual(t, myIssuerId1, myIssuerId3) - require.NotEqual(t, myKeyId1, myKeyId2) - require.Equal(t, myKeyId1, myKeyId3) - - resp, err = CBList(b, s, "issuers") - require.NoError(t, err) - require.Equal(t, 3, len(resp.Data["keys"].([]string))) - require.Contains(t, resp.Data["keys"], string(myIssuerId1.(issuerID))) - require.Contains(t, resp.Data["keys"], string(myIssuerId2.(issuerID))) - require.Contains(t, resp.Data["keys"], string(myIssuerId3.(issuerID))) -} - -func TestIntermediateWithExistingKey(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - var err error - - // Fail requests if type is existing, and we specify the key_type param - _, err = CBWrite(b, s, "intermediate/generate/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "key_type": "rsa", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "key_type nor key_bits arguments can be set in this mode") - - // Fail requests if type is existing, and we specify the key_bits param - _, err = CBWrite(b, s, "intermediate/generate/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "key_bits": "2048", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "key_type nor key_bits arguments can be set in this mode") - - // Fail if the specified key does not exist. - _, err = CBWrite(b, s, "issuers/generate/intermediate/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "key_ref": "my-key1", - }) - require.Error(t, err) - require.Contains(t, err.Error(), "unable to find PKI key for reference: my-key1") - - // Create the first intermediate CA - resp, err := CBWrite(b, s, "issuers/generate/intermediate/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "key_type": "rsa", - }) - require.NoError(t, err) - // csr1 := resp.Data["csr"] - myKeyId1 := resp.Data["key_id"] - require.NotEmpty(t, myKeyId1) - - // Create the second intermediate CA - resp, err = CBWrite(b, s, "issuers/generate/intermediate/internal", map[string]interface{}{ - "common_name": "root myvault.com", - "key_type": "rsa", - "key_name": "interkey1", - }) - require.NoError(t, err) - // csr2 := resp.Data["csr"] - myKeyId2 := resp.Data["key_id"] - require.NotEmpty(t, myKeyId2) - - // Create a third intermediate CA re-using key from intermediate CA 1 - resp, err = CBWrite(b, s, "issuers/generate/intermediate/existing", map[string]interface{}{ - "common_name": "root myvault.com", - "key_ref": myKeyId1, - }) - require.NoError(t, err) - // csr3 := resp.Data["csr"] - myKeyId3 := resp.Data["key_id"] - require.NotEmpty(t, myKeyId3) - - require.NotEqual(t, myKeyId1, myKeyId2) - require.Equal(t, myKeyId1, myKeyId3, "our new ca did not seem to reuse the key as we expected.") -} - -func TestIssuanceTTLs(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "root example.com", - "issuer_name": "root", - "ttl": "10s", - "key_type": "ec", - }) - require.NoError(t, err) - require.NotNil(t, resp) - rootCert := parseCert(t, resp.Data["certificate"].(string)) - - _, err = CBWrite(b, s, "roles/local-testing", map[string]interface{}{ - "allow_any_name": true, - "enforce_hostnames": false, - "key_type": "ec", - }) - require.NoError(t, err) - - _, err = CBWrite(b, s, "issue/local-testing", map[string]interface{}{ - "common_name": "testing", - "ttl": "1s", - }) - require.NoError(t, err, "expected issuance to succeed due to shorter ttl than cert ttl") - - _, err = CBWrite(b, s, "issue/local-testing", map[string]interface{}{ - "common_name": "testing", - }) - require.Error(t, err, "expected issuance to fail due to longer default ttl than cert ttl") - - resp, err = CBPatch(b, s, "issuer/root", map[string]interface{}{ - "leaf_not_after_behavior": "permit", - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.Equal(t, resp.Data["leaf_not_after_behavior"], "permit") - - _, err = CBWrite(b, s, "issue/local-testing", map[string]interface{}{ - "common_name": "testing", - }) - require.NoError(t, err, "expected issuance to succeed due to permitted longer TTL") - - resp, err = CBWrite(b, s, "issuer/root", map[string]interface{}{ - "issuer_name": "root", - "leaf_not_after_behavior": "truncate", - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.Equal(t, resp.Data["leaf_not_after_behavior"], "truncate") - - _, err = CBWrite(b, s, "issue/local-testing", map[string]interface{}{ - "common_name": "testing", - }) - require.NoError(t, err, "expected issuance to succeed due to truncated ttl") - - // Sleep until the parent cert expires and the clock rolls over - // to the next second. - time.Sleep(time.Until(rootCert.NotAfter) + (1500 * time.Millisecond)) - - resp, err = CBWrite(b, s, "issuer/root", map[string]interface{}{ - "issuer_name": "root", - "leaf_not_after_behavior": "err", - }) - require.NoError(t, err) - require.NotNil(t, resp) - - // Even 1s ttl should now fail. - _, err = CBWrite(b, s, "issue/local-testing", map[string]interface{}{ - "common_name": "testing", - "ttl": "1s", - }) - require.Error(t, err, "expected issuance to fail due to longer default ttl than cert ttl") -} - -func TestSealWrappedStorageConfigured(t *testing.T) { - t.Parallel() - b, _ := CreateBackendWithStorage(t) - wrappedEntries := b.Backend.PathsSpecial.SealWrapStorage - - // Make sure our legacy bundle is within the list - // NOTE: do not convert these test values to constants, we should always have these paths within seal wrap config - require.Contains(t, wrappedEntries, "config/ca_bundle", "Legacy bundle missing from seal wrap") - // The trailing / is important as it treats the entire folder requiring seal wrapping, not just config/key - require.Contains(t, wrappedEntries, "config/key/", "key prefix with trailing / missing from seal wrap.") -} - -func TestBackend_ConfigCA_WithECParams(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // Generated key with OpenSSL: - // $ openssl ecparam -out p256.key -name prime256v1 -genkey - // - // Regression test for https://github.com/hashicorp/vault/issues/16667 - resp, err := CBWrite(b, s, "config/ca", map[string]interface{}{ - "pem_bundle": ` ------BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MHcCAQEEINzXthCZdhyV7+wIEBl/ty+ctNsUS99ykTeax6EbYZtvoAoGCCqGSM49 -AwEHoUQDQgAE57NX8bR/nDoW8yRgLswoXBQcjHrdyfuHS0gPwki6BNnfunUzryVb -8f22/JWj6fsEF6AOADZlrswKIbR2Es9e/w== ------END EC PRIVATE KEY----- - `, - }) - require.NoError(t, err) - require.NotNil(t, resp, "expected ca info") - importedKeys := resp.Data["imported_keys"].([]string) - importedIssuers := resp.Data["imported_issuers"].([]string) - - require.Equal(t, len(importedKeys), 1) - require.Equal(t, len(importedIssuers), 0) -} - -func TestPerIssuerAIA(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // Generating a root without anything should not have AIAs. - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "root example.com", - "issuer_name": "root", - "key_type": "ec", - }) - require.NoError(t, err) - require.NotNil(t, resp) - rootCert := parseCert(t, resp.Data["certificate"].(string)) - require.Empty(t, rootCert.OCSPServer) - require.Empty(t, rootCert.IssuingCertificateURL) - require.Empty(t, rootCert.CRLDistributionPoints) - - // Set some local URLs on the issuer. - _, err = CBWrite(b, s, "issuer/default", map[string]interface{}{ - "issuing_certificates": []string{"https://google.com"}, - }) - require.NoError(t, err) - - _, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allow_any_name": true, - "ttl": "85s", - "key_type": "ec", - }) - require.NoError(t, err) - - // Issue something with this re-configured issuer. - resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ - "common_name": "localhost.com", - }) - require.NoError(t, err) - require.NotNil(t, resp) - leafCert := parseCert(t, resp.Data["certificate"].(string)) - require.Empty(t, leafCert.OCSPServer) - require.Equal(t, leafCert.IssuingCertificateURL, []string{"https://google.com"}) - require.Empty(t, leafCert.CRLDistributionPoints) - - // Set global URLs and ensure they don't appear on this issuer's leaf. - _, err = CBWrite(b, s, "config/urls", map[string]interface{}{ - "issuing_certificates": []string{"https://example.com/ca", "https://backup.example.com/ca"}, - "crl_distribution_points": []string{"https://example.com/crl", "https://backup.example.com/crl"}, - "ocsp_servers": []string{"https://example.com/ocsp", "https://backup.example.com/ocsp"}, - }) - require.NoError(t, err) - resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ - "common_name": "localhost.com", - }) - require.NoError(t, err) - require.NotNil(t, resp) - leafCert = parseCert(t, resp.Data["certificate"].(string)) - require.Empty(t, leafCert.OCSPServer) - require.Equal(t, leafCert.IssuingCertificateURL, []string{"https://google.com"}) - require.Empty(t, leafCert.CRLDistributionPoints) - - // Now come back and remove the local modifications and ensure we get - // the defaults again. - _, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "issuing_certificates": []string{}, - }) - require.NoError(t, err) - resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ - "common_name": "localhost.com", - }) - require.NoError(t, err) - require.NotNil(t, resp) - leafCert = parseCert(t, resp.Data["certificate"].(string)) - require.Equal(t, leafCert.IssuingCertificateURL, []string{"https://example.com/ca", "https://backup.example.com/ca"}) - require.Equal(t, leafCert.OCSPServer, []string{"https://example.com/ocsp", "https://backup.example.com/ocsp"}) - require.Equal(t, leafCert.CRLDistributionPoints, []string{"https://example.com/crl", "https://backup.example.com/crl"}) - - // Validate that we can set an issuer name and remove it. - _, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "issuer_name": "my-issuer", - }) - require.NoError(t, err) - _, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "issuer_name": "", - }) - require.NoError(t, err) -} - -func TestIssuersWithoutCRLBits(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // Importing a root without CRL signing bits should work fine. - customBundleWithoutCRLBits := ` ------BEGIN CERTIFICATE----- -MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhyb290 -LW5ldzAeFw0yMjA4MjQxMjEzNTVaFw0yMzA5MDMxMjEzNTVaMBMxETAPBgNVBAMM -CHJvb3QtbmV3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAojTA/Mx7 -LVW/Zgn/N4BqZbaF82MrTIBFug3ob7mqycNRlWp4/PH8v37+jYn8e691HUsKjden -rDTrO06kiQKiJinAzmlLJvgcazE3aXoh7wSzVG9lFHYvljEmVj+yDbkeaqaCktup -skuNjxCoN9BLmKzZIwVCHn92ZHlhN6LI7CNaU3SDJdu7VftWF9Ugzt9FIvI+6Gcn -/WNE9FWvZ9o7035rZ+1vvTn7/tgxrj2k3XvD51Kq4tsSbqjnSf3QieXT6E6uvtUE -TbPp3xjBElgBCKmeogR1l28rs1aujqqwzZ0B/zOeF8ptaH0aZOIBsVDJR8yTwHzq -s34hNdNfKLHzOwIDAQABo3gwdjAdBgNVHQ4EFgQUF4djNmx+1+uJINhZ82pN+7jz -H8EwHwYDVR0jBBgwFoAUF4djNmx+1+uJINhZ82pN+7jzH8EwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAoQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZI -hvcNAQELBQADggEBAICQovBz4KLWlLmXeZ2Vf6WfQYyGNgGyJa10XNXtWQ5dM2NU -OLAit4x1c2dz+aFocc8ZsX/ikYi/bruT2rsGWqMAGC4at3U4GuaYGO5a6XzMKIDC -nxIlbiO+Pn6Xum7fAqUri7+ZNf/Cygmc5sByi3MAAIkszeObUDZFTJL7gEOuXIMT -rKIXCINq/U+qc7m9AQ8vKhF1Ddj+dLGLzNQ5j3cKfilPs/wRaYqbMQvnmarX+5Cs -k1UL6kWSQsiP3+UWaBlcWkmD6oZ3fIG7c0aMxf7RISq1eTAM9XjH3vMxWQJlS5q3 -2weJ2LYoPe/DwX5CijR0IezapBCrin1BscJMLFQ= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCiNMD8zHstVb9m -Cf83gGpltoXzYytMgEW6DehvuarJw1GVanj88fy/fv6Nifx7r3UdSwqN16esNOs7 -TqSJAqImKcDOaUsm+BxrMTdpeiHvBLNUb2UUdi+WMSZWP7INuR5qpoKS26myS42P -EKg30EuYrNkjBUIef3ZkeWE3osjsI1pTdIMl27tV+1YX1SDO30Ui8j7oZyf9Y0T0 -Va9n2jvTfmtn7W+9Ofv+2DGuPaTde8PnUqri2xJuqOdJ/dCJ5dPoTq6+1QRNs+nf -GMESWAEIqZ6iBHWXbyuzVq6OqrDNnQH/M54Xym1ofRpk4gGxUMlHzJPAfOqzfiE1 -018osfM7AgMBAAECggEAAVd6kZZaN69IZITIc1vHRYa2rlZpKS2JP7c8Vd3Z/4Fz -ZZvnJ7LgVAmUYg5WPZ2sOqBNLfKVN/oke5Q0dALgdxYl7dWQIhPjHeRFbZFtjqEV -OXZGBniamMO/HSKGWGrqFf7BM/H7AhClUwQgjnzVSz+B+LJJidM+SVys3n1xuDmC -EP+iOda+bAHqHv/7oCELQKhLmCvPc9v2fDy+180ttdo8EHuxwVnKiyR/ryKFhSyx -K1wgAPQ9jO+V+GESL90rqpX/r501REsIOOpm4orueelHTD4+dnHxvUPqJ++9aYGX -79qBNPPUhxrQI1yoHxwW0cTxW5EqkZ9bT2lSd5rjcQKBgQDNyPBpidkHPrYemQDT -RldtS6FiW/jc1It/CRbjU4A6Gi7s3Cda43pEUObKNLeXMyLQaMf4GbDPDX+eh7B8 -RkUq0Q/N0H4bn1hbxYSUdgv0j/6czpMo6rLcJHGwOTSpHGsNsxSLL7xlpgzuzqrG -FzEgjMA1aD3w8B9+/77AoSLoMQKBgQDJyYMw82+euLYRbR5Wc/SbrWfh2n1Mr2BG -pp1ZNYorXE5CL4ScdLcgH1q/b8r5XGwmhMcpeA+geAAaKmk1CGG+gPLoq20c9Q1Y -Ykq9tUVJasIkelvbb/SPxyjkJdBwylzcPP14IJBsqQM0be+yVqLJJVHSaoKhXZcl -IW2xgCpjKwKBgFpeX5U5P+F6nKebMU2WmlYY3GpBUWxIummzKCX0SV86mFjT5UR4 -mPzfOjqaI/V2M1eqbAZ74bVLjDumAs7QXReMb5BGetrOgxLqDmrT3DQt9/YMkXtq -ddlO984XkRSisjB18BOfhvBsl0lX4I7VKHHO3amWeX0RNgOjc7VMDfRBAoGAWAQH -r1BfvZHACLXZ58fISCdJCqCsysgsbGS8eW77B5LJp+DmLQBT6DUE9j+i/0Wq/ton -rRTrbAkrsj4RicpQKDJCwe4UN+9DlOu6wijRQgbJC/Q7IOoieJxcX7eGxcve2UnZ -HY7GsD7AYRwa02UquCYJHIjM1enmxZFhMW1AD+UCgYEAm4jdNz5e4QjA4AkNF+cB -ZenrAZ0q3NbTyiSsJEAtRe/c5fNFpmXo3mqgCannarREQYYDF0+jpSoTUY8XAc4q -wL7EZNzwxITLqBnnHQbdLdAvYxB43kvWTy+JRK8qY9LAMCCFeDoYwXkWV4Wkx/b0 -TgM7RZnmEjNdeaa4M52o7VY= ------END PRIVATE KEY----- - ` - resp, err := CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ - "pem_bundle": customBundleWithoutCRLBits, - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotEmpty(t, resp.Data) - require.NotEmpty(t, resp.Data["imported_issuers"]) - require.NotEmpty(t, resp.Data["imported_keys"]) - require.NotEmpty(t, resp.Data["mapping"]) - - // Shouldn't have crl-signing on the newly imported issuer's usage. - resp, err = CBRead(b, s, "issuer/default") - require.NoError(t, err) - require.NotNil(t, resp) - require.NotEmpty(t, resp.Data) - require.NotEmpty(t, resp.Data["usage"]) - require.NotContains(t, resp.Data["usage"], "crl-signing") - - // Modifying to set CRL should fail. - resp, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "usage": "issuing-certificates,crl-signing", - }) - require.Error(t, err) - require.True(t, resp.IsError()) - - // Modifying to set issuing-certificates and ocsp-signing should succeed. - resp, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "usage": "issuing-certificates,ocsp-signing", - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotEmpty(t, resp.Data) - require.NotEmpty(t, resp.Data["usage"]) - require.NotContains(t, resp.Data["usage"], "crl-signing") -} - -func TestBackend_IfModifiedSinceHeaders(t *testing.T) { - t.Parallel() - 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 - - // Mount PKI. - err := client.Sys().Mount("pki", &api.MountInput{ - Type: "pki", - Config: api.MountConfigInput{ - DefaultLeaseTTL: "16h", - MaxLeaseTTL: "60h", - // Required to allow the header to be passed through. - PassthroughRequestHeaders: []string{"if-modified-since"}, - AllowedResponseHeaders: []string{"Last-Modified"}, - }, - }) - require.NoError(t, err) - - // Get a time before CA generation. Subtract two seconds to ensure - // the value in the seconds field is different than the time the CA - // is actually generated at. - beforeOldCAGeneration := time.Now().Add(-2 * time.Second) - - // Generate an internal CA. This one is the default. - resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "Root X1", - "key_type": "ec", - "issuer_name": "old-root", - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data["certificate"]) - - // CA is generated, but give a grace window. - afterOldCAGeneration := time.Now().Add(2 * time.Second) - - // When you _save_ headers, client returns a copy. But when you go to - // reset them, it doesn't create a new copy (and instead directly - // assigns). This means we have to continually refresh our view of the - // last headers, otherwise the headers added after the last set operation - // leak into this copy... Yuck! - lastHeaders := client.Headers() - for _, path := range []string{"pki/cert/ca", "pki/cert/crl", "pki/issuer/default/json", "pki/issuer/old-root/json", "pki/issuer/old-root/crl", "pki/cert/delta-crl", "pki/issuer/old-root/crl/delta"} { - t.Logf("path: %v", path) - field := "certificate" - if strings.HasPrefix(path, "pki/issuer") && strings.Contains(path, "/crl") { - field = "crl" - } - - // Reading the CA should work, without a header. - resp, err := client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - - // Ensure that the CA is returned correctly if we give it the old time. - client.AddHeader("If-Modified-Since", beforeOldCAGeneration.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - - // Ensure that the CA is elided if we give it the present time (plus a - // grace window). - client.AddHeader("If-Modified-Since", afterOldCAGeneration.Format(time.RFC1123)) - t.Logf("headers: %v", client.Headers()) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - // Wait three seconds. This ensures we have adequate grace period - // to distinguish the two cases, even with grace periods. - time.Sleep(3 * time.Second) - - // Generating a second root. This one isn't the default. - beforeNewCAGeneration := time.Now().Add(-2 * time.Second) - - // Generate an internal CA. This one is the default. - _, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "Root X1", - "key_type": "ec", - "issuer_name": "new-root", - }) - require.NoError(t, err) - - // As above. - afterNewCAGeneration := time.Now().Add(2 * time.Second) - - // New root isn't the default, so it has fewer paths. - for _, path := range []string{"pki/issuer/new-root/json", "pki/issuer/new-root/crl", "pki/issuer/new-root/crl/delta"} { - t.Logf("path: %v", path) - field := "certificate" - if strings.HasPrefix(path, "pki/issuer") && strings.Contains(path, "/crl") { - field = "crl" - } - - // Reading the CA should work, without a header. - resp, err := client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - - // Ensure that the CA is returned correctly if we give it the old time. - client.AddHeader("If-Modified-Since", beforeNewCAGeneration.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - - // Ensure that the CA is elided if we give it the present time (plus a - // grace window). - client.AddHeader("If-Modified-Since", afterNewCAGeneration.Format(time.RFC1123)) - t.Logf("headers: %v", client.Headers()) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - // Wait three seconds. This ensures we have adequate grace period - // to distinguish the two cases, even with grace periods. - time.Sleep(3 * time.Second) - - // Now swap the default issuers around. - _, err = client.Logical().Write("pki/config/issuers", map[string]interface{}{ - "default": "new-root", - }) - require.NoError(t, err) - - // Reading both with the last modified date should return new values. - for _, path := range []string{"pki/cert/ca", "pki/cert/crl", "pki/issuer/default/json", "pki/issuer/old-root/json", "pki/issuer/new-root/json", "pki/issuer/old-root/crl", "pki/issuer/new-root/crl", "pki/cert/delta-crl", "pki/issuer/old-root/crl/delta", "pki/issuer/new-root/crl/delta"} { - t.Logf("path: %v", path) - field := "certificate" - if strings.HasPrefix(path, "pki/issuer") && strings.Contains(path, "/crl") { - field = "crl" - } - - // Ensure that the CA is returned correctly if we give it the old time. - client.AddHeader("If-Modified-Since", afterOldCAGeneration.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - - // Ensure that the CA is returned correctly if we give it the old time. - client.AddHeader("If-Modified-Since", afterNewCAGeneration.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - // Wait for things to settle, record the present time, and wait for the - // clock to definitely tick over again. - time.Sleep(2 * time.Second) - preRevocationTimestamp := time.Now() - time.Sleep(2 * time.Second) - - // The above tests should say everything is cached. - for _, path := range []string{"pki/cert/ca", "pki/cert/crl", "pki/issuer/default/json", "pki/issuer/old-root/json", "pki/issuer/new-root/json", "pki/issuer/old-root/crl", "pki/issuer/new-root/crl", "pki/cert/delta-crl", "pki/issuer/old-root/crl/delta", "pki/issuer/new-root/crl/delta"} { - t.Logf("path: %v", path) - - // Ensure that the CA is returned correctly if we give it the new time. - client.AddHeader("If-Modified-Since", preRevocationTimestamp.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - // We could generate some leaves and verify the revocation updates the - // CRL. But, revoking the issuer behaves the same, so let's do that - // instead. - _, err = client.Logical().Write("pki/issuer/old-root/revoke", map[string]interface{}{}) - require.NoError(t, err) - - // CA should still be valid. - for _, path := range []string{"pki/cert/ca", "pki/issuer/default/json", "pki/issuer/old-root/json", "pki/issuer/new-root/json"} { - t.Logf("path: %v", path) - - // Ensure that the CA is returned correctly if we give it the old time. - client.AddHeader("If-Modified-Since", preRevocationTimestamp.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - // CRL should be invalidated - for _, path := range []string{"pki/cert/crl", "pki/issuer/old-root/crl", "pki/issuer/new-root/crl", "pki/cert/delta-crl", "pki/issuer/old-root/crl/delta", "pki/issuer/new-root/crl/delta"} { - t.Logf("path: %v", path) - field := "certificate" - if strings.HasPrefix(path, "pki/issuer") && strings.Contains(path, "/crl") { - field = "crl" - } - - client.AddHeader("If-Modified-Since", preRevocationTimestamp.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - // If we send some time in the future, everything should be cached again! - futureTime := time.Now().Add(30 * time.Second) - for _, path := range []string{"pki/cert/ca", "pki/cert/crl", "pki/issuer/default/json", "pki/issuer/old-root/json", "pki/issuer/new-root/json", "pki/issuer/old-root/crl", "pki/issuer/new-root/crl", "pki/cert/delta-crl", "pki/issuer/old-root/crl/delta", "pki/issuer/new-root/crl/delta"} { - t.Logf("path: %v", path) - - // Ensure that the CA is returned correctly if we give it the new time. - client.AddHeader("If-Modified-Since", futureTime.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - beforeThreeWaySwap := time.Now().Add(-2 * time.Second) - - // Now, do a three-way swap of names (old->tmp; new->old; tmp->new). This - // should result in all names/CRLs being invalidated. - _, err = client.Logical().JSONMergePatch(ctx, "pki/issuer/old-root", map[string]interface{}{ - "issuer_name": "tmp-root", - }) - require.NoError(t, err) - _, err = client.Logical().JSONMergePatch(ctx, "pki/issuer/new-root", map[string]interface{}{ - "issuer_name": "old-root", - }) - require.NoError(t, err) - _, err = client.Logical().JSONMergePatch(ctx, "pki/issuer/tmp-root", map[string]interface{}{ - "issuer_name": "new-root", - }) - require.NoError(t, err) - - afterThreeWaySwap := time.Now().Add(2 * time.Second) - - for _, path := range []string{"pki/cert/ca", "pki/cert/crl", "pki/issuer/default/json", "pki/issuer/old-root/json", "pki/issuer/new-root/json", "pki/issuer/old-root/crl", "pki/issuer/new-root/crl", "pki/cert/delta-crl", "pki/issuer/old-root/crl/delta", "pki/issuer/new-root/crl/delta"} { - t.Logf("path: %v", path) - field := "certificate" - if strings.HasPrefix(path, "pki/issuer") && strings.Contains(path, "/crl") { - field = "crl" - } - - // Ensure that the CA is returned if we give it the pre-update time. - client.AddHeader("If-Modified-Since", beforeThreeWaySwap.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - - // Ensure that the CA is elided correctly if we give it the after time. - client.AddHeader("If-Modified-Since", afterThreeWaySwap.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - - // Finally, rebuild the delta CRL and ensure that only that is - // invalidated. We first need to enable it though, and wait for - // all CRLs to rebuild. - _, err = client.Logical().Write("pki/config/crl", map[string]interface{}{ - "auto_rebuild": true, - "enable_delta": true, - }) - require.NoError(t, err) - time.Sleep(4 * time.Second) - beforeDeltaRotation := time.Now().Add(-2 * time.Second) - - resp, err = client.Logical().Read("pki/crl/rotate-delta") - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.Equal(t, resp.Data["success"], true) - - afterDeltaRotation := time.Now().Add(2 * time.Second) - - for _, path := range []string{"pki/cert/ca", "pki/cert/crl", "pki/issuer/default/json", "pki/issuer/old-root/json", "pki/issuer/new-root/json", "pki/issuer/old-root/crl", "pki/issuer/new-root/crl"} { - t.Logf("path: %v", path) - - for _, when := range []time.Time{beforeDeltaRotation, afterDeltaRotation} { - client.AddHeader("If-Modified-Since", when.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } - } - - for _, path := range []string{"pki/cert/delta-crl", "pki/issuer/old-root/crl/delta", "pki/issuer/new-root/crl/delta"} { - t.Logf("path: %v", path) - field := "certificate" - if strings.HasPrefix(path, "pki/issuer") && strings.Contains(path, "/crl") { - field = "crl" - } - - // Ensure that the CRL is present if we give it the pre-update time. - client.AddHeader("If-Modified-Since", beforeDeltaRotation.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data[field]) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - - client.AddHeader("If-Modified-Since", afterDeltaRotation.Format(time.RFC1123)) - resp, err = client.Logical().Read(path) - require.NoError(t, err) - require.Nil(t, resp) - client.SetHeaders(lastHeaders) - lastHeaders = client.Headers() - } -} - -func TestBackend_InitializeCertificateCounts(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - ctx := context.Background() - - // Set up an Issuer and Role - // We need a root certificate to write/revoke certificates with - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - if resp == nil { - t.Fatal("expected ca info") - } - - // Create a role - _, err = CBWrite(b, s, "roles/example", map[string]interface{}{ - "allowed_domains": "myvault.com", - "allow_bare_domains": true, - "allow_subdomains": true, - "max_ttl": "2h", - }) - if err != nil { - t.Fatal(err) - } - - // Put certificates A, B, C, D, E in backend - var certificates []string = []string{"a", "b", "c", "d", "e"} - serials := make([]string, 5) - for i, cn := range certificates { - resp, err = CBWrite(b, s, "issue/example", map[string]interface{}{ - "common_name": cn + ".myvault.com", - }) - if err != nil { - t.Fatal(err) - } - serials[i] = resp.Data["serial_number"].(string) - } - - // Turn on certificate counting: - CBWrite(b, s, "config/auto-tidy", map[string]interface{}{ - "maintain_stored_certificate_counts": true, - "publish_stored_certificate_count_metrics": false, - }) - // Assert initialize from clean is correct: - b.initializeStoredCertificateCounts(ctx) - - // Revoke certificates A + B - revocations := serials[0:2] - for _, key := range revocations { - resp, err = CBWrite(b, s, "revoke", map[string]interface{}{ - "serial_number": key, - }) - if err != nil { - t.Fatal(err) - } - } - - if b.certCount.Load() != 6 { - t.Fatalf("Failed to count six certificates root,A,B,C,D,E, instead counted %d certs", b.certCount.Load()) - } - if b.revokedCertCount.Load() != 2 { - t.Fatalf("Failed to count two revoked certificates A+B, instead counted %d certs", b.revokedCertCount.Load()) - } - - // Simulates listing while initialize in progress, by "restarting it" - b.certCount.Store(0) - b.revokedCertCount.Store(0) - b.certsCounted.Store(false) - - // Revoke certificates C, D - dirtyRevocations := serials[2:4] - for _, key := range dirtyRevocations { - resp, err = CBWrite(b, s, "revoke", map[string]interface{}{ - "serial_number": key, - }) - if err != nil { - t.Fatal(err) - } - } - - // Put certificates F, G in the backend - dirtyCertificates := []string{"f", "g"} - for _, cn := range dirtyCertificates { - resp, err = CBWrite(b, s, "issue/example", map[string]interface{}{ - "common_name": cn + ".myvault.com", - }) - if err != nil { - t.Fatal(err) - } - } - - // Run initialize - b.initializeStoredCertificateCounts(ctx) - - // Test certificate count - if b.certCount.Load() != 8 { - t.Fatalf("Failed to initialize count of certificates root, A,B,C,D,E,F,G counted %d certs", b.certCount.Load()) - } - - if b.revokedCertCount.Load() != 4 { - t.Fatalf("Failed to count revoked certificates A,B,C,D counted %d certs", b.revokedCertCount.Load()) - } - - return -} - -// Verify that our default values are consistent when creating an issuer and when we do an -// empty POST update to it. This will hopefully identify if we have different default values -// for fields across the two APIs. -func TestBackend_VerifyIssuerUpdateDefaultsMatchCreation(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "myvault.com", - }) - requireSuccessNonNilResponse(t, resp, err, "failed generating root issuer") - - resp, err = CBRead(b, s, "issuer/default") - requireSuccessNonNilResponse(t, resp, err, "failed reading default issuer") - preUpdateValues := resp.Data - - // This field gets reset during issuer update to the empty string - // (meaning Go will auto-detect the rev-sig-algo). - preUpdateValues["revocation_signature_algorithm"] = "" - - resp, err = CBWrite(b, s, "issuer/default", map[string]interface{}{}) - requireSuccessNonNilResponse(t, resp, err, "failed updating default issuer with no values") - - resp, err = CBRead(b, s, "issuer/default") - requireSuccessNonNilResponse(t, resp, err, "failed reading default issuer") - postUpdateValues := resp.Data - - require.Equal(t, preUpdateValues, postUpdateValues, - "A value was updated based on the empty update of an issuer, "+ - "most likely we have a different set of field parameters across create and update of issuers.") -} - -func TestBackend_VerifyPSSKeysIssuersFailImport(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // PKCS8 parsing fails on this key due to rsaPSS OID - rsaOIDKey := ` ------BEGIN PRIVATE KEY----- -MIIEugIBADALBgkqhkiG9w0BAQoEggSmMIIEogIBAAKCAQEAtN0/NPuJHLuyEdBr -tUikXoXOV741XZcNvLAIVBIqDA0ege2gXt9A15FGUI4X3u6kT16Fl6MRdtUZ/qNS -Vs15nK9A1PI/AVekMgTVFTnoCzs550CKN8iRk9Om+lwHimpyXxKkFW69v8fsXwKE -Bsz69jjT7HV9VZQ7fQhmE79brAMuwKP1fUQKdHq5OBKtQ7Cl3Gmipp0izCsVuQIE -kBHvT3UUgyaSp2n+FONpOiyuBoYUH5tVEv9sZzBqSsrYBJYF+GvfnFy9AcTdqRe2 -VX2SjjWjDF84T30OBA798gIFIPwu9R4OjWOlPeh2bo2kGeo3AITjwFZ28m7kS7kc -OtvHpwIDAQABAoIBAFQxmjbj0RQbG+3HBBzD0CBgUYnu9ZC3vKFVoMriGci6YrVB -FSKU8u5mpkDhpKMWnE6GRdItCvgyg4NSLAZUaIRT4O5ARqwtTDYsobTb2/U+gNnx -5WXKbFpQcK6jIK+ClfNEDjYb8yDPxG0GEsfHrBvqoFy25L1t37N4sWwH7HjJyZIe -Hbqx4NVDur9qgqaUwkfSeufn4ycHqFtkzKNzCUarDkST9cxE6/1AKfhl09PPuMEa -lAY2JLiEplQL5sh9cxG5FObJbutJo5EIhR2OdM0VcPf0MTD9LXKRoGR3SNlG7IlS -llJzBjlh4J1ByMX32btKMHzEvlhyrMI90E1SEGECgYEAx1yDQWe4/b1MBqCxA3d0 -20dDmUHSRQFhkd/Mzkl5dPzRkG42W3ryNbMKdeuL0ZgK9AhfaLCjcj1i+44O7dHb -qBTVwfRrer2uoQVCqqJ6z8PGxPJJxTaqh9QuJxkoQ0i43ZNPcjc2M2sWLn+lkkdE -MaGMiyrmjIQEC6tmgCtZ1VUCgYEA6D9xoT9VuAnQjDvW2tO5N2U2H/8ZyRd1pC3z -H1CzjwShhxsP4YOUaVdw59K95JL4SMxSmpRrhthlW3cRaiT/exBcXLEvz0Qu0OhW -a6155ZFjK3UaLDKlwvmtuoAsuAFqX084LO0B1oxvUJESgyPncQ36fv2lZGV7A66z -Uo+BKQsCgYB2yGBMMAjA5nDN4iCV+C7gF+3m+pjWFKSVzcqxfoWndptGeuRYTUDT -TgIFkHqWPwkHrZVrQxOflYPMbi/m8wr1crSKA5+mWi4aMpAuKvERqYxc/B+IKbIh -jAKTuSGMNWAwZP0JCGx65mso+VUleuDe0Wpz4PPM9TuT2GQSKcI0oQKBgHAHcouC -npmo+lU65DgoWzaydrpWdpy+2Tt6AsW/Su4ZIMWoMy/oJaXuzQK2cG0ay/NpxArW -v0uLhNDrDZZzBF3blYIM4nALhr205UMJqjwntnuXACoDwFvdzoShIXEdFa+l6gYZ -yYIxudxWLmTd491wDb5GIgrcvMsY8V1I5dfjAoGAM9g2LtdqgPgK33dCDtZpBm8m -y4ri9PqHxnpps9WJ1dO6MW/YbW+a7vbsmNczdJ6XNLEfy2NWho1dw3xe7ztFVDjF -cWNUzs1+/6aFsi41UX7EFn3zAFhQUPxT59hXspuWuKbRAWc5fMnxbCfI/Cr8wTLJ -E/0kiZ4swUMyI4tYSbM= ------END PRIVATE KEY----- -` - _, err := CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ - "pem_bundle": rsaOIDKey, - }) - require.Error(t, err, "expected error importing PKCS8 rsaPSS OID key") - - _, err = CBWrite(b, s, "keys/import", map[string]interface{}{ - "key": rsaOIDKey, - }) - require.Error(t, err, "expected error importing PKCS8 rsaPSS OID key") - - // Importing a cert with rsaPSS OID should also fail - rsaOIDCert := ` ------BEGIN CERTIFICATE----- -MIIDfjCCAjGgAwIBAgIBATBCBgkqhkiG9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUA -oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogQCAgDeMBMxETAPBgNVBAMM -CHJvb3Qtb2xkMB4XDTIyMDkxNjE0MDEwM1oXDTIzMDkyNjE0MDEwM1owEzERMA8G -A1UEAwwIcm9vdC1vbGQwggEgMAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKCAQEAtN0/ -NPuJHLuyEdBrtUikXoXOV741XZcNvLAIVBIqDA0ege2gXt9A15FGUI4X3u6kT16F -l6MRdtUZ/qNSVs15nK9A1PI/AVekMgTVFTnoCzs550CKN8iRk9Om+lwHimpyXxKk -FW69v8fsXwKEBsz69jjT7HV9VZQ7fQhmE79brAMuwKP1fUQKdHq5OBKtQ7Cl3Gmi -pp0izCsVuQIEkBHvT3UUgyaSp2n+FONpOiyuBoYUH5tVEv9sZzBqSsrYBJYF+Gvf -nFy9AcTdqRe2VX2SjjWjDF84T30OBA798gIFIPwu9R4OjWOlPeh2bo2kGeo3AITj -wFZ28m7kS7kcOtvHpwIDAQABo3UwczAdBgNVHQ4EFgQUVGkTAUJ8inxIVGBlfxf4 -cDhRSnowHwYDVR0jBBgwFoAUVGkTAUJ8inxIVGBlfxf4cDhRSnowDAYDVR0TBAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwEwQgYJKoZI -hvcNAQEKMDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgB -ZQMEAgEFAKIEAgIA3gOCAQEAQZ3iQ3NjvS4FYJ5WG41huZI0dkvNFNan+ZYWlYHJ -MIQhbFogb/UQB0rlsuldG0+HF1RDXoYNuThfzt5hiBWYEtMBNurezvnOn4DF0hrl -Uk3sBVnvTalVXg+UVjqh9hBGB75JYJl6a5Oa2Zrq++4qGNwjd0FqgnoXzqS5UGuB -TJL8nlnXPuOIK3VHoXEy7l9GtvEzKcys0xa7g1PYpaJ5D2kpbBJmuQGmU6CDcbP+ -m0hI4QDfVfHtnBp2VMCvhj0yzowtwF4BFIhv4EXZBU10mzxVj0zyKKft9++X8auH -nebuK22ZwzbPe4NhOvAdfNDElkrrtGvTnzkDB7ezPYjelA== ------END CERTIFICATE----- -` - _, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ - "pem_bundle": rsaOIDCert, - }) - require.Error(t, err, "expected error importing PKCS8 rsaPSS OID cert") - - _, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ - "pem_bundle": rsaOIDKey + "\n" + rsaOIDCert, - }) - require.Error(t, err, "expected error importing PKCS8 rsaPSS OID key+cert") - - _, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ - "pem_bundle": rsaOIDCert + "\n" + rsaOIDKey, - }) - require.Error(t, err, "expected error importing PKCS8 rsaPSS OID cert+key") - - // After all these errors, we should have zero issuers and keys. - resp, err := CBList(b, s, "issuers") - require.NoError(t, err) - require.Equal(t, nil, resp.Data["keys"]) - - resp, err = CBList(b, s, "keys") - require.NoError(t, err) - require.Equal(t, nil, resp.Data["keys"]) - - // If we create a new PSS root, we should be able to issue an intermediate - // under it. - resp, err = CBWrite(b, s, "root/generate/exported", map[string]interface{}{ - "use_pss": "true", - "common_name": "root x1 - pss", - "key_type": "ec", - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data["certificate"]) - require.NotEmpty(t, resp.Data["private_key"]) - - resp, err = CBWrite(b, s, "intermediate/generate/exported", map[string]interface{}{ - "use_pss": "true", - "common_name": "int x1 - pss", - "key_type": "ec", - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data["csr"]) - require.NotEmpty(t, resp.Data["private_key"]) - - resp, err = CBWrite(b, s, "issuer/default/sign-intermediate", map[string]interface{}{ - "use_pss": "true", - "common_name": "int x1 - pss", - "csr": resp.Data["csr"].(string), - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.NotEmpty(t, resp.Data["certificate"]) - - resp, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ - "pem_bundle": resp.Data["certificate"].(string), - }) - require.NoError(t, err) - - // Finally, if we were to take an rsaPSS OID'd CSR and use it against this - // mount, it will fail. - _, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allow_any_name": true, - "ttl": "85s", - "key_type": "any", - }) - require.NoError(t, err) - - // Issuing a leaf from a CSR with rsaPSS OID should fail... - rsaOIDCSR := `-----BEGIN CERTIFICATE REQUEST----- -MIICkTCCAUQCAQAwGTEXMBUGA1UEAwwOcmFuY2hlci5teS5vcmcwggEgMAsGCSqG -SIb3DQEBCgOCAQ8AMIIBCgKCAQEAtzHuGEUK55lXI08yp9DXoye9yCZbkJZO+Hej -1TWGEkbX4hzauRJeNp2+wn8xU5y8ITjWSIXEVDHeezosLCSy0Y2QT7/V45zWPUYY -ld0oUnPiwsb9CPFlBRFnX3dO9SS5MONIrNCJGKXmLdF3lgSl8zPT6J/hWM+JBjHO -hBzK6L8IYwmcEujrQfnOnOztzgMEBJtWG8rnI8roz1adpczTddDKGymh2QevjhlL -X9CLeYSSQZInOMsgaDYl98Hn00K5x0CBp8ADzzXtaPSQ9nsnihN8VvZ/wHw6YbBS -BSHa6OD+MrYnw3Sao6/YgBRNT2glIX85uro4ARW9zGB9/748dwIDAQABoAAwQgYJ -KoZIhvcNAQEKMDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglg -hkgBZQMEAgEFAKIEAgIA3gOCAQEARGAa0HiwzWCpvAdLOVc4/srEyOYFZPLbtv+Y -ezZIaUBNaWhOvkunqpa48avmcbGlji7r6fxJ5sT28lHt7ODWcJfn1XPAnqesXErm -EBuOIhCv6WiwVyGeTVynuHYkHyw3rIL/zU7N8+zIFV2G2M1UAv5D/eyh/74cr9Of -+nvm9jAbkHix8UwOBCFY2LLNl6bXvbIeJEdDOEtA9UmDXs8QGBg4lngyqcE2Z7rz -+5N/x4guMk2FqblbFGiCc5fLB0Gp6lFFOqhX9Q8nLJ6HteV42xGJUUtsFpppNCRm -82dGIH2PTbXZ0k7iAAwLaPjzOv1v58Wq90o35d4iEsOfJ8v98Q== ------END CERTIFICATE REQUEST-----` - - _, err = CBWrite(b, s, "issuer/default/sign/testing", map[string]interface{}{ - "common_name": "example.com", - "csr": rsaOIDCSR, - }) - require.Error(t, err) - - _, err = CBWrite(b, s, "issuer/default/sign-verbatim", map[string]interface{}{ - "common_name": "example.com", - "use_pss": true, - "csr": rsaOIDCSR, - }) - require.Error(t, err) - - _, err = CBWrite(b, s, "issuer/default/sign-intermediate", map[string]interface{}{ - "common_name": "faulty x1 - pss", - "use_pss": true, - "csr": rsaOIDCSR, - }) - require.Error(t, err) - - // Vault has a weird API for signing self-signed certificates. Ensure - // that doesn't accept rsaPSS OID'd certificates either. - _, err = CBWrite(b, s, "issuer/default/sign-self-issued", map[string]interface{}{ - "use_pss": true, - "certificate": rsaOIDCert, - }) - require.Error(t, err) - - // Issuing a regular leaf should succeed. - _, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allow_any_name": true, - "ttl": "85s", - "key_type": "rsa", - "use_pss": "true", - }) - require.NoError(t, err) - - resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ - "common_name": "example.com", - "use_pss": "true", - }) - requireSuccessNonNilResponse(t, resp, err, "failed to issue PSS leaf") -} - -func TestPKI_EmptyCRLConfigUpgraded(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // Write an empty CRLConfig into storage. - crlConfigEntry, err := logical.StorageEntryJSON("config/crl", &crlConfig{}) - require.NoError(t, err) - err = s.Put(ctx, crlConfigEntry) - require.NoError(t, err) - - resp, err := CBRead(b, s, "config/crl") - require.NoError(t, err) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - require.Equal(t, resp.Data["expiry"], defaultCrlConfig.Expiry) - require.Equal(t, resp.Data["disable"], defaultCrlConfig.Disable) - require.Equal(t, resp.Data["ocsp_disable"], defaultCrlConfig.OcspDisable) - require.Equal(t, resp.Data["auto_rebuild"], defaultCrlConfig.AutoRebuild) - require.Equal(t, resp.Data["auto_rebuild_grace_period"], defaultCrlConfig.AutoRebuildGracePeriod) - require.Equal(t, resp.Data["enable_delta"], defaultCrlConfig.EnableDelta) - require.Equal(t, resp.Data["delta_rebuild_interval"], defaultCrlConfig.DeltaRebuildInterval) -} - -func TestPKI_ListRevokedCerts(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // Test empty cluster - resp, err := CBList(b, s, "certs/revoked") - requireSuccessNonNilResponse(t, resp, err, "failed listing empty cluster") - require.Empty(t, resp.Data, "response map contained data that we did not expect") - - // Set up a mount that we can revoke under (We will create 3 leaf certs, 2 of which will be revoked) - resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "test.com", - "key_type": "ec", - }) - requireSuccessNonNilResponse(t, resp, err, "error generating root CA") - requireFieldsSetInResp(t, resp, "serial_number") - issuerSerial := resp.Data["serial_number"] - - resp, err = CBWrite(b, s, "roles/test", map[string]interface{}{ - "allowed_domains": "test.com", - "allow_subdomains": "true", - "max_ttl": "1h", - }) - requireSuccessNonNilResponse(t, resp, err, "error setting up pki role") - - resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "test1.test.com", - }) - requireSuccessNonNilResponse(t, resp, err, "error issuing cert 1") - requireFieldsSetInResp(t, resp, "serial_number") - serial1 := resp.Data["serial_number"] - - resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "test2.test.com", - }) - requireSuccessNonNilResponse(t, resp, err, "error issuing cert 2") - requireFieldsSetInResp(t, resp, "serial_number") - serial2 := resp.Data["serial_number"] - - resp, err = CBWrite(b, s, "issue/test", map[string]interface{}{ - "common_name": "test3.test.com", - }) - requireSuccessNonNilResponse(t, resp, err, "error issuing cert 2") - requireFieldsSetInResp(t, resp, "serial_number") - serial3 := resp.Data["serial_number"] - - resp, err = CBWrite(b, s, "revoke", map[string]interface{}{"serial_number": serial1}) - requireSuccessNonNilResponse(t, resp, err, "error revoking cert 1") - - resp, err = CBWrite(b, s, "revoke", map[string]interface{}{"serial_number": serial2}) - requireSuccessNonNilResponse(t, resp, err, "error revoking cert 2") - - // Test that we get back the expected revoked serial numbers. - resp, err = CBList(b, s, "certs/revoked") - requireSuccessNonNilResponse(t, resp, err, "failed listing revoked certs") - requireFieldsSetInResp(t, resp, "keys") - revokedKeys := resp.Data["keys"].([]string) - - require.Contains(t, revokedKeys, serial1) - require.Contains(t, revokedKeys, serial2) - require.Equal(t, 2, len(revokedKeys), "Expected 2 revoked entries got %d: %v", len(revokedKeys), revokedKeys) - - // Test that listing our certs returns a different response - resp, err = CBList(b, s, "certs") - requireSuccessNonNilResponse(t, resp, err, "failed listing written certs") - requireFieldsSetInResp(t, resp, "keys") - certKeys := resp.Data["keys"].([]string) - - require.Contains(t, certKeys, serial1) - require.Contains(t, certKeys, serial2) - require.Contains(t, certKeys, serial3) - require.Contains(t, certKeys, issuerSerial) - require.Equal(t, 4, len(certKeys), "Expected 4 cert entries got %d: %v", len(certKeys), certKeys) -} - -func TestPKI_TemplatedAIAs(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // Setting templated AIAs should succeed. - _, err := CBWrite(b, s, "config/cluster", map[string]interface{}{ - "path": "http://localhost:8200/v1/pki", - "aia_path": "http://localhost:8200/cdn/pki", - }) - require.NoError(t, err) - - aiaData := map[string]interface{}{ - "crl_distribution_points": "{{cluster_path}}/issuer/{{issuer_id}}/crl/der", - "issuing_certificates": "{{cluster_aia_path}}/issuer/{{issuer_id}}/der", - "ocsp_servers": "{{cluster_path}}/ocsp", - "enable_templating": true, - } - _, err = CBWrite(b, s, "config/urls", aiaData) - require.NoError(t, err) - - // But root generation will fail. - rootData := map[string]interface{}{ - "common_name": "Long-Lived Root X1", - "issuer_name": "long-root-x1", - "key_type": "ec", - } - _, err = CBWrite(b, s, "root/generate/internal", rootData) - require.Error(t, err) - require.Contains(t, err.Error(), "unable to parse AIA URL") - - // Clearing the config and regenerating the root should succeed. - _, err = CBWrite(b, s, "config/urls", map[string]interface{}{ - "crl_distribution_points": "", - "issuing_certificates": "", - "ocsp_servers": "", - "enable_templating": false, - }) - require.NoError(t, err) - resp, err := CBWrite(b, s, "root/generate/internal", rootData) - requireSuccessNonNilResponse(t, resp, err) - issuerId := string(resp.Data["issuer_id"].(issuerID)) - - // Now write the original AIA config and sign a leaf. - _, err = CBWrite(b, s, "config/urls", aiaData) - require.NoError(t, err) - _, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allow_any_name": "true", - "key_type": "ec", - "ttl": "50m", - }) - require.NoError(t, err) - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "example.com", - }) - requireSuccessNonNilResponse(t, resp, err) - - // Validate the AIA info is correctly templated. - cert := parseCert(t, resp.Data["certificate"].(string)) - require.Equal(t, cert.OCSPServer, []string{"http://localhost:8200/v1/pki/ocsp"}) - require.Equal(t, cert.IssuingCertificateURL, []string{"http://localhost:8200/cdn/pki/issuer/" + issuerId + "/der"}) - require.Equal(t, cert.CRLDistributionPoints, []string{"http://localhost:8200/v1/pki/issuer/" + issuerId + "/crl/der"}) - - // Modify our issuer to set custom AIAs: these URLs are bad. - _, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "enable_aia_url_templating": "false", - "crl_distribution_points": "a", - "issuing_certificates": "b", - "ocsp_servers": "c", - }) - require.Error(t, err) - - // These URLs are good. - _, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "enable_aia_url_templating": "false", - "crl_distribution_points": "http://localhost/a", - "issuing_certificates": "http://localhost/b", - "ocsp_servers": "http://localhost/c", - }) - - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "example.com", - }) - requireSuccessNonNilResponse(t, resp, err) - - // Validate the AIA info is correctly templated. - cert = parseCert(t, resp.Data["certificate"].(string)) - require.Equal(t, cert.OCSPServer, []string{"http://localhost/c"}) - require.Equal(t, cert.IssuingCertificateURL, []string{"http://localhost/b"}) - require.Equal(t, cert.CRLDistributionPoints, []string{"http://localhost/a"}) - - // These URLs are bad, but will fail at issuance time due to AIA templating. - resp, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "enable_aia_url_templating": "true", - "crl_distribution_points": "a", - "issuing_certificates": "b", - "ocsp_servers": "c", - }) - requireSuccessNonNilResponse(t, resp, err) - require.NotEmpty(t, resp.Warnings) - _, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "example.com", - }) - require.Error(t, err) -} - -func requireSubjectUserIDAttr(t *testing.T, cert string, target string) { - xCert := parseCert(t, cert) - - for _, attr := range xCert.Subject.Names { - var userID string - if attr.Type.Equal(certutil.SubjectPilotUserIDAttributeOID) { - if target == "" { - t.Fatalf("expected no UserID (OID: %v) subject attributes in cert:\n%v", certutil.SubjectPilotUserIDAttributeOID, cert) - } - - switch aValue := attr.Value.(type) { - case string: - userID = aValue - case []byte: - userID = string(aValue) - default: - t.Fatalf("unknown type for UserID attribute: %v\nCert: %v", attr, cert) - } - - if userID == target { - return - } - } - } - - if target != "" { - t.Fatalf("failed to find UserID (OID: %v) matching %v in cert:\n%v", certutil.SubjectPilotUserIDAttributeOID, target, cert) - } -} - -func TestUserIDsInLeafCerts(t *testing.T) { - t.Parallel() - b, s := CreateBackendWithStorage(t) - - // 1. Setup root issuer. - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "Vault Root CA", - "key_type": "ec", - "ttl": "7200h", - }) - requireSuccessNonNilResponse(t, resp, err, "failed generating root issuer") - - // 2. Allow no user IDs. - resp, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allowed_user_ids": "", - "key_type": "ec", - }) - requireSuccessNonNilResponse(t, resp, err, "failed setting up role") - - // - Issue cert without user IDs should work. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "") - - // - Issue cert with user ID should fail. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid", - }) - require.Error(t, err) - require.True(t, resp.IsError()) - - // 3. Allow any user IDs. - resp, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allowed_user_ids": "*", - "key_type": "ec", - }) - requireSuccessNonNilResponse(t, resp, err, "failed setting up role") - - // - Issue cert without user IDs. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "") - - // - Issue cert with one user ID. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") - - // - Issue cert with two user IDs. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid,robot", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "robot") - - // 4. Allow one specific user ID. - resp, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allowed_user_ids": "humanoid", - "key_type": "ec", - }) - requireSuccessNonNilResponse(t, resp, err, "failed setting up role") - - // - Issue cert without user IDs. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "") - - // - Issue cert with approved ID. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") - - // - Issue cert with non-approved user ID should fail. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "robot", - }) - require.Error(t, err) - require.True(t, resp.IsError()) - - // - Issue cert with one approved and one non-approved should also fail. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid,robot", - }) - require.Error(t, err) - require.True(t, resp.IsError()) - - // 5. Allow two specific user IDs. - resp, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allowed_user_ids": "humanoid,robot", - "key_type": "ec", - }) - requireSuccessNonNilResponse(t, resp, err, "failed setting up role") - - // - Issue cert without user IDs. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "") - - // - Issue cert with one approved ID. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") - - // - Issue cert with other user ID. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "robot", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "robot") - - // - Issue cert with unknown user ID will fail. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "robot2", - }) - require.Error(t, err) - require.True(t, resp.IsError()) - - // - Issue cert with both should succeed. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid,robot", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "robot") - - // 6. Use a glob. - resp, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ - "allowed_user_ids": "human*", - "key_type": "ec", - "use_csr_sans": true, // setup for further testing. - }) - requireSuccessNonNilResponse(t, resp, err, "failed setting up role") - - // - Issue cert without user IDs. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "") - - // - Issue cert with approved ID. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "humanoid", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") - - // - Issue cert with another approved ID. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "human", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "human") - - // - Issue cert with literal glob. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "human*", - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "human*") - - // - Still no robotic certs are allowed; will fail. - resp, err = CBWrite(b, s, "issue/testing", map[string]interface{}{ - "common_name": "localhost", - "user_ids": "robot", - }) - require.Error(t, err) - require.True(t, resp.IsError()) - - // Create a CSR and validate it works with both sign/ and sign-verbatim. - csrTemplate := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "localhost", - ExtraNames: []pkix.AttributeTypeAndValue{ - { - Type: certutil.SubjectPilotUserIDAttributeOID, - Value: "humanoid", - }, - }, - }, - } - _, _, csrPem := generateCSR(t, &csrTemplate, "ec", 256) - - // Should work with role-based signing. - resp, err = CBWrite(b, s, "sign/testing", map[string]interface{}{ - "csr": csrPem, - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") - - // - Definitely will work with sign-verbatim. - resp, err = CBWrite(b, s, "sign-verbatim", map[string]interface{}{ - "csr": csrPem, - }) - requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") - requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") -} - -// TestStandby_Operations test proper forwarding for PKI requests from a standby node to the -// active node within a cluster. -func TestStandby_Operations(t *testing.T) { - conf, opts := teststorage.ClusterSetup(&vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "pki": Factory, - }, - }, nil, teststorage.InmemBackendSetup) - cluster := vault.NewTestCluster(t, conf, opts) - cluster.Start() - defer cluster.Cleanup() - - testhelpers.WaitForActiveNodeAndStandbys(t, cluster) - standbyCores := testhelpers.DeriveStandbyCores(t, cluster) - require.Greater(t, len(standbyCores), 0, "Need at least one standby core.") - client := standbyCores[0].Client - - mountPKIEndpoint(t, client, "pki") - - _, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{ - "key_type": "ec", - "common_name": "root-ca.com", - "ttl": "600h", - }) - require.NoError(t, err, "error setting up pki role: %v", err) - - _, err = client.Logical().Write("pki/roles/example", map[string]interface{}{ - "allowed_domains": "example.com", - "allow_subdomains": "true", - "no_store": "false", // make sure we store this cert - "ttl": "5h", - "key_type": "ec", - }) - require.NoError(t, err, "error setting up pki role: %v", err) - - resp, err := client.Logical().Write("pki/issue/example", map[string]interface{}{ - "common_name": "test.example.com", - }) - require.NoError(t, err, "error issuing certificate: %v", err) - require.NotNil(t, resp, "got nil response from issuing request") - serialOfCert := resp.Data["serial_number"].(string) - - resp, err = client.Logical().Write("pki/revoke", map[string]interface{}{ - "serial_number": serialOfCert, - }) - require.NoError(t, err, "error revoking certificate: %v", err) - require.NotNil(t, resp, "got nil response from revoke request") -} - -type pathAuthCheckerFunc func(t *testing.T, client *api.Client, path string, token string) - -func isPermDenied(err error) bool { - return err != nil && strings.Contains(err.Error(), "permission denied") -} - -func isUnsupportedPathOperation(err error) bool { - return err != nil && (strings.Contains(err.Error(), "unsupported path") || strings.Contains(err.Error(), "unsupported operation")) -} - -func isDeniedOp(err error) bool { - return isPermDenied(err) || isUnsupportedPathOperation(err) -} - -func pathShouldBeAuthed(t *testing.T, client *api.Client, path string, token string) { - client.SetToken("") - resp, err := client.Logical().ReadWithContext(ctx, path) - if err == nil || !isPermDenied(err) { - t.Fatalf("expected failure to read %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().ListWithContext(ctx, path) - if err == nil || !isPermDenied(err) { - t.Fatalf("expected failure to list %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{}) - if err == nil || !isPermDenied(err) { - t.Fatalf("expected failure to write %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().DeleteWithContext(ctx, path) - if err == nil || !isPermDenied(err) { - t.Fatalf("expected failure to delete %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{}) - if err == nil || !isPermDenied(err) { - t.Fatalf("expected failure to patch %v while unauthed: %v / %v", path, err, resp) - } -} - -func pathShouldBeUnauthedReadList(t *testing.T, client *api.Client, path string, token string) { - // Should be able to read both with and without a token. - client.SetToken("") - resp, err := client.Logical().ReadWithContext(ctx, path) - if err != nil && isPermDenied(err) { - // Read will sometimes return permission denied, when the handler - // does not support the given operation. Retry with the token. - client.SetToken(token) - resp2, err2 := client.Logical().ReadWithContext(ctx, path) - if err2 != nil && !isUnsupportedPathOperation(err2) { - t.Fatalf("unexpected failure to read %v while unauthed: %v / %v\nWhile authed: %v / %v", path, err, resp, err2, resp2) - } - client.SetToken("") - } - resp, err = client.Logical().ListWithContext(ctx, path) - if err != nil && isPermDenied(err) { - // List will sometimes return permission denied, when the handler - // does not support the given operation. Retry with the token. - client.SetToken(token) - resp2, err2 := client.Logical().ListWithContext(ctx, path) - if err2 != nil && !isUnsupportedPathOperation(err2) { - t.Fatalf("unexpected failure to list %v while unauthed: %v / %v\nWhile authed: %v / %v", path, err, resp, err2, resp2) - } - client.SetToken("") - } - - // These should all be denied. - resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{}) - if err == nil || !isDeniedOp(err) { - if !strings.Contains(path, "ocsp") || !strings.Contains(err.Error(), "Code: 40") { - t.Fatalf("unexpected failure during write on read-only path %v while unauthed: %v / %v", path, err, resp) - } - } - resp, err = client.Logical().DeleteWithContext(ctx, path) - if err == nil || !isDeniedOp(err) { - t.Fatalf("unexpected failure during delete on read-only path %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{}) - if err == nil || !isDeniedOp(err) { - t.Fatalf("unexpected failure during patch on read-only path %v while unauthed: %v / %v", path, err, resp) - } - - // Retrying with token should allow read/list, but not modification still. - client.SetToken(token) - resp, err = client.Logical().ReadWithContext(ctx, path) - if err != nil && isPermDenied(err) { - t.Fatalf("unexpected failure to read %v while authed: %v / %v", path, err, resp) - } - resp, err = client.Logical().ListWithContext(ctx, path) - if err != nil && isPermDenied(err) { - t.Fatalf("unexpected failure to list %v while authed: %v / %v", path, err, resp) - } - - // Should all be denied. - resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{}) - if err == nil || !isDeniedOp(err) { - if !strings.Contains(path, "ocsp") || !strings.Contains(err.Error(), "Code: 40") { - t.Fatalf("unexpected failure during write on read-only path %v while authed: %v / %v", path, err, resp) - } - } - resp, err = client.Logical().DeleteWithContext(ctx, path) - if err == nil || !isDeniedOp(err) { - t.Fatalf("unexpected failure during delete on read-only path %v while authed: %v / %v", path, err, resp) - } - resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{}) - if err == nil || !isDeniedOp(err) { - t.Fatalf("unexpected failure during patch on read-only path %v while authed: %v / %v", path, err, resp) - } -} - -func pathShouldBeUnauthedWriteOnly(t *testing.T, client *api.Client, path string, token string) { - client.SetToken("") - resp, err := client.Logical().WriteWithContext(ctx, path, map[string]interface{}{}) - if err != nil && isPermDenied(err) { - t.Fatalf("unexpected failure to write %v while unauthed: %v / %v", path, err, resp) - } - - // These should all be denied. However, on OSS, we might end up with - // a regular 404, which looks like err == resp == nil; hence we only - // fail when there's a non-nil response and/or a non-nil err. - resp, err = client.Logical().ReadWithContext(ctx, path) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - t.Fatalf("unexpected failure during read on write-only path %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().ListWithContext(ctx, path) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - t.Fatalf("unexpected failure during list on write-only path %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().DeleteWithContext(ctx, path) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - t.Fatalf("unexpected failure during delete on write-only path %v while unauthed: %v / %v", path, err, resp) - } - resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{}) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - t.Fatalf("unexpected failure during patch on write-only path %v while unauthed: %v / %v", path, err, resp) - } - - // Retrying with token should allow writing, but nothing else. - client.SetToken(token) - resp, err = client.Logical().WriteWithContext(ctx, path, map[string]interface{}{}) - if err != nil && isPermDenied(err) { - t.Fatalf("unexpected failure to write %v while unauthed: %v / %v", path, err, resp) - } - - // These should all be denied. - resp, err = client.Logical().ReadWithContext(ctx, path) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - t.Fatalf("unexpected failure during read on write-only path %v while authed: %v / %v", path, err, resp) - } - resp, err = client.Logical().ListWithContext(ctx, path) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - if resp != nil || err != nil { - t.Fatalf("unexpected failure during list on write-only path %v while authed: %v / %v", path, err, resp) - } - } - resp, err = client.Logical().DeleteWithContext(ctx, path) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - t.Fatalf("unexpected failure during delete on write-only path %v while authed: %v / %v", path, err, resp) - } - resp, err = client.Logical().JSONMergePatch(ctx, path, map[string]interface{}{}) - if (err == nil && resp != nil) || (err != nil && !isDeniedOp(err)) { - t.Fatalf("unexpected failure during patch on write-only path %v while authed: %v / %v", path, err, resp) - } -} - -type pathAuthChecker int - -const ( - shouldBeAuthed pathAuthChecker = iota - shouldBeUnauthedReadList - shouldBeUnauthedWriteOnly -) - -var pathAuthChckerMap = map[pathAuthChecker]pathAuthCheckerFunc{ - shouldBeAuthed: pathShouldBeAuthed, - shouldBeUnauthedReadList: pathShouldBeUnauthedReadList, - shouldBeUnauthedWriteOnly: pathShouldBeUnauthedWriteOnly, -} - -func TestProperAuthing(t *testing.T) { - t.Parallel() - ctx := context.Background() - 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 - token := client.Token() - - // Mount PKI. - err := client.Sys().MountWithContext(ctx, "pki", &api.MountInput{ - Type: "pki", - Config: api.MountConfigInput{ - DefaultLeaseTTL: "16h", - MaxLeaseTTL: "60h", - }, - }) - if err != nil { - t.Fatal(err) - } - - // Setup basic configuration. - _, err = client.Logical().WriteWithContext(ctx, "pki/root/generate/internal", map[string]interface{}{ - "ttl": "40h", - "common_name": "myvault.com", - }) - if err != nil { - t.Fatal(err) - } - - _, err = client.Logical().WriteWithContext(ctx, "pki/roles/test", map[string]interface{}{ - "allow_localhost": true, - }) - if err != nil { - t.Fatal(err) - } - - resp, err := client.Logical().WriteWithContext(ctx, "pki/issue/test", map[string]interface{}{ - "common_name": "localhost", - }) - if err != nil || resp == nil { - t.Fatal(err) - } - serial := resp.Data["serial_number"].(string) - - paths := map[string]pathAuthChecker{ - "ca_chain": shouldBeUnauthedReadList, - "cert/ca_chain": shouldBeUnauthedReadList, - "ca": shouldBeUnauthedReadList, - "ca/pem": shouldBeUnauthedReadList, - "cert/" + serial: shouldBeUnauthedReadList, - "cert/" + serial + "/raw": shouldBeUnauthedReadList, - "cert/" + serial + "/raw/pem": shouldBeUnauthedReadList, - "cert/crl": shouldBeUnauthedReadList, - "cert/crl/raw": shouldBeUnauthedReadList, - "cert/crl/raw/pem": shouldBeUnauthedReadList, - "cert/delta-crl": shouldBeUnauthedReadList, - "cert/delta-crl/raw": shouldBeUnauthedReadList, - "cert/delta-crl/raw/pem": shouldBeUnauthedReadList, - "cert/unified-crl": shouldBeUnauthedReadList, - "cert/unified-crl/raw": shouldBeUnauthedReadList, - "cert/unified-crl/raw/pem": shouldBeUnauthedReadList, - "cert/unified-delta-crl": shouldBeUnauthedReadList, - "cert/unified-delta-crl/raw": shouldBeUnauthedReadList, - "cert/unified-delta-crl/raw/pem": shouldBeUnauthedReadList, - "certs": shouldBeAuthed, - "certs/revoked": shouldBeAuthed, - "certs/revocation-queue/": shouldBeAuthed, - "certs/unified-revoked/": shouldBeAuthed, - "config/auto-tidy": shouldBeAuthed, - "config/ca": shouldBeAuthed, - "config/cluster": shouldBeAuthed, - "config/crl": shouldBeAuthed, - "config/issuers": shouldBeAuthed, - "config/keys": shouldBeAuthed, - "config/urls": shouldBeAuthed, - "crl": shouldBeUnauthedReadList, - "crl/pem": shouldBeUnauthedReadList, - "crl/delta": shouldBeUnauthedReadList, - "crl/delta/pem": shouldBeUnauthedReadList, - "crl/rotate": shouldBeAuthed, - "crl/rotate-delta": shouldBeAuthed, - "intermediate/cross-sign": shouldBeAuthed, - "intermediate/generate/exported": shouldBeAuthed, - "intermediate/generate/internal": shouldBeAuthed, - "intermediate/generate/existing": shouldBeAuthed, - "intermediate/generate/kms": shouldBeAuthed, - "intermediate/set-signed": shouldBeAuthed, - "issue/test": shouldBeAuthed, - "issuer/default": shouldBeAuthed, - "issuer/default/der": shouldBeUnauthedReadList, - "issuer/default/json": shouldBeUnauthedReadList, - "issuer/default/pem": shouldBeUnauthedReadList, - "issuer/default/crl": shouldBeUnauthedReadList, - "issuer/default/crl/pem": shouldBeUnauthedReadList, - "issuer/default/crl/der": shouldBeUnauthedReadList, - "issuer/default/crl/delta": shouldBeUnauthedReadList, - "issuer/default/crl/delta/der": shouldBeUnauthedReadList, - "issuer/default/crl/delta/pem": shouldBeUnauthedReadList, - "issuer/default/unified-crl": shouldBeUnauthedReadList, - "issuer/default/unified-crl/pem": shouldBeUnauthedReadList, - "issuer/default/unified-crl/der": shouldBeUnauthedReadList, - "issuer/default/unified-crl/delta": shouldBeUnauthedReadList, - "issuer/default/unified-crl/delta/der": shouldBeUnauthedReadList, - "issuer/default/unified-crl/delta/pem": shouldBeUnauthedReadList, - "issuer/default/issue/test": shouldBeAuthed, - "issuer/default/resign-crls": shouldBeAuthed, - "issuer/default/revoke": shouldBeAuthed, - "issuer/default/sign-intermediate": shouldBeAuthed, - "issuer/default/sign-revocation-list": shouldBeAuthed, - "issuer/default/sign-self-issued": shouldBeAuthed, - "issuer/default/sign-verbatim": shouldBeAuthed, - "issuer/default/sign-verbatim/test": shouldBeAuthed, - "issuer/default/sign/test": shouldBeAuthed, - "issuers": shouldBeUnauthedReadList, - "issuers/generate/intermediate/exported": shouldBeAuthed, - "issuers/generate/intermediate/internal": shouldBeAuthed, - "issuers/generate/intermediate/existing": shouldBeAuthed, - "issuers/generate/intermediate/kms": shouldBeAuthed, - "issuers/generate/root/exported": shouldBeAuthed, - "issuers/generate/root/internal": shouldBeAuthed, - "issuers/generate/root/existing": shouldBeAuthed, - "issuers/generate/root/kms": shouldBeAuthed, - "issuers/import/cert": shouldBeAuthed, - "issuers/import/bundle": shouldBeAuthed, - "key/default": shouldBeAuthed, - "keys": shouldBeAuthed, - "keys/generate/internal": shouldBeAuthed, - "keys/generate/exported": shouldBeAuthed, - "keys/generate/kms": shouldBeAuthed, - "keys/import": shouldBeAuthed, - "ocsp": shouldBeUnauthedWriteOnly, - "ocsp/dGVzdAo=": shouldBeUnauthedReadList, - "revoke": shouldBeAuthed, - "revoke-with-key": shouldBeAuthed, - "roles/test": shouldBeAuthed, - "roles": shouldBeAuthed, - "root": shouldBeAuthed, - "root/generate/exported": shouldBeAuthed, - "root/generate/internal": shouldBeAuthed, - "root/generate/existing": shouldBeAuthed, - "root/generate/kms": shouldBeAuthed, - "root/replace": shouldBeAuthed, - "root/rotate/internal": shouldBeAuthed, - "root/rotate/exported": shouldBeAuthed, - "root/rotate/existing": shouldBeAuthed, - "root/rotate/kms": shouldBeAuthed, - "root/sign-intermediate": shouldBeAuthed, - "root/sign-self-issued": shouldBeAuthed, - "sign-verbatim": shouldBeAuthed, - "sign-verbatim/test": shouldBeAuthed, - "sign/test": shouldBeAuthed, - "tidy": shouldBeAuthed, - "tidy-cancel": shouldBeAuthed, - "tidy-status": shouldBeAuthed, - "unified-crl": shouldBeUnauthedReadList, - "unified-crl/pem": shouldBeUnauthedReadList, - "unified-crl/delta": shouldBeUnauthedReadList, - "unified-crl/delta/pem": shouldBeUnauthedReadList, - "unified-ocsp": shouldBeUnauthedWriteOnly, - "unified-ocsp/dGVzdAo=": shouldBeUnauthedReadList, - } - for path, checkerType := range paths { - checker := pathAuthChckerMap[checkerType] - checker(t, client, "pki/"+path, token) - } - - client.SetToken(token) - openAPIResp, err := client.Logical().ReadWithContext(ctx, "sys/internal/specs/openapi") - if err != nil { - t.Fatalf("failed to get openapi data: %v", err) - } - - validatedPath := false - for openapi_path, raw_data := range openAPIResp.Data["paths"].(map[string]interface{}) { - if !strings.HasPrefix(openapi_path, "/pki/") { - t.Logf("Skipping path: %v", openapi_path) - continue - } - - t.Logf("Validating path: %v", openapi_path) - validatedPath = true - // Substitute values in from our testing map. - raw_path := openapi_path[5:] - if strings.Contains(raw_path, "roles/") && strings.Contains(raw_path, "{name}") { - raw_path = strings.ReplaceAll(raw_path, "{name}", "test") - } - if strings.Contains(raw_path, "{role}") { - raw_path = strings.ReplaceAll(raw_path, "{role}", "test") - } - if strings.Contains(raw_path, "ocsp/") && strings.Contains(raw_path, "{req}") { - raw_path = strings.ReplaceAll(raw_path, "{req}", "dGVzdAo=") - } - if strings.Contains(raw_path, "{issuer_ref}") { - raw_path = strings.ReplaceAll(raw_path, "{issuer_ref}", "default") - } - if strings.Contains(raw_path, "{key_ref}") { - raw_path = strings.ReplaceAll(raw_path, "{key_ref}", "default") - } - if strings.Contains(raw_path, "{exported}") { - raw_path = strings.ReplaceAll(raw_path, "{exported}", "internal") - } - if strings.Contains(raw_path, "{serial}") { - raw_path = strings.ReplaceAll(raw_path, "{serial}", serial) - } - - handler, present := paths[raw_path] - if !present { - t.Fatalf("OpenAPI reports PKI mount contains %v->%v but was not tested to be authed or authed.", openapi_path, raw_path) - } - - openapi_data := raw_data.(map[string]interface{}) - hasList := false - rawGetData, hasGet := openapi_data["get"] - if hasGet { - getData := rawGetData.(map[string]interface{}) - getParams, paramsPresent := getData["parameters"].(map[string]interface{}) - if getParams != nil && paramsPresent { - if _, hasList = getParams["list"]; hasList { - // LIST is exclusive from GET on the same endpoint usually. - hasGet = false - } - } - } - _, hasPost := openapi_data["post"] - _, hasDelete := openapi_data["delete"] - - if handler == shouldBeUnauthedReadList { - if hasPost || hasDelete { - t.Fatalf("Unauthed read-only endpoints should not have POST/DELETE capabilities: %v->%v", openapi_path, raw_path) - } - } else if handler == shouldBeUnauthedWriteOnly { - if hasGet || hasList { - t.Fatalf("Unauthed write-only endpoints should not have GET/LIST capabilities: %v->%v", openapi_path, raw_path) - } - } - } - - if !validatedPath { - t.Fatalf("Expected to have validated at least one path.") - } -} - -func TestPatchIssuer(t *testing.T) { - t.Parallel() - - type TestCase struct { - Field string - Before interface{} - Patched interface{} - } - testCases := []TestCase{ - { - Field: "issuer_name", - Before: "root", - Patched: "root-new", - }, - { - Field: "leaf_not_after_behavior", - Before: "err", - Patched: "permit", - }, - { - Field: "usage", - Before: "crl-signing,issuing-certificates,ocsp-signing,read-only", - Patched: "issuing-certificates,read-only", - }, - { - Field: "revocation_signature_algorithm", - Before: "ECDSAWithSHA256", - Patched: "ECDSAWithSHA384", - }, - { - Field: "issuing_certificates", - Before: []string{"http://localhost/v1/pki-1/ca"}, - Patched: []string{"http://localhost/v1/pki/ca"}, - }, - { - Field: "crl_distribution_points", - Before: []string{"http://localhost/v1/pki-1/crl"}, - Patched: []string{"http://localhost/v1/pki/crl"}, - }, - { - Field: "ocsp_servers", - Before: []string{"http://localhost/v1/pki-1/ocsp"}, - Patched: []string{"http://localhost/v1/pki/ocsp"}, - }, - { - Field: "enable_aia_url_templating", - Before: false, - Patched: true, - }, - { - Field: "manual_chain", - Before: []string(nil), - Patched: []string{"self"}, - }, - } - - for index, testCase := range testCases { - t.Logf("index: %v / tc: %v", index, testCase) - - b, s := CreateBackendWithStorage(t) - - // 1. Setup root issuer. - resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ - "common_name": "Vault Root CA", - "key_type": "ec", - "ttl": "7200h", - "issuer_name": "root", - }) - requireSuccessNonNilResponse(t, resp, err, "failed generating root issuer") - id := string(resp.Data["issuer_id"].(issuerID)) - - // 2. Enable Cluster paths - resp, err = CBWrite(b, s, "config/urls", map[string]interface{}{ - "path": "https://localhost/v1/pki", - "aia_path": "http://localhost/v1/pki", - }) - requireSuccessNonNilResponse(t, resp, err, "failed updating AIA config") - - // 3. Add AIA information - resp, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - "issuing_certificates": "http://localhost/v1/pki-1/ca", - "crl_distribution_points": "http://localhost/v1/pki-1/crl", - "ocsp_servers": "http://localhost/v1/pki-1/ocsp", - }) - requireSuccessNonNilResponse(t, resp, err, "failed setting up issuer") - - // 4. Read the issuer before. - resp, err = CBRead(b, s, "issuer/default") - requireSuccessNonNilResponse(t, resp, err, "failed reading root issuer before") - require.Equal(t, testCase.Before, resp.Data[testCase.Field], "bad expectations") - - // 5. Perform modification. - resp, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ - testCase.Field: testCase.Patched, - }) - requireSuccessNonNilResponse(t, resp, err, "failed patching root issuer") - - if testCase.Field != "manual_chain" { - require.Equal(t, testCase.Patched, resp.Data[testCase.Field], "failed persisting value") - } else { - // self->id - require.Equal(t, []string{id}, resp.Data[testCase.Field], "failed persisting value") - } - - // 6. Ensure it stuck - resp, err = CBRead(b, s, "issuer/default") - requireSuccessNonNilResponse(t, resp, err, "failed reading root issuer after") - - if testCase.Field != "manual_chain" { - require.Equal(t, testCase.Patched, resp.Data[testCase.Field]) - } else { - // self->id - require.Equal(t, []string{id}, resp.Data[testCase.Field], "failed persisting value") - } - } -} - -var ( - initTest sync.Once - rsaCAKey string - rsaCACert string - ecCAKey string - ecCACert string - edCAKey string - edCACert string -)