mirror of https://github.com/status-im/consul.git
ca: examine the full chain in newCARoot
make TestNewCARoot much more strict compare the full result instead of only a few fields. add a test case with 2 and 3 certificates in the pem
This commit is contained in:
parent
71f3ae04e2
commit
42ec34d101
|
@ -135,6 +135,7 @@ type PrimaryProvider interface {
|
|||
// the active intermediate. If multiple intermediates are needed to complete
|
||||
// the chain from the signing certificate back to the active root, they should
|
||||
// all by bundled here.
|
||||
// TODO: replace with GenerateLeafSigningCert
|
||||
GenerateIntermediate() (string, error)
|
||||
|
||||
// SignIntermediate will validate the CSR to ensure the trust domain in the
|
||||
|
@ -193,7 +194,12 @@ type SecondaryProvider interface {
|
|||
//
|
||||
// TODO: rename this struct
|
||||
type RootResult struct {
|
||||
// PEM encoded certificate that will be used as the primary CA.
|
||||
// PEM encoded bundle of CA certificates. The first certificate must be the
|
||||
// primary CA used to sign intermediates for secondary datacenters, and the
|
||||
// last certificate must be the trusted CA.
|
||||
//
|
||||
// If there is only a single certificate in the bundle then it will be used
|
||||
// as both the primary CA and the trusted CA.
|
||||
PEM string
|
||||
}
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
|
|||
if err != nil {
|
||||
return RootResult{}, err
|
||||
}
|
||||
_, err = v.client.Logical().Write(v.config.RootPKIPath+"root/generate/internal", map[string]interface{}{
|
||||
resp, err := v.client.Logical().Write(v.config.RootPKIPath+"root/generate/internal", map[string]interface{}{
|
||||
"common_name": connect.CACN("vault", uid, v.clusterID, v.isPrimary),
|
||||
"uri_sans": v.spiffeID.URI().String(),
|
||||
"key_type": v.config.PrivateKeyType,
|
||||
|
@ -288,12 +288,10 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
|
|||
if err != nil {
|
||||
return RootResult{}, err
|
||||
}
|
||||
|
||||
// retrieve the newly generated cert so that we can return it
|
||||
// TODO: is this already available from the Local().Write() above?
|
||||
rootPEM, err = v.getCA(v.config.RootPKIPath)
|
||||
if err != nil {
|
||||
return RootResult{}, err
|
||||
var ok bool
|
||||
rootPEM, ok = resp.Data["certificate"].(string)
|
||||
if !ok {
|
||||
return RootResult{}, fmt.Errorf("unexpected response from Vault: %v", resp.Data["certificate"])
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -302,7 +300,18 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return RootResult{PEM: rootPEM}, nil
|
||||
rootChain, err := v.getCAChain(v.config.RootPKIPath)
|
||||
if err != nil {
|
||||
return RootResult{}, err
|
||||
}
|
||||
|
||||
// Workaround for a bug in the Vault PKI API.
|
||||
// See https://github.com/hashicorp/vault/issues/13489
|
||||
if rootChain == "" {
|
||||
rootChain = rootPEM
|
||||
}
|
||||
|
||||
return RootResult{PEM: rootChain}, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediateCSR creates a private key and generates a CSR
|
||||
|
@ -467,6 +476,29 @@ func (v *VaultProvider) getCA(path string) (string, error) {
|
|||
return root, nil
|
||||
}
|
||||
|
||||
// TODO: refactor to remove duplication with getCA
|
||||
func (v *VaultProvider) getCAChain(path string) (string, error) {
|
||||
req := v.client.NewRequest("GET", "/v1/"+path+"/ca_chain")
|
||||
resp, err := v.client.RawRequest(req)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
return "", ErrBackendNotMounted
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
root := EnsureTrailingNewline(string(raw))
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediate mounts the configured intermediate PKI backend if
|
||||
// necessary, then generates and signs a new CA CSR using the root PKI backend
|
||||
// and updates the intermediate backend to use that new certificate.
|
||||
|
@ -571,6 +603,7 @@ func (v *VaultProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
|
|||
// CrossSignCA takes a CA certificate and cross-signs it to form a trust chain
|
||||
// back to our active root.
|
||||
func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||
// TODO: is this necessary? Doesn't vault check this for us?
|
||||
rootPEM, err := v.getCA(v.config.RootPKIPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -20,11 +20,11 @@ const (
|
|||
DefaultIntermediateCertTTL = 24 * 365 * time.Hour
|
||||
)
|
||||
|
||||
func pemEncodeKey(key []byte, blockType string) (string, error) {
|
||||
func pemEncode(value []byte, blockType string) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: key}); err != nil {
|
||||
return "", fmt.Errorf("error encoding private key: %s", err)
|
||||
if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: value}); err != nil {
|
||||
return "", fmt.Errorf("error encoding value %v: %s", blockType, err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func generateRSAKey(keyBits int) (crypto.Signer, string, error) {
|
|||
}
|
||||
|
||||
bs := x509.MarshalPKCS1PrivateKey(pk)
|
||||
pemBlock, err := pemEncodeKey(bs, "RSA PRIVATE KEY")
|
||||
pemBlock, err := pemEncode(bs, "RSA PRIVATE KEY")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func generateECDSAKey(keyBits int) (crypto.Signer, string, error) {
|
|||
return nil, "", fmt.Errorf("error marshaling ECDSA private key: %s", err)
|
||||
}
|
||||
|
||||
pemBlock, err := pemEncodeKey(bs, "EC PRIVATE KEY")
|
||||
pemBlock, err := pemEncode(bs, "EC PRIVATE KEY")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
|
@ -56,6 +56,21 @@ func ParseLeafCerts(pemValue string) (*x509.Certificate, *x509.CertPool, error)
|
|||
return leaf, intermediates, nil
|
||||
}
|
||||
|
||||
// CertSubjects can be used in debugging to return the subject of each
|
||||
// certificate in the PEM bundle. Each subject is separated by a newline.
|
||||
func CertSubjects(pem string) string {
|
||||
certs, err := parseCerts(pem)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
var buf strings.Builder
|
||||
for _, cert := range certs {
|
||||
buf.WriteString(cert.Subject.String())
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ParseCerts parses the all x509 certificates from a PEM-encoded value.
|
||||
// The first returned cert is a leaf cert and any other ones are intermediates.
|
||||
//
|
||||
|
@ -90,21 +105,10 @@ func parseCerts(pemValue string) ([]*x509.Certificate, error) {
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// CalculateCertFingerprint parses the x509 certificate from a PEM-encoded value
|
||||
// and calculates the SHA-1 fingerprint.
|
||||
func CalculateCertFingerprint(pemValue string) (string, error) {
|
||||
// The _ result below is not an error but the remaining PEM bytes.
|
||||
block, _ := pem.Decode([]byte(pemValue))
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("no PEM-encoded data found")
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return "", fmt.Errorf("first PEM-block should be CERTIFICATE type")
|
||||
}
|
||||
|
||||
hash := sha1.Sum(block.Bytes)
|
||||
return HexString(hash[:]), nil
|
||||
// CalculateCertFingerprint calculates the SHA-1 fingerprint from the cert bytes.
|
||||
func CalculateCertFingerprint(cert []byte) string {
|
||||
hash := sha1.Sum(cert)
|
||||
return HexString(hash[:])
|
||||
}
|
||||
|
||||
// ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key
|
||||
|
|
|
@ -112,10 +112,7 @@ func testCA(t testing.T, xc *structs.CARoot, keyType string, keyBits int, ttl ti
|
|||
t.Fatalf("error encoding private key: %s", err)
|
||||
}
|
||||
result.RootCert = buf.String()
|
||||
result.ID, err = CalculateCertFingerprint(result.RootCert)
|
||||
if err != nil {
|
||||
t.Fatalf("error generating CA ID fingerprint: %s", err)
|
||||
}
|
||||
result.ID = CalculateCertFingerprint(bs)
|
||||
result.SerialNumber = uint64(sn.Int64())
|
||||
result.NotBefore = template.NotBefore.UTC()
|
||||
result.NotAfter = template.NotAfter.UTC()
|
||||
|
|
|
@ -253,28 +253,24 @@ func (c *CAManager) initializeCAConfig() (*structs.CAConfiguration, error) {
|
|||
return config, nil
|
||||
}
|
||||
|
||||
// parseCARoot returns a filled-in structs.CARoot from a raw PEM value.
|
||||
func parseCARoot(pemValue, provider, clusterID string) (*structs.CARoot, error) {
|
||||
id, err := connect.CalculateCertFingerprint(pemValue)
|
||||
// newCARoot returns a filled-in structs.CARoot from a raw PEM value.
|
||||
func newCARoot(pemValue, provider, clusterID string) (*structs.CARoot, error) {
|
||||
primaryCert, err := connect.ParseCert(pemValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing root fingerprint: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
rootCert, err := connect.ParseCert(pemValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing root cert: %v", err)
|
||||
}
|
||||
keyType, keyBits, err := connect.KeyInfoFromCert(rootCert)
|
||||
keyType, keyBits, err := connect.KeyInfoFromCert(primaryCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error extracting root key info: %v", err)
|
||||
}
|
||||
return &structs.CARoot{
|
||||
ID: id,
|
||||
Name: fmt.Sprintf("%s CA Root Cert", strings.Title(provider)),
|
||||
SerialNumber: rootCert.SerialNumber.Uint64(),
|
||||
SigningKeyID: connect.EncodeSigningKeyID(rootCert.SubjectKeyId),
|
||||
ID: connect.CalculateCertFingerprint(primaryCert.Raw),
|
||||
Name: fmt.Sprintf("%s CA Primary Cert", strings.Title(provider)),
|
||||
SerialNumber: primaryCert.SerialNumber.Uint64(),
|
||||
SigningKeyID: connect.EncodeSigningKeyID(primaryCert.SubjectKeyId),
|
||||
ExternalTrustDomain: clusterID,
|
||||
NotBefore: rootCert.NotBefore,
|
||||
NotAfter: rootCert.NotAfter,
|
||||
NotBefore: primaryCert.NotBefore,
|
||||
NotAfter: primaryCert.NotAfter,
|
||||
RootCert: pemValue,
|
||||
PrivateKeyType: keyType,
|
||||
PrivateKeyBits: keyBits,
|
||||
|
@ -435,7 +431,7 @@ func (c *CAManager) secondaryInitialize(provider ca.Provider, conf *structs.CACo
|
|||
}
|
||||
var roots structs.IndexedCARoots
|
||||
if err := c.delegate.forwardDC("ConnectCA.Roots", c.serverConf.PrimaryDatacenter, &args, &roots); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to get CA roots from primary DC: %w", err)
|
||||
}
|
||||
c.secondarySetPrimaryRoots(roots)
|
||||
|
||||
|
@ -487,12 +483,12 @@ func (c *CAManager) primaryInitialize(provider ca.Provider, conf *structs.CAConf
|
|||
return fmt.Errorf("error generating CA root certificate: %v", err)
|
||||
}
|
||||
|
||||
rootCA, err := parseCARoot(root.PEM, conf.Provider, conf.ClusterID)
|
||||
rootCA, err := newCARoot(root.PEM, conf.Provider, conf.ClusterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Also create the intermediate CA, which is the one that actually signs leaf certs
|
||||
// TODO: delete this
|
||||
interPEM, err := provider.GenerateIntermediate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating intermediate cert: %v", err)
|
||||
|
@ -887,7 +883,7 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
}
|
||||
|
||||
newRootPEM := providerRoot.PEM
|
||||
newActiveRoot, err := parseCARoot(newRootPEM, args.Config.Provider, args.Config.ClusterID)
|
||||
newActiveRoot, err := newCARoot(newRootPEM, args.Config.Provider, args.Config.ClusterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -940,7 +936,7 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
// get a cross-signed certificate.
|
||||
// 3. Take the active root for the new provider and append the intermediate from step 2
|
||||
// to its list of intermediates.
|
||||
// TODO: this cert is already parsed once in parseCARoot, could we remove the second parse?
|
||||
// TODO: this cert is already parsed once in newCARoot, could we remove the second parse?
|
||||
newRoot, err := connect.ParseCert(newRootPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -980,6 +976,7 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: delete this
|
||||
intermediate, err := newProvider.GenerateIntermediate()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -618,7 +618,7 @@ func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) {
|
|||
generateExternalRootCA(t, vclient)
|
||||
|
||||
meshRootPath := "pki-root"
|
||||
primaryCert := setupPrimaryCA(t, vclient, meshRootPath)
|
||||
primaryCert := setupPrimaryCA(t, vclient, meshRootPath, "")
|
||||
|
||||
_, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.CAConfig = &structs.CAConfiguration{
|
||||
|
@ -635,7 +635,6 @@ func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) {
|
|||
},
|
||||
}
|
||||
})
|
||||
defer s1.Shutdown()
|
||||
|
||||
runStep(t, "check primary DC", func(t *testing.T) {
|
||||
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
|
||||
|
@ -704,10 +703,90 @@ func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc str
|
|||
cert := structs.IssuedCert{}
|
||||
err = msgpackrpc.CallWithCodec(codec, "ConnectCA.Sign", &req, &cert)
|
||||
require.NoError(t, err)
|
||||
|
||||
return cert.CertPEM
|
||||
}
|
||||
|
||||
func TestCAManager_Initialize_Vault_WithExternalTrustedCA(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
ca.SkipIfVaultNotPresent(t)
|
||||
|
||||
vault := ca.NewTestVaultServer(t)
|
||||
vclient := vault.Client()
|
||||
rootPEM := generateExternalRootCA(t, vclient)
|
||||
|
||||
primaryCAPath := "pki-primary"
|
||||
primaryCert := setupPrimaryCA(t, vclient, primaryCAPath, rootPEM)
|
||||
|
||||
_, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.CAConfig = &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vault.RootToken,
|
||||
"RootPKIPath": primaryCAPath,
|
||||
"IntermediatePKIPath": "pki-intermediate/",
|
||||
// TODO: there are failures to init the CA system if these are not set
|
||||
// to the values of the already initialized CA.
|
||||
"PrivateKeyType": "ec",
|
||||
"PrivateKeyBits": 256,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
runStep(t, "check primary DC", func(t *testing.T) {
|
||||
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
|
||||
|
||||
codec := rpcClient(t, s1)
|
||||
roots := structs.IndexedCARoots{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 1)
|
||||
require.Equal(t, primaryCert, roots.Roots[0].RootCert)
|
||||
|
||||
leafCertPEM := getLeafCert(t, codec, roots.TrustDomain, "dc1")
|
||||
verifyLeafCert(t, roots.Roots[0], leafCertPEM)
|
||||
})
|
||||
|
||||
// TODO: renew primary leaf signing cert
|
||||
// TODO: rotate root
|
||||
|
||||
runStep(t, "run secondary DC", func(t *testing.T) {
|
||||
_, sDC2 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "dc2"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.CAConfig = &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vault.RootToken,
|
||||
"RootPKIPath": primaryCAPath,
|
||||
"IntermediatePKIPath": "pki-secondary/",
|
||||
// TODO: there are failures to init the CA system if these are not set
|
||||
// to the values of the already initialized CA.
|
||||
"PrivateKeyType": "ec",
|
||||
"PrivateKeyBits": 256,
|
||||
},
|
||||
}
|
||||
})
|
||||
defer sDC2.Shutdown()
|
||||
joinWAN(t, sDC2, s1)
|
||||
testrpc.WaitForActiveCARoot(t, sDC2.RPC, "dc2", nil)
|
||||
|
||||
codec := rpcClient(t, sDC2)
|
||||
roots := structs.IndexedCARoots{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 1)
|
||||
|
||||
leafCertPEM := getLeafCert(t, codec, roots.TrustDomain, "dc2")
|
||||
verifyLeafCert(t, roots.Roots[0], leafCertPEM)
|
||||
|
||||
// TODO: renew secondary leaf signing cert
|
||||
})
|
||||
}
|
||||
|
||||
func generateExternalRootCA(t *testing.T, client *vaultapi.Client) string {
|
||||
t.Helper()
|
||||
err := client.Sys().Mount("corp", &vaultapi.MountInput{
|
||||
|
@ -725,10 +804,10 @@ func generateExternalRootCA(t *testing.T, client *vaultapi.Client) string {
|
|||
"ttl": "2400h",
|
||||
})
|
||||
require.NoError(t, err, "failed to generate root")
|
||||
return resp.Data["certificate"].(string)
|
||||
return ca.EnsureTrailingNewline(resp.Data["certificate"].(string))
|
||||
}
|
||||
|
||||
func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string) string {
|
||||
func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string, rootPEM string) string {
|
||||
t.Helper()
|
||||
err := client.Sys().Mount(path, &vaultapi.MountInput{
|
||||
Type: "pki",
|
||||
|
|
|
@ -15,11 +15,13 @@ import (
|
|||
msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
"github.com/hashicorp/consul/agent/connect/ca"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/agent/token"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
)
|
||||
|
@ -1246,74 +1248,122 @@ func TestConnectCA_ConfigurationSet_PersistsRoots(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestParseCARoot(t *testing.T) {
|
||||
type test struct {
|
||||
func TestNewCARoot(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
pem string
|
||||
wantSerial uint64
|
||||
wantSigningKeyID string
|
||||
wantKeyType string
|
||||
wantKeyBits int
|
||||
wantErr bool
|
||||
expected *structs.CARoot
|
||||
expectedErr string
|
||||
}
|
||||
// Test certs generated with
|
||||
// go run connect/certgen/certgen.go -out-dir /tmp/connect-certs -key-type ec -key-bits 384
|
||||
// for various key types. This does limit the exposure to formats that might
|
||||
// exist in external certificates which can be used as Connect CAs.
|
||||
// Specifically many other certs will have serial numbers that don't fit into
|
||||
// 64 bits but for reasons we truncate down to 64 bits which means our
|
||||
// `SerialNumber` will not match the one reported by openssl. We should
|
||||
// probably fix that at some point as it seems like a big footgun but it would
|
||||
// be a breaking API change to change the type to not be a JSON number and
|
||||
// JSON numbers don't even support the full range of a uint64...
|
||||
tests := []test{
|
||||
{"no cert", "", 0, "", "", 0, true},
|
||||
{
|
||||
name: "default cert",
|
||||
// Watchout for indentations they will break PEM format
|
||||
pem: readTestData(t, "cert-with-ec-256-key.pem"),
|
||||
// Based on `openssl x509 -noout -text` report from the cert
|
||||
wantSerial: 8341954965092507701,
|
||||
wantSigningKeyID: "97:4D:17:81:64:F8:B4:AF:05:E8:6C:79:C5:40:3B:0E:3E:8B:C0:AE:38:51:54:8A:2F:05:DB:E3:E8:E4:24:EC",
|
||||
wantKeyType: "ec",
|
||||
wantKeyBits: 256,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ec 384 cert",
|
||||
// Watchout for indentations they will break PEM format
|
||||
pem: readTestData(t, "cert-with-ec-384-key.pem"),
|
||||
// Based on `openssl x509 -noout -text` report from the cert
|
||||
wantSerial: 2935109425518279965,
|
||||
wantSigningKeyID: "0B:A0:88:9B:DC:95:31:51:2E:3D:D4:F9:42:D0:6A:A0:62:46:82:D2:7C:22:E7:29:A9:AA:E8:A5:8C:CF:C7:42",
|
||||
wantKeyType: "ec",
|
||||
wantKeyBits: 384,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "rsa 4096 cert",
|
||||
// Watchout for indentations they will break PEM format
|
||||
pem: readTestData(t, "cert-with-rsa-4096-key.pem"),
|
||||
// Based on `openssl x509 -noout -text` report from the cert
|
||||
wantSerial: 5186695743100577491,
|
||||
wantSigningKeyID: "92:FA:CC:97:57:1E:31:84:A2:33:DD:9B:6A:A8:7C:FC:BE:E2:94:CA:AC:B3:33:17:39:3B:B8:67:9B:DC:C1:08",
|
||||
wantKeyType: "rsa",
|
||||
wantKeyBits: 4096,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
root, err := parseCARoot(tt.pem, "consul", "cluster")
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
|
||||
run := func(t *testing.T, tc testCase) {
|
||||
root, err := newCARoot(tc.pem, "provider-name", "cluster-id")
|
||||
if tc.expectedErr != "" {
|
||||
testutil.RequireErrorContains(t, err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.wantSerial, root.SerialNumber)
|
||||
require.Equal(t, strings.ToLower(tt.wantSigningKeyID), root.SigningKeyID)
|
||||
require.Equal(t, tt.wantKeyType, root.PrivateKeyType)
|
||||
require.Equal(t, tt.wantKeyBits, root.PrivateKeyBits)
|
||||
assert.DeepEqual(t, root, tc.expected)
|
||||
}
|
||||
|
||||
// Test certs can be generated with
|
||||
// go run connect/certgen/certgen.go -out-dir /tmp/connect-certs -key-type ec -key-bits 384
|
||||
// serial generated with:
|
||||
// openssl x509 -noout -text
|
||||
testCases := []testCase{
|
||||
{
|
||||
name: "no cert",
|
||||
expectedErr: "no PEM-encoded data found",
|
||||
},
|
||||
{
|
||||
name: "type=ec bits=256",
|
||||
pem: readTestData(t, "cert-with-ec-256-key.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "c9:1b:24:e0:89:63:1a:ba:22:01:f4:cf:bc:f1:c0:36:b2:6b:6c:3d",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 8341954965092507701,
|
||||
SigningKeyID: "97:4d:17:81:64:f8:b4:af:05:e8:6c:79:c5:40:3b:0e:3e:8b:c0:ae:38:51:54:8a:2f:05:db:e3:e8:e4:24:ec",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2019, 10, 17, 11, 46, 29, 0, time.UTC),
|
||||
NotAfter: time.Date(2029, 10, 17, 11, 46, 29, 0, time.UTC),
|
||||
RootCert: readTestData(t, "cert-with-ec-256-key.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 256,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type=ec bits=384",
|
||||
pem: readTestData(t, "cert-with-ec-384-key.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "29:69:c4:0f:aa:8f:bd:07:31:0d:51:3b:45:62:3d:c0:b2:fc:c6:3f",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 2935109425518279965,
|
||||
SigningKeyID: "0b:a0:88:9b:dc:95:31:51:2e:3d:d4:f9:42:d0:6a:a0:62:46:82:d2:7c:22:e7:29:a9:aa:e8:a5:8c:cf:c7:42",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2019, 10, 17, 11, 55, 18, 0, time.UTC),
|
||||
NotAfter: time.Date(2029, 10, 17, 11, 55, 18, 0, time.UTC),
|
||||
RootCert: readTestData(t, "cert-with-ec-384-key.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 384,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type=rsa bits=4096",
|
||||
pem: readTestData(t, "cert-with-rsa-4096-key.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "3a:6a:e3:e2:2d:44:85:5a:e9:44:3b:ef:d2:90:78:83:7f:61:a2:84",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 5186695743100577491,
|
||||
SigningKeyID: "92:fa:cc:97:57:1e:31:84:a2:33:dd:9b:6a:a8:7c:fc:be:e2:94:ca:ac:b3:33:17:39:3b:b8:67:9b:dc:c1:08",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2019, 10, 17, 11, 53, 15, 0, time.UTC),
|
||||
NotAfter: time.Date(2029, 10, 17, 11, 53, 15, 0, time.UTC),
|
||||
RootCert: readTestData(t, "cert-with-rsa-4096-key.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "rsa",
|
||||
PrivateKeyBits: 4096,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two certs in pem",
|
||||
pem: readTestData(t, "pem-with-two-certs.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "42:43:10:1f:71:6b:21:21:d1:10:49:d1:f0:41:78:8c:0a:77:ef:c0",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 17692800288680335732,
|
||||
SigningKeyID: "9d:5c:27:43:ce:58:7b:ca:3e:7d:c4:fb:b6:2e:b7:13:e9:a1:68:3e",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2022, 1, 5, 23, 22, 12, 0, time.UTC),
|
||||
NotAfter: time.Date(2022, 4, 7, 15, 22, 42, 0, time.UTC),
|
||||
RootCert: readTestData(t, "pem-with-two-certs.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 256,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three certs in pem",
|
||||
pem: readTestData(t, "pem-with-three-certs.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "42:43:10:1f:71:6b:21:21:d1:10:49:d1:f0:41:78:8c:0a:77:ef:c0",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 17692800288680335732,
|
||||
SigningKeyID: "9d:5c:27:43:ce:58:7b:ca:3e:7d:c4:fb:b6:2e:b7:13:e9:a1:68:3e",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2022, 1, 5, 23, 22, 12, 0, time.UTC),
|
||||
NotAfter: time.Date(2022, 4, 7, 15, 22, 42, 0, time.UTC),
|
||||
RootCert: readTestData(t, "pem-with-three-certs.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 256,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
run(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICUjCCATqgAwIBAgIUQjOIDzaM7bGW8bU69Yl0H0C4WXQwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQwNzE1MjI0MlowFTETMBEGA1UEAxMKcHJpbWFyeSBDQTBZMBMGByqGSM49AgEG
|
||||
CCqGSM49AwEHA0IABEIcOmVSobge9pLDGh6rfyFg2+ilTFmo2ICv5vrgUfIZhi8O
|
||||
fwYz5WGb7qBPRdMw9kP8BWH/lCrn2W3Ax3x2E+2jYzBhMA4GA1UdDwEB/wQEAwIB
|
||||
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdXCdDzlh7yj59xPu2LrcT6aFo
|
||||
PjAfBgNVHSMEGDAWgBS+x+IFMFb+hCJy1OQzcdzJuwDVhDANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEAWWBBoygbjUEtoueGuC+jHAlr+VOBwiQPJLQA+xtbCvWSn8yIx/M1RyhY
|
||||
0/6WLMzhYA1lQAIze8CgKzqoGXXIcHif3PRZ3mRUMNdV/qGUv0oHZBzTKZVySOIm
|
||||
MLIoq7WvyVdVNxyvRalhHxiQA1Hrh+zQKjXhVPM6dpG0duTNYit9kJCCeNDzRjWc
|
||||
a/GgFyeeYMTheU3eBR6Vp2A8hy2h5xw82ul8YLwX0bCtcP12XAUzj3jFqwt6RLxW
|
||||
Wc7rvsLfgimEfulQwo2WLPWZw8bJdnPvNcUFX8f2Zvqy0Jg6fELnxO+AdHnAnI9J
|
||||
WtJr0ImA95Hw8gGTzmXOddYVGHuGLA==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDHzCCAgegAwIBAgIUDaEOI5nsEt9abNBbJibbQt+VZQIwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQxNTIzMjI0MlowFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAut/Gbr3MvypzEmRDTl7HGaSoVIydNEZNPqDD
|
||||
jh1lqMFywB4DujTmkWLYcPJJ0RTT2NsSakteti/e1DHCuBSU0t3Q3K1paTh8aVLx
|
||||
eK0IKNlCWqX5d1aYzCNZsRjJuQgPX6p/xcNGS+RS27jmRWPpvm6n1JfMvYRa7fF+
|
||||
HnKhGNO+hDbhkQO4s0V1U+unNhshKDhTW3mBLmAEb2OHLOEaUZtYSbqr1E9tYXgU
|
||||
DiYRkeWUpQXJ6pE91fmcaZFG0SxkqWnhe7GUa6wbb/vROWph4A1ZVHympBtOYwoJ
|
||||
eibcJjBZLrugZdix8kl8NDI7SuIM/P0x0m9WkNfhJ9vSgQXlaQIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvsfiBTBW
|
||||
/oQictTkM3HcybsA1YQwHwYDVR0jBBgwFoAUvsfiBTBW/oQictTkM3HcybsA1YQw
|
||||
DQYJKoZIhvcNAQELBQADggEBALUJWitOV4xAvfNB8Z20AQ+/hdXkWVgj1VBbd++v
|
||||
+X88q1TnueKAExU5o87MCjh9jMxalZqSVN9MUbQ4Xa+tmkjayizdpFaw6TbbaMIB
|
||||
Tgqq5ATXMnOdZd46QC764Po9R9+k9hk4dNIr5gk1ifXZDMy/7jSOVARvpwzr0cTx
|
||||
flRCTgZbcK10freoU7a74/YjEpG0wggGlR4aRWfm90Im9JM3aI55zAYQFzduf56c
|
||||
HXJDLgBtbOx/ceqVrkPdvYwP9Q34tKAMiheQ0G3tTxP3Xc87gh4UEDV02oHhcbqw
|
||||
WSm+8zTfGUlchowPRdqKE66urWTep+BA9c8zUqDdoq5lE9s=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICGTCCAZ+gAwIBAgIIJhC6ZZyZ/lQwCgYIKoZIzj0EAwMwFDESMBAGA1UEAxMJ
|
||||
VGVzdCBDQSAxMB4XDTIyMDEwNTIzNDMyNVoXDTMyMDEwNTIzNDMyNVowFDESMBAG
|
||||
A1UEAxMJVGVzdCBDQSAxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETEyAhuLLOcxy
|
||||
z2UHI7ePcB5AXL1o6mLwVfzyeaGfqUevzrFcLQ7WPiypZJW1KhOW5Q2bRgcjE8y3
|
||||
fN+B8D+KT4fPtaRLtUVX6aZ0LCROFdgWjVo2DCvCq5VQnCGjW8r0o4G9MIG6MA4G
|
||||
A1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCBU/reewmUW
|
||||
iduB8xxfW5clyUmrMewrWwtJuWPA/tFvTTArBgNVHSMEJDAigCBU/reewmUWiduB
|
||||
8xxfW5clyUmrMewrWwtJuWPA/tFvTTA/BgNVHREEODA2hjRzcGlmZmU6Ly8xMTEx
|
||||
MTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqGSM49
|
||||
BAMDA2gAMGUCMA4V/Iemelne4ZB+0glmxoKV6OPQ64oKkkrcy+vo1t1RZ+7jntRx
|
||||
mxAnY3S2m35boQIxAOARpY+qfR3U3JM+vMW9KO0/KqM+y1/uvIaOA0bQex2w8bfN
|
||||
V+QjFUDmjTT1dLpc7A==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,34 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICUjCCATqgAwIBAgIUQjOIDzaM7bGW8bU69Yl0H0C4WXQwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQwNzE1MjI0MlowFTETMBEGA1UEAxMKcHJpbWFyeSBDQTBZMBMGByqGSM49AgEG
|
||||
CCqGSM49AwEHA0IABEIcOmVSobge9pLDGh6rfyFg2+ilTFmo2ICv5vrgUfIZhi8O
|
||||
fwYz5WGb7qBPRdMw9kP8BWH/lCrn2W3Ax3x2E+2jYzBhMA4GA1UdDwEB/wQEAwIB
|
||||
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdXCdDzlh7yj59xPu2LrcT6aFo
|
||||
PjAfBgNVHSMEGDAWgBS+x+IFMFb+hCJy1OQzcdzJuwDVhDANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEAWWBBoygbjUEtoueGuC+jHAlr+VOBwiQPJLQA+xtbCvWSn8yIx/M1RyhY
|
||||
0/6WLMzhYA1lQAIze8CgKzqoGXXIcHif3PRZ3mRUMNdV/qGUv0oHZBzTKZVySOIm
|
||||
MLIoq7WvyVdVNxyvRalhHxiQA1Hrh+zQKjXhVPM6dpG0duTNYit9kJCCeNDzRjWc
|
||||
a/GgFyeeYMTheU3eBR6Vp2A8hy2h5xw82ul8YLwX0bCtcP12XAUzj3jFqwt6RLxW
|
||||
Wc7rvsLfgimEfulQwo2WLPWZw8bJdnPvNcUFX8f2Zvqy0Jg6fELnxO+AdHnAnI9J
|
||||
WtJr0ImA95Hw8gGTzmXOddYVGHuGLA==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDHzCCAgegAwIBAgIUDaEOI5nsEt9abNBbJibbQt+VZQIwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQxNTIzMjI0MlowFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAut/Gbr3MvypzEmRDTl7HGaSoVIydNEZNPqDD
|
||||
jh1lqMFywB4DujTmkWLYcPJJ0RTT2NsSakteti/e1DHCuBSU0t3Q3K1paTh8aVLx
|
||||
eK0IKNlCWqX5d1aYzCNZsRjJuQgPX6p/xcNGS+RS27jmRWPpvm6n1JfMvYRa7fF+
|
||||
HnKhGNO+hDbhkQO4s0V1U+unNhshKDhTW3mBLmAEb2OHLOEaUZtYSbqr1E9tYXgU
|
||||
DiYRkeWUpQXJ6pE91fmcaZFG0SxkqWnhe7GUa6wbb/vROWph4A1ZVHympBtOYwoJ
|
||||
eibcJjBZLrugZdix8kl8NDI7SuIM/P0x0m9WkNfhJ9vSgQXlaQIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvsfiBTBW
|
||||
/oQictTkM3HcybsA1YQwHwYDVR0jBBgwFoAUvsfiBTBW/oQictTkM3HcybsA1YQw
|
||||
DQYJKoZIhvcNAQELBQADggEBALUJWitOV4xAvfNB8Z20AQ+/hdXkWVgj1VBbd++v
|
||||
+X88q1TnueKAExU5o87MCjh9jMxalZqSVN9MUbQ4Xa+tmkjayizdpFaw6TbbaMIB
|
||||
Tgqq5ATXMnOdZd46QC764Po9R9+k9hk4dNIr5gk1ifXZDMy/7jSOVARvpwzr0cTx
|
||||
flRCTgZbcK10freoU7a74/YjEpG0wggGlR4aRWfm90Im9JM3aI55zAYQFzduf56c
|
||||
HXJDLgBtbOx/ceqVrkPdvYwP9Q34tKAMiheQ0G3tTxP3Xc87gh4UEDV02oHhcbqw
|
||||
WSm+8zTfGUlchowPRdqKE66urWTep+BA9c8zUqDdoq5lE9s=
|
||||
-----END CERTIFICATE-----
|
|
@ -66,14 +66,15 @@ func (r IndexedCARoots) Active() *CARoot {
|
|||
|
||||
// CARoot represents a root CA certificate that is trusted.
|
||||
type CARoot struct {
|
||||
// ID is a globally unique ID (UUID) representing this CA root.
|
||||
// ID is a globally unique ID (UUID) representing this CA chain. It is
|
||||
// calculated from the SHA1 of the primary CA certificate.
|
||||
ID string
|
||||
|
||||
// Name is a human-friendly name for this CA root. This value is
|
||||
// opaque to Consul and is not used for anything internally.
|
||||
Name string
|
||||
|
||||
// SerialNumber is the x509 serial number of the certificate.
|
||||
// SerialNumber is the x509 serial number of the primary CA certificate.
|
||||
SerialNumber uint64
|
||||
|
||||
// SigningKeyID is the connect.HexString encoded id of the public key that
|
||||
|
@ -96,8 +97,11 @@ type CARoot struct {
|
|||
// future flexibility.
|
||||
ExternalTrustDomain string
|
||||
|
||||
// Time validity bounds.
|
||||
// NotBefore is the x509.Certificate.NotBefore value of the primary CA
|
||||
// certificate. This value should generally be a time in the past.
|
||||
NotBefore time.Time
|
||||
// NotAfter is the x509.Certificate.NotAfter value of the primary CA
|
||||
// certificate. This is the time when the certificate will expire.
|
||||
NotAfter time.Time
|
||||
|
||||
// RootCert is the PEM-encoded public certificate for the root CA. The
|
||||
|
|
Loading…
Reference in New Issue