2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-05-31 19:00:20 +00:00
|
|
|
package pki
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-10-03 16:39:54 +00:00
|
|
|
"crypto"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/pem"
|
|
|
|
"fmt"
|
2022-05-31 19:00:20 +00:00
|
|
|
"testing"
|
|
|
|
|
2023-04-20 14:57:20 +00:00
|
|
|
"github.com/hashicorp/vault/api"
|
|
|
|
vaulthttp "github.com/hashicorp/vault/http"
|
|
|
|
vaultocsp "github.com/hashicorp/vault/sdk/helper/ocsp"
|
2023-02-15 22:51:27 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
|
2022-05-31 19:00:20 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2023-04-20 14:57:20 +00:00
|
|
|
"github.com/hashicorp/vault/vault"
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-hclog"
|
2022-05-31 19:00:20 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestIntegration_RotateRootUsesNext(t *testing.T) {
|
2022-08-01 20:43:38 +00:00
|
|
|
t.Parallel()
|
2022-11-14 23:26:26 +00:00
|
|
|
b, s := CreateBackendWithStorage(t)
|
2022-05-31 19:00:20 +00:00
|
|
|
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) {
|
2022-08-01 20:43:38 +00:00
|
|
|
t.Parallel()
|
2022-11-14 23:26:26 +00:00
|
|
|
b, s := CreateBackendWithStorage(t)
|
2022-05-31 19:00:20 +00:00
|
|
|
|
|
|
|
// 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) {
|
2022-08-01 20:43:38 +00:00
|
|
|
t.Parallel()
|
2022-11-14 23:26:26 +00:00
|
|
|
b, s := CreateBackendWithStorage(t)
|
2022-05-31 19:00:20 +00:00
|
|
|
|
|
|
|
// 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) {
|
2022-08-01 20:43:38 +00:00
|
|
|
t.Parallel()
|
2022-11-14 23:26:26 +00:00
|
|
|
b, s := CreateBackendWithStorage(t)
|
2022-05-31 19:00:20 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2022-06-02 16:59:07 +00:00
|
|
|
func TestIntegration_SetSignedWithBackwardsPemBundles(t *testing.T) {
|
2022-08-01 20:43:38 +00:00
|
|
|
t.Parallel()
|
2022-11-14 23:26:26 +00:00
|
|
|
rootBackend, rootStorage := CreateBackendWithStorage(t)
|
|
|
|
intBackend, intStorage := CreateBackendWithStorage(t)
|
2022-06-02 16:59:07 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
// 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")
|
2022-12-05 15:40:39 +00:00
|
|
|
require.NotNil(t, resp, "got nil response from setting up role example: %#v", resp)
|
2022-06-02 16:59:07 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2022-10-03 16:39:54 +00:00
|
|
|
func TestIntegration_CSRGeneration(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-11-14 23:26:26 +00:00
|
|
|
b, s := CreateBackendWithStorage(t)
|
2022-10-03 16:39:54 +00:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-08 19:40:29 +00:00
|
|
|
func TestIntegration_AutoIssuer(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-11-14 23:26:26 +00:00
|
|
|
b, s := CreateBackendWithStorage(t)
|
2022-11-08 19:40:29 +00:00
|
|
|
|
|
|
|
// 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",
|
|
|
|
})
|
2023-02-15 22:51:27 +00:00
|
|
|
|
2022-11-08 19:40:29 +00:00
|
|
|
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"])
|
|
|
|
|
2023-02-15 22:51:27 +00:00
|
|
|
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.ReadOperation), resp, true)
|
|
|
|
|
2022-11-08 19:40:29 +00:00
|
|
|
// Enable the new config option.
|
2023-02-15 22:51:27 +00:00
|
|
|
resp, err = CBWrite(b, s, "config/issuers", map[string]interface{}{
|
2022-11-08 19:40:29 +00:00
|
|
|
"default": issuerIdOne,
|
|
|
|
"default_follows_latest_issuer": true,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
2023-02-15 22:51:27 +00:00
|
|
|
schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.UpdateOperation), resp, true)
|
2022-11-08 19:40:29 +00:00
|
|
|
|
|
|
|
// 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"])
|
|
|
|
}
|
|
|
|
|
2023-04-20 14:57:20 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-31 19:00:20 +00:00
|
|
|
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
|
|
|
|
}
|