| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package pki |
| |
| import ( |
| "context" |
| "crypto" |
| "crypto/ecdsa" |
| "crypto/ed25519" |
| "crypto/rsa" |
| "crypto/x509" |
| "encoding/pem" |
| "fmt" |
| "testing" |
| |
| "github.com/hashicorp/vault/api" |
| vaulthttp "github.com/hashicorp/vault/http" |
| vaultocsp "github.com/hashicorp/vault/sdk/helper/ocsp" |
| "github.com/hashicorp/vault/sdk/helper/testhelpers/schema" |
| "github.com/hashicorp/vault/sdk/logical" |
| "github.com/hashicorp/vault/vault" |
| |
| "github.com/hashicorp/go-hclog" |
| "github.com/stretchr/testify/require" |
| ) |
| |
| func TestIntegration_RotateRootUsesNext(t *testing.T) { |
| t.Parallel() |
| b, s := CreateBackendWithStorage(t) |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/rotate/internal", |
| Storage: s, |
| Data: map[string]interface{}{ |
| "common_name": "test.com", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed rotate root") |
| require.NotNil(t, resp, "got nil response from rotate root") |
| require.False(t, resp.IsError(), "got an error from rotate root: %#v", resp) |
| |
| issuerId1 := resp.Data["issuer_id"].(issuerID) |
| issuerName1 := resp.Data["issuer_name"] |
| |
| require.NotEmpty(t, issuerId1, "issuer id was empty on initial rotate root command") |
| require.Equal(t, "next", issuerName1, "expected an issuer name of next on initial rotate root command") |
| |
| // Call it again, we should get a new issuer id, but since next issuer_name is used we should get a blank value. |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/rotate/internal", |
| Storage: s, |
| Data: map[string]interface{}{ |
| "common_name": "test.com", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed rotate root") |
| require.NotNil(t, resp, "got nil response from rotate root") |
| require.False(t, resp.IsError(), "got an error from rotate root: %#v", resp) |
| |
| issuerId2 := resp.Data["issuer_id"].(issuerID) |
| issuerName2 := resp.Data["issuer_name"] |
| |
| require.NotEmpty(t, issuerId2, "issuer id was empty on second rotate root command") |
| require.NotEqual(t, issuerId1, issuerId2, "should have been different issuer ids") |
| require.Empty(t, issuerName2, "expected a blank issuer name on the second rotate root command") |
| |
| // Call it again, making sure we can use our own name if desired. |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/rotate/internal", |
| Storage: s, |
| Data: map[string]interface{}{ |
| "common_name": "test.com", |
| "issuer_name": "next-cert", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed rotate root") |
| require.NotNil(t, resp, "got nil response from rotate root") |
| require.False(t, resp.IsError(), "got an error from rotate root: %#v", resp) |
| |
| issuerId3 := resp.Data["issuer_id"].(issuerID) |
| issuerName3 := resp.Data["issuer_name"] |
| |
| require.NotEmpty(t, issuerId3, "issuer id was empty on third rotate root command") |
| require.NotEqual(t, issuerId3, issuerId1, "should have been different issuer id from initial") |
| require.NotEqual(t, issuerId3, issuerId2, "should have been different issuer id from second call") |
| require.Equal(t, "next-cert", issuerName3, "expected an issuer name that we specified on third rotate root command") |
| } |
| |
| func TestIntegration_ReplaceRootNormal(t *testing.T) { |
| t.Parallel() |
| b, s := CreateBackendWithStorage(t) |
| |
| // generate roots |
| genTestRootCa(t, b, s) |
| issuerId2, _ := genTestRootCa(t, b, s) |
| |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/replace", |
| Storage: s, |
| Data: map[string]interface{}{ |
| "default": issuerId2.String(), |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed replacing root") |
| require.NotNil(t, resp, "got nil response from replacing root") |
| require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp) |
| |
| replacedIssuer := resp.Data["default"] |
| require.Equal(t, issuerId2, replacedIssuer, "expected return value to match issuer we set") |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ReadOperation, |
| Path: "config/issuers", |
| Storage: s, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed replacing root") |
| require.NotNil(t, resp, "got nil response from replacing root") |
| require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp) |
| |
| defaultIssuer := resp.Data["default"] |
| require.Equal(t, issuerId2, defaultIssuer, "expected default issuer to be updated") |
| } |
| |
| func TestIntegration_ReplaceRootDefaultsToNext(t *testing.T) { |
| t.Parallel() |
| b, s := CreateBackendWithStorage(t) |
| |
| // generate roots |
| genTestRootCa(t, b, s) |
| issuerId2, _ := genTestRootCaWithIssuerName(t, b, s, "next") |
| |
| // Do not specify the default value to replace. |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/replace", |
| Storage: s, |
| Data: map[string]interface{}{}, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed replacing root") |
| require.NotNil(t, resp, "got nil response from replacing root") |
| require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp) |
| |
| replacedIssuer := resp.Data["default"] |
| require.Equal(t, issuerId2, replacedIssuer, "expected return value to match the 'next' issuer we set") |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ReadOperation, |
| Path: "config/issuers", |
| Storage: s, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed replacing root") |
| require.NotNil(t, resp, "got nil response from replacing root") |
| require.False(t, resp.IsError(), "got an error from replacing root: %#v", resp) |
| |
| defaultIssuer := resp.Data["default"] |
| require.Equal(t, issuerId2, defaultIssuer, "expected default issuer to be updated") |
| } |
| |
| func TestIntegration_ReplaceRootBadIssuer(t *testing.T) { |
| t.Parallel() |
| b, s := CreateBackendWithStorage(t) |
| |
| // generate roots |
| genTestRootCa(t, b, s) |
| genTestRootCa(t, b, s) |
| |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/replace", |
| Storage: s, |
| Data: map[string]interface{}{ |
| "default": "a-bad-issuer-id", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed replacing root, should have been an error in the response.") |
| require.NotNil(t, resp, "got nil response from replacing root") |
| require.True(t, resp.IsError(), "did not get an error from replacing root: %#v", resp) |
| |
| // Make sure we trap replacing with default. |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/replace", |
| Storage: s, |
| Data: map[string]interface{}{ |
| "default": "default", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed replacing root, should have been an error in the response.") |
| require.NotNil(t, resp, "got nil response from replacing root") |
| require.True(t, resp.IsError(), "did not get an error from replacing root: %#v", resp) |
| |
| // Make sure we trap replacing with blank string. |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/replace", |
| Storage: s, |
| Data: map[string]interface{}{ |
| "default": "", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed replacing root, should have been an error in the response.") |
| require.NotNil(t, resp, "got nil response from replacing root") |
| require.True(t, resp.IsError(), "did not get an error from replacing root: %#v", resp) |
| } |
| |
| func TestIntegration_SetSignedWithBackwardsPemBundles(t *testing.T) { |
| t.Parallel() |
| rootBackend, rootStorage := CreateBackendWithStorage(t) |
| intBackend, intStorage := CreateBackendWithStorage(t) |
| |
| // generate root |
| resp, err := rootBackend.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "issuers/generate/root/internal", |
| Storage: rootStorage, |
| Data: map[string]interface{}{ |
| "common_name": "test.com", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed generating root ca") |
| require.NotNil(t, resp, "got nil response from generating root ca") |
| require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp) |
| rootCert := resp.Data["certificate"].(string) |
| |
| schema.ValidateResponse(t, schema.GetResponseSchema(t, rootBackend.Route("issuers/generate/root/internal"), logical.UpdateOperation), resp, true) |
| |
| // generate intermediate |
| resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "issuers/generate/intermediate/internal", |
| Storage: intStorage, |
| Data: map[string]interface{}{ |
| "common_name": "test.com", |
| }, |
| MountPoint: "pki-int/", |
| }) |
| require.NoError(t, err, "failed generating int ca") |
| require.NotNil(t, resp, "got nil response from generating int ca") |
| require.False(t, resp.IsError(), "got an error from generating int ca: %#v", resp) |
| intCsr := resp.Data["csr"].(string) |
| |
| // sign csr |
| resp, err = rootBackend.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "root/sign-intermediate", |
| Storage: rootStorage, |
| Data: map[string]interface{}{ |
| "csr": intCsr, |
| "format": "pem_bundle", |
| }, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed generating root ca") |
| require.NotNil(t, resp, "got nil response from generating root ca") |
| require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp) |
| |
| intCert := resp.Data["certificate"].(string) |
| |
| // Send in the chain backwards now and make sure we link intCert as default. |
| resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "intermediate/set-signed", |
| Storage: intStorage, |
| Data: map[string]interface{}{ |
| "certificate": rootCert + "\n" + intCert + "\n", |
| }, |
| MountPoint: "pki-int/", |
| }) |
| require.NoError(t, err, "failed generating root ca") |
| require.NotNil(t, resp, "got nil response from generating root ca") |
| require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp) |
| |
| // setup role |
| resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "roles/example", |
| Storage: intStorage, |
| Data: map[string]interface{}{ |
| "allowed_domains": "example.com", |
| "allow_subdomains": "true", |
| "max_ttl": "1h", |
| }, |
| MountPoint: "pki-int/", |
| }) |
| require.NoError(t, err, "failed setting up role example") |
| require.NotNil(t, resp, "got nil response from setting up role example: %#v", resp) |
| |
| schema.ValidateResponse(t, schema.GetResponseSchema(t, intBackend.Route("roles/example"), logical.UpdateOperation), resp, true) |
| |
| // Issue cert |
| resp, err = intBackend.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "issue/example", |
| Storage: intStorage, |
| Data: map[string]interface{}{ |
| "common_name": "test.example.com", |
| "ttl": "5m", |
| }, |
| MountPoint: "pki-int/", |
| }) |
| require.NoError(t, err, "failed issuing a leaf cert from int ca") |
| require.NotNil(t, resp, "got nil response issuing a leaf cert from int ca") |
| require.False(t, resp.IsError(), "got an error issuing a leaf cert from int ca: %#v", resp) |
| |
| schema.ValidateResponse(t, schema.GetResponseSchema(t, intBackend.Route("issue/example"), logical.UpdateOperation), resp, true) |
| } |
| |
| func TestIntegration_CSRGeneration(t *testing.T) { |
| t.Parallel() |
| b, s := CreateBackendWithStorage(t) |
| testCases := []struct { |
| keyType string |
| usePss bool |
| keyBits int |
| sigBits int |
| expectedPublicKeyType crypto.PublicKey |
| expectedSignature x509.SignatureAlgorithm |
| }{ |
| {"rsa", false, 2048, 0, &rsa.PublicKey{}, x509.SHA256WithRSA}, |
| {"rsa", false, 2048, 384, &rsa.PublicKey{}, x509.SHA384WithRSA}, |
| // Add back once https://github.com/golang/go/issues/45990 is fixed. |
| // {"rsa", true, 2048, 0, &rsa.PublicKey{}, x509.SHA256WithRSAPSS}, |
| // {"rsa", true, 2048, 512, &rsa.PublicKey{}, x509.SHA512WithRSAPSS}, |
| {"ec", false, 224, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA256}, |
| {"ec", false, 256, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA256}, |
| {"ec", false, 384, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA384}, |
| {"ec", false, 521, 0, &ecdsa.PublicKey{}, x509.ECDSAWithSHA512}, |
| {"ec", false, 521, 224, &ecdsa.PublicKey{}, x509.ECDSAWithSHA512}, // We ignore signature_bits for ec |
| {"ed25519", false, 0, 0, ed25519.PublicKey{}, x509.PureEd25519}, // We ignore both fields for ed25519 |
| } |
| for _, tc := range testCases { |
| keyTypeName := tc.keyType |
| if tc.usePss { |
| keyTypeName = tc.keyType + "-pss" |
| } |
| testName := fmt.Sprintf("%s-%d-%d", keyTypeName, tc.keyBits, tc.sigBits) |
| t.Run(testName, func(t *testing.T) { |
| resp, err := CBWrite(b, s, "intermediate/generate/internal", map[string]interface{}{ |
| "common_name": "myint.com", |
| "key_type": tc.keyType, |
| "key_bits": tc.keyBits, |
| "signature_bits": tc.sigBits, |
| "use_pss": tc.usePss, |
| }) |
| requireSuccessNonNilResponse(t, resp, err) |
| requireFieldsSetInResp(t, resp, "csr") |
| |
| csrString := resp.Data["csr"].(string) |
| pemBlock, _ := pem.Decode([]byte(csrString)) |
| require.NotNil(t, pemBlock, "failed to parse returned csr pem block") |
| csr, err := x509.ParseCertificateRequest(pemBlock.Bytes) |
| require.NoError(t, err, "failed parsing certificate request") |
| |
| require.Equal(t, tc.expectedSignature, csr.SignatureAlgorithm, |
| "Expected %s, got %s", tc.expectedSignature.String(), csr.SignatureAlgorithm.String()) |
| require.IsType(t, tc.expectedPublicKeyType, csr.PublicKey) |
| }) |
| } |
| } |
| |
| func TestIntegration_AutoIssuer(t *testing.T) { |
| t.Parallel() |
| b, s := CreateBackendWithStorage(t) |
| |
| // Generate two roots. The first should become default under the existing |
| // behavior; when we update the config and generate a second, it should |
| // take over as default. Deleting the first and re-importing it will make |
| // it default again, and then disabling the option and removing and |
| // reimporting the second and creating a new root won't affect it again. |
| resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ |
| "common_name": "Root X1", |
| "issuer_name": "root-1", |
| "key_type": "ec", |
| }) |
| |
| requireSuccessNonNilResponse(t, resp, err) |
| issuerIdOne := resp.Data["issuer_id"] |
| require.NotEmpty(t, issuerIdOne) |
| certOne := resp.Data["certificate"] |
| require.NotEmpty(t, certOne) |
| |
| resp, err = CBRead(b, s, "config/issuers") |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, issuerIdOne, resp.Data["default"]) |
| |
| schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.ReadOperation), resp, true) |
| |
| // Enable the new config option. |
| resp, err = CBWrite(b, s, "config/issuers", map[string]interface{}{ |
| "default": issuerIdOne, |
| "default_follows_latest_issuer": true, |
| }) |
| require.NoError(t, err) |
| schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.UpdateOperation), resp, true) |
| |
| // Now generate the second root; it should become default. |
| resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ |
| "common_name": "Root X2", |
| "issuer_name": "root-2", |
| "key_type": "ec", |
| }) |
| requireSuccessNonNilResponse(t, resp, err) |
| issuerIdTwo := resp.Data["issuer_id"] |
| require.NotEmpty(t, issuerIdTwo) |
| certTwo := resp.Data["certificate"] |
| require.NotEmpty(t, certTwo) |
| |
| resp, err = CBRead(b, s, "config/issuers") |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, issuerIdTwo, resp.Data["default"]) |
| |
| // Deleting the first shouldn't affect the default issuer. |
| _, err = CBDelete(b, s, "issuer/root-1") |
| require.NoError(t, err) |
| resp, err = CBRead(b, s, "config/issuers") |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, issuerIdTwo, resp.Data["default"]) |
| |
| // But reimporting it should update it to the new issuer's value. |
| resp, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ |
| "pem_bundle": certOne, |
| }) |
| requireSuccessNonNilResponse(t, resp, err) |
| issuerIdOneReimported := issuerID(resp.Data["imported_issuers"].([]string)[0]) |
| |
| resp, err = CBRead(b, s, "config/issuers") |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, issuerIdOneReimported, resp.Data["default"]) |
| |
| // Now update the config to disable this option again. |
| _, err = CBWrite(b, s, "config/issuers", map[string]interface{}{ |
| "default": issuerIdOneReimported, |
| "default_follows_latest_issuer": false, |
| }) |
| require.NoError(t, err) |
| |
| // Generating a new root shouldn't update the default. |
| resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ |
| "common_name": "Root X3", |
| "issuer_name": "root-3", |
| "key_type": "ec", |
| }) |
| requireSuccessNonNilResponse(t, resp, err) |
| issuerIdThree := resp.Data["issuer_id"] |
| require.NotEmpty(t, issuerIdThree) |
| |
| resp, err = CBRead(b, s, "config/issuers") |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, issuerIdOneReimported, resp.Data["default"]) |
| |
| // Deleting and re-importing root 2 should also not affect it. |
| _, err = CBDelete(b, s, "issuer/root-2") |
| require.NoError(t, err) |
| resp, err = CBRead(b, s, "config/issuers") |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, issuerIdOneReimported, resp.Data["default"]) |
| |
| resp, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ |
| "pem_bundle": certTwo, |
| }) |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, 1, len(resp.Data["imported_issuers"].([]string))) |
| resp, err = CBRead(b, s, "config/issuers") |
| requireSuccessNonNilResponse(t, resp, err) |
| require.Equal(t, issuerIdOneReimported, resp.Data["default"]) |
| } |
| |
| func TestIntegrationOCSPClientWithPKI(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() |
| cores := cluster.Cores |
| vault.TestWaitActive(t, cores[0].Core) |
| client := cores[0].Client |
| |
| err := client.Sys().Mount("pki", &api.MountInput{ |
| Type: "pki", |
| Config: api.MountConfigInput{ |
| DefaultLeaseTTL: "16h", |
| MaxLeaseTTL: "32h", |
| }, |
| }) |
| require.NoError(t, err) |
| |
| resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{ |
| "ttl": "40h", |
| "common_name": "Root R1", |
| "key_type": "ec", |
| }) |
| require.NoError(t, err) |
| require.NotNil(t, resp) |
| require.NotNil(t, resp.Data) |
| require.NotEmpty(t, resp.Data["issuer_id"]) |
| rootIssuerId := resp.Data["issuer_id"].(string) |
| |
| // Set URLs pointing to the issuer. |
| _, err = client.Logical().Write("pki/config/cluster", map[string]interface{}{ |
| "path": client.Address() + "/v1/pki", |
| "aia_path": client.Address() + "/v1/pki", |
| }) |
| require.NoError(t, err) |
| |
| _, err = client.Logical().Write("pki/config/urls", map[string]interface{}{ |
| "enable_templating": true, |
| "crl_distribution_points": "{{cluster_aia_path}}/issuer/{{issuer_id}}/crl/der", |
| "issuing_certificates": "{{cluster_aia_path}}/issuer/{{issuer_id}}/der", |
| "ocsp_servers": "{{cluster_aia_path}}/ocsp", |
| }) |
| require.NoError(t, err) |
| |
| // Build an intermediate CA |
| resp, err = client.Logical().Write("pki/intermediate/generate/internal", map[string]interface{}{ |
| "common_name": "Int X1", |
| "key_type": "ec", |
| }) |
| require.NoError(t, err) |
| require.NotNil(t, resp) |
| require.NotNil(t, resp.Data) |
| require.NotEmpty(t, resp.Data["csr"]) |
| intermediateCSR := resp.Data["csr"].(string) |
| |
| resp, err = client.Logical().Write("pki/root/sign-intermediate", map[string]interface{}{ |
| "csr": intermediateCSR, |
| "ttl": "20h", |
| }) |
| require.NoError(t, err) |
| require.NotNil(t, resp) |
| require.NotNil(t, resp.Data) |
| require.NotEmpty(t, resp.Data["certificate"]) |
| intermediateCert := resp.Data["certificate"] |
| |
| resp, err = client.Logical().Write("pki/intermediate/set-signed", map[string]interface{}{ |
| "certificate": intermediateCert, |
| }) |
| require.NoError(t, err) |
| require.NotNil(t, resp) |
| require.NotNil(t, resp.Data) |
| require.NotEmpty(t, resp.Data["imported_issuers"]) |
| rawImportedIssuers := resp.Data["imported_issuers"].([]interface{}) |
| require.Equal(t, len(rawImportedIssuers), 1) |
| importedIssuer := rawImportedIssuers[0].(string) |
| require.NotEmpty(t, importedIssuer) |
| |
| // Set intermediate as default. |
| _, err = client.Logical().Write("pki/config/issuers", map[string]interface{}{ |
| "default": importedIssuer, |
| }) |
| require.NoError(t, err) |
| |
| // Setup roles for root, intermediate. |
| _, err = client.Logical().Write("pki/roles/example-root", map[string]interface{}{ |
| "allowed_domains": "example.com", |
| "allow_subdomains": "true", |
| "max_ttl": "1h", |
| "key_type": "ec", |
| "issuer_ref": rootIssuerId, |
| }) |
| require.NoError(t, err) |
| |
| _, err = client.Logical().Write("pki/roles/example-int", map[string]interface{}{ |
| "allowed_domains": "example.com", |
| "allow_subdomains": "true", |
| "max_ttl": "1h", |
| "key_type": "ec", |
| }) |
| require.NoError(t, err) |
| |
| // Issue certs and validate them against OCSP. |
| for _, path := range []string{"pki/issue/example-int", "pki/issue/example-root"} { |
| t.Logf("Validating against path: %v", path) |
| resp, err = client.Logical().Write(path, map[string]interface{}{ |
| "common_name": "test.example.com", |
| "ttl": "5m", |
| }) |
| require.NoError(t, err) |
| require.NotNil(t, resp) |
| require.NotNil(t, resp.Data) |
| require.NotEmpty(t, resp.Data["certificate"]) |
| require.NotEmpty(t, resp.Data["issuing_ca"]) |
| require.NotEmpty(t, resp.Data["serial_number"]) |
| |
| certPEM := resp.Data["certificate"].(string) |
| certBlock, _ := pem.Decode([]byte(certPEM)) |
| require.NotNil(t, certBlock) |
| cert, err := x509.ParseCertificate(certBlock.Bytes) |
| require.NoError(t, err) |
| require.NotNil(t, cert) |
| |
| issuerPEM := resp.Data["issuing_ca"].(string) |
| issuerBlock, _ := pem.Decode([]byte(issuerPEM)) |
| require.NotNil(t, issuerBlock) |
| issuer, err := x509.ParseCertificate(issuerBlock.Bytes) |
| require.NoError(t, err) |
| require.NotNil(t, issuer) |
| |
| serialNumber := resp.Data["serial_number"].(string) |
| |
| testLogger := hclog.New(hclog.DefaultOptions) |
| |
| conf := &vaultocsp.VerifyConfig{ |
| OcspFailureMode: vaultocsp.FailOpenFalse, |
| ExtraCas: []*x509.Certificate{cluster.CACert}, |
| } |
| ocspClient := vaultocsp.New(func() hclog.Logger { |
| return testLogger |
| }, 10) |
| |
| err = ocspClient.VerifyLeafCertificate(context.Background(), cert, issuer, conf) |
| require.NoError(t, err) |
| |
| _, err = client.Logical().Write("pki/revoke", map[string]interface{}{ |
| "serial_number": serialNumber, |
| }) |
| require.NoError(t, err) |
| |
| err = ocspClient.VerifyLeafCertificate(context.Background(), cert, issuer, conf) |
| require.Error(t, err) |
| } |
| } |
| |
| func genTestRootCa(t *testing.T, b *backend, s logical.Storage) (issuerID, keyID) { |
| return genTestRootCaWithIssuerName(t, b, s, "") |
| } |
| |
| func genTestRootCaWithIssuerName(t *testing.T, b *backend, s logical.Storage, issuerName string) (issuerID, keyID) { |
| data := map[string]interface{}{ |
| "common_name": "test.com", |
| } |
| if len(issuerName) > 0 { |
| data["issuer_name"] = issuerName |
| } |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "issuers/generate/root/internal", |
| Storage: s, |
| Data: data, |
| MountPoint: "pki/", |
| }) |
| require.NoError(t, err, "failed generating root ca") |
| require.NotNil(t, resp, "got nil response from generating root ca") |
| require.False(t, resp.IsError(), "got an error from generating root ca: %#v", resp) |
| |
| issuerId := resp.Data["issuer_id"].(issuerID) |
| keyId := resp.Data["key_id"].(keyID) |
| |
| require.NotEmpty(t, issuerId, "returned issuer id was empty") |
| require.NotEmpty(t, keyId, "returned key id was empty") |
| |
| return issuerId, keyId |
| } |