mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 11:38:02 +00:00
Compare issuer certificates using cert, signature algo and signature fields (#15285)
* Move existing test helpers into a new test_helpers.go file within PKI * Compare issuer certificates by cert, signature algo and signature - Instead of comparing the strings of a certificate, instead leverage the Go Raw attribute within a parsed certificate to compare. The Raw attribute is a byte array of an ASN.1 DER containing the cert, signature algo and signature. - Rework a bit of the importIssuers function as well to fail checks on the inbound issuer earlier as well as load keys/issuers just before we need them
This commit is contained in:
@@ -9,16 +9,12 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
@@ -2780,23 +2776,6 @@ func TestBackend_SignSelfIssued_DifferentTypes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func getSelfSigned(t *testing.T, subject, issuer *x509.Certificate, key *rsa.PrivateKey) (string, *x509.Certificate) {
|
||||
t.Helper()
|
||||
selfSigned, err := x509.CreateCertificate(rand.Reader, subject, issuer, key.Public(), key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(selfSigned)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pemSS := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: selfSigned,
|
||||
})))
|
||||
return pemSS, cert
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -4028,70 +4007,6 @@ func TestBackend_RevokePlusTidy_Intermediate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func getParsedCrl(t *testing.T, client *api.Client, mountPoint string) *pkix.CertificateList {
|
||||
path := fmt.Sprintf("/v1/%s/crl", mountPoint)
|
||||
return getParsedCrlAtPath(t, client, path)
|
||||
}
|
||||
|
||||
func getParsedCrlForIssuer(t *testing.T, client *api.Client, mountPoint string, issuer string) *pkix.CertificateList {
|
||||
path := fmt.Sprintf("/v1/%v/issuer/%v/crl/der", mountPoint, issuer)
|
||||
crl := getParsedCrlAtPath(t, client, path)
|
||||
|
||||
// Now fetch the issuer as well and verify the certificate
|
||||
path = fmt.Sprintf("/v1/%v/issuer/%v/der", mountPoint, issuer)
|
||||
req := client.NewRequest("GET", path)
|
||||
resp, err := client.RawRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
certBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(certBytes) == 0 {
|
||||
t.Fatalf("expected certificate in response body")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cert == nil {
|
||||
t.Fatalf("expected parsed certificate")
|
||||
}
|
||||
|
||||
if err := cert.CheckCRLSignature(crl); err != nil {
|
||||
t.Fatalf("expected valid signature on CRL for issuer %v: %v", issuer, crl)
|
||||
}
|
||||
|
||||
return crl
|
||||
}
|
||||
|
||||
func getParsedCrlAtPath(t *testing.T, client *api.Client, path string) *pkix.CertificateList {
|
||||
req := client.NewRequest("GET", path)
|
||||
resp, err := client.RawRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
crlBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(crlBytes) == 0 {
|
||||
t.Fatalf("expected CRL in response body")
|
||||
}
|
||||
|
||||
crl, err := x509.ParseDERCRL(crlBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return crl
|
||||
}
|
||||
|
||||
func TestBackend_Root_FullCAChain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testName string
|
||||
@@ -5074,90 +4989,3 @@ var (
|
||||
edCAKey string
|
||||
edCACert string
|
||||
)
|
||||
|
||||
func mountPKIEndpoint(t *testing.T, client *api.Client, path string) {
|
||||
var err error
|
||||
err = client.Sys().Mount(path, &api.MountInput{
|
||||
Type: "pki",
|
||||
Config: api.MountConfigInput{
|
||||
DefaultLeaseTTL: "16h",
|
||||
MaxLeaseTTL: "32h",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed mounting pki endpoint")
|
||||
}
|
||||
|
||||
func requireSignedBy(t *testing.T, cert *x509.Certificate, key crypto.PublicKey) {
|
||||
switch key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
requireRSASignedBy(t, cert, key.(*rsa.PublicKey))
|
||||
case *ecdsa.PublicKey:
|
||||
requireECDSASignedBy(t, cert, key.(*ecdsa.PublicKey))
|
||||
case ed25519.PublicKey:
|
||||
requireED25519SignedBy(t, cert, key.(ed25519.PublicKey))
|
||||
default:
|
||||
require.Fail(t, "unknown public key type %#v", key)
|
||||
}
|
||||
}
|
||||
|
||||
func requireRSASignedBy(t *testing.T, cert *x509.Certificate, key *rsa.PublicKey) {
|
||||
require.Contains(t, []x509.SignatureAlgorithm{x509.SHA256WithRSA, x509.SHA512WithRSA},
|
||||
cert.SignatureAlgorithm, "only sha256 signatures supported")
|
||||
|
||||
var hasher hash.Hash
|
||||
var hashAlgo crypto.Hash
|
||||
|
||||
switch cert.SignatureAlgorithm {
|
||||
case x509.SHA256WithRSA:
|
||||
hasher = sha256.New()
|
||||
hashAlgo = crypto.SHA256
|
||||
case x509.SHA512WithRSA:
|
||||
hasher = sha512.New()
|
||||
hashAlgo = crypto.SHA512
|
||||
}
|
||||
|
||||
hasher.Write(cert.RawTBSCertificate)
|
||||
hashData := hasher.Sum(nil)
|
||||
|
||||
err := rsa.VerifyPKCS1v15(key, hashAlgo, hashData, cert.Signature)
|
||||
require.NoError(t, err, "the certificate was not signed by the expected public rsa key.")
|
||||
}
|
||||
|
||||
func requireECDSASignedBy(t *testing.T, cert *x509.Certificate, key *ecdsa.PublicKey) {
|
||||
require.Contains(t, []x509.SignatureAlgorithm{x509.ECDSAWithSHA256, x509.ECDSAWithSHA512},
|
||||
cert.SignatureAlgorithm, "only ecdsa signatures supported")
|
||||
|
||||
var hasher hash.Hash
|
||||
switch cert.SignatureAlgorithm {
|
||||
case x509.ECDSAWithSHA256:
|
||||
hasher = sha256.New()
|
||||
case x509.ECDSAWithSHA512:
|
||||
hasher = sha512.New()
|
||||
}
|
||||
|
||||
hasher.Write(cert.RawTBSCertificate)
|
||||
hashData := hasher.Sum(nil)
|
||||
|
||||
verify := ecdsa.VerifyASN1(key, hashData, cert.Signature)
|
||||
require.True(t, verify, "the certificate was not signed by the expected public ecdsa key.")
|
||||
}
|
||||
|
||||
func requireED25519SignedBy(t *testing.T, cert *x509.Certificate, key ed25519.PublicKey) {
|
||||
require.Equal(t, x509.PureEd25519, cert.SignatureAlgorithm)
|
||||
ed25519.Verify(key, cert.RawTBSCertificate, cert.Signature)
|
||||
}
|
||||
|
||||
func parseCert(t *testing.T, pemCert string) *x509.Certificate {
|
||||
block, _ := pem.Decode([]byte(pemCert))
|
||||
require.NotNil(t, block, "failed to decode PEM block")
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
return cert
|
||||
}
|
||||
|
||||
func requireMatchingPublicKeys(t *testing.T, cert *x509.Certificate, key crypto.PublicKey) {
|
||||
certPubKey := cert.PublicKey
|
||||
require.True(t, reflect.DeepEqual(certPubKey, key),
|
||||
"public keys mismatched: got: %v, expected: %v", certPubKey, key)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -1550,3 +1551,15 @@ func stringToOid(in string) (asn1.ObjectIdentifier, error) {
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseCertificateFromBytes(certBytes []byte) (*x509.Certificate, error) {
|
||||
block, extra := pem.Decode(certBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("unable to parse certificate: invalid PEM")
|
||||
}
|
||||
if len(strings.TrimSpace(string(extra))) > 0 {
|
||||
return nil, errors.New("unable to parse certificate: trailing PEM data")
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package pki
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -66,17 +64,21 @@ func TestBackend_CRL_EnableDisable(t *testing.T) {
|
||||
serials[i] = resp.Data["serial_number"].(string)
|
||||
}
|
||||
|
||||
test := func(num int) {
|
||||
certList := getCrlCertificateList(t, client)
|
||||
test := func(numRevokedExpected int, expectedSerials ...string) {
|
||||
certList := getCrlCertificateList(t, client, "pki")
|
||||
lenList := len(certList.RevokedCertificates)
|
||||
if lenList != num {
|
||||
t.Fatalf("expected %d revoked certificates, found %d", num, lenList)
|
||||
if lenList != numRevokedExpected {
|
||||
t.Fatalf("expected %d revoked certificates, found %d", numRevokedExpected, lenList)
|
||||
}
|
||||
|
||||
for _, serialNum := range expectedSerials {
|
||||
requireSerialNumberInCRL(t, certList, serialNum)
|
||||
}
|
||||
}
|
||||
|
||||
revoke := func(num int) {
|
||||
revoke := func(serialIndex int) {
|
||||
resp, err = client.Logical().Write("pki/revoke", map[string]interface{}{
|
||||
"serial_number": serials[num],
|
||||
"serial_number": serials[serialIndex],
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -102,14 +104,14 @@ func TestBackend_CRL_EnableDisable(t *testing.T) {
|
||||
test(0)
|
||||
revoke(0)
|
||||
revoke(1)
|
||||
test(2)
|
||||
test(2, serials[0], serials[1])
|
||||
toggle(true)
|
||||
test(0)
|
||||
revoke(2)
|
||||
revoke(3)
|
||||
test(0)
|
||||
toggle(false)
|
||||
test(4)
|
||||
test(4, serials[0], serials[1], serials[2], serials[3])
|
||||
revoke(4)
|
||||
revoke(5)
|
||||
test(6)
|
||||
@@ -119,12 +121,12 @@ func TestBackend_CRL_EnableDisable(t *testing.T) {
|
||||
test(6)
|
||||
|
||||
// The rotate command should reset the update time of the CRL.
|
||||
crlCreationTime1 := getCrlCertificateList(t, client).ThisUpdate
|
||||
crlCreationTime1 := getCrlCertificateList(t, client, "pki").ThisUpdate
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err = client.Logical().Read("pki/crl/rotate")
|
||||
require.NoError(t, err)
|
||||
|
||||
crlCreationTime2 := getCrlCertificateList(t, client).ThisUpdate
|
||||
crlCreationTime2 := getCrlCertificateList(t, client, "pki").ThisUpdate
|
||||
require.NotEqual(t, crlCreationTime1, crlCreationTime2)
|
||||
}
|
||||
|
||||
@@ -164,22 +166,3 @@ func requestCrlFromBackend(t *testing.T, s logical.Storage, b *backend) *logical
|
||||
require.False(t, resp.IsError(), "crl error response: %v", resp)
|
||||
return resp
|
||||
}
|
||||
|
||||
func getCrlCertificateList(t *testing.T, client *api.Client) pkix.TBSCertificateList {
|
||||
resp, err := client.Logical().ReadWithContext(context.Background(), "pki/cert/crl")
|
||||
require.NoError(t, err, "crl req failed with an error")
|
||||
require.NotNil(t, resp, "crl response was nil with no error")
|
||||
|
||||
crlPem := resp.Data["certificate"].(string)
|
||||
return parseCrlPemString(t, crlPem)
|
||||
}
|
||||
|
||||
func parseCrlPemString(t *testing.T, crlPem string) pkix.TBSCertificateList {
|
||||
return parseCrlPemBytes(t, []byte(crlPem))
|
||||
}
|
||||
|
||||
func parseCrlPemBytes(t *testing.T, crlPem []byte) pkix.TBSCertificateList {
|
||||
certList, err := x509.ParseCRL(crlPem)
|
||||
require.NoError(t, err)
|
||||
return certList.TBSCertList
|
||||
}
|
||||
|
||||
@@ -10,21 +10,6 @@ import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
||||
config := logical.TestBackendConfig()
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
|
||||
var err error
|
||||
b := Backend(config)
|
||||
err = b.Setup(context.Background(), config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Assume for our tests we have performed the migration already.
|
||||
b.pkiStorageVersion.Store(1)
|
||||
return b, config.StorageView
|
||||
}
|
||||
|
||||
func TestPki_RoleGenerateLease(t *testing.T) {
|
||||
var resp *logical.Response
|
||||
var err error
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package pki
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -349,15 +349,12 @@ func importKey(mkc managedKeyContext, s logical.Storage, keyValue string, keyNam
|
||||
}
|
||||
|
||||
func (i issuerEntry) GetCertificate() (*x509.Certificate, error) {
|
||||
block, extra := pem.Decode([]byte(i.Certificate))
|
||||
if block == nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse certificate from issuer: invalid PEM: %v", i.ID)}
|
||||
}
|
||||
if len(strings.TrimSpace(string(extra))) > 0 {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse certificate for issuer (%v): trailing PEM data: %v", i.ID, string(extra))}
|
||||
cert, err := parseCertificateFromBytes([]byte(i.Certificate))
|
||||
if err != nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse certificate from issuer: %s: %v", err.Error(), i.ID)}
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (i issuerEntry) EnsureUsage(usage issuerUsage) error {
|
||||
@@ -513,19 +510,23 @@ func importIssuer(ctx managedKeyContext, s logical.Storage, certValue string, is
|
||||
// Discussed further in #11960 and RFC 7468.
|
||||
certValue = strings.TrimSpace(certValue) + "\n"
|
||||
|
||||
// Before we can import a known issuer, we first need to know if the issuer
|
||||
// exists in storage already. This means iterating through all known
|
||||
// issuers and comparing their private value against this value.
|
||||
knownIssuers, err := listIssuers(ctx.ctx, s)
|
||||
// Extracting the certificate is necessary for two reasons: first, it lets
|
||||
// us fetch the serial number; second, for the public key comparison with
|
||||
// known keys.
|
||||
issuerCert, err := parseCertificateFromBytes([]byte(certValue))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Before we return below, we need to iterate over _all_ keys and see if
|
||||
// one of them a public key matching this certificate, and if so, update our
|
||||
// link accordingly. We fetch the list of keys up front, even may not need
|
||||
// it, to give ourselves a better chance of succeeding below.
|
||||
knownKeys, err := listKeys(ctx.ctx, s)
|
||||
// Ensure this certificate is a usable as a CA certificate.
|
||||
if !issuerCert.BasicConstraintsValid || !issuerCert.IsCA {
|
||||
return nil, false, errutil.UserError{Err: "Refusing to import non-CA certificate"}
|
||||
}
|
||||
|
||||
// Before we can import a known issuer, we first need to know if the issuer
|
||||
// exists in storage already. This means iterating through all known
|
||||
// issuers and comparing their private value against this value.
|
||||
knownIssuers, err := listIssuers(ctx.ctx, s)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -535,8 +536,11 @@ func importIssuer(ctx managedKeyContext, s logical.Storage, certValue string, is
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if existingIssuer.Certificate == certValue {
|
||||
existingIssuerCert, err := existingIssuer.GetCertificate()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if areCertificatesEqual(existingIssuerCert, issuerCert) {
|
||||
// Here, we don't need to stitch together the key entries,
|
||||
// because the last run should've done that for us (or, when
|
||||
// importing a key).
|
||||
@@ -559,21 +563,17 @@ func importIssuer(ctx managedKeyContext, s logical.Storage, certValue string, is
|
||||
return nil, false, fmt.Errorf("bad issuer: potentially multiple PEM blobs in one certificate storage entry:\n%v", result.Certificate)
|
||||
}
|
||||
|
||||
// Extracting the certificate is necessary for two reasons: first, it lets
|
||||
// us fetch the serial number; second, for the public key comparison with
|
||||
// known keys.
|
||||
issuerCert, err := result.GetCertificate()
|
||||
result.SerialNumber = strings.TrimSpace(certutil.GetHexFormatted(issuerCert.SerialNumber.Bytes(), ":"))
|
||||
|
||||
// Before we return below, we need to iterate over _all_ keys and see if
|
||||
// one of them a public key matching this certificate, and if so, update our
|
||||
// link accordingly. We fetch the list of keys up front, even may not need
|
||||
// it, to give ourselves a better chance of succeeding below.
|
||||
knownKeys, err := listKeys(ctx.ctx, s)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Ensure this certificate is a usable as a CA certificate.
|
||||
if !issuerCert.BasicConstraintsValid || !issuerCert.IsCA {
|
||||
return nil, false, errutil.UserError{Err: "Refusing to import non-CA certificate"}
|
||||
}
|
||||
|
||||
result.SerialNumber = strings.TrimSpace(certutil.GetHexFormatted(issuerCert.SerialNumber.Bytes(), ":"))
|
||||
|
||||
// Now, for each key, try and compute the issuer<->key link. We delay
|
||||
// writing issuer to storage as we won't need to update the key, only
|
||||
// the issuer.
|
||||
@@ -620,6 +620,10 @@ func importIssuer(ctx managedKeyContext, s logical.Storage, certValue string, is
|
||||
return &result, false, nil
|
||||
}
|
||||
|
||||
func areCertificatesEqual(cert1 *x509.Certificate, cert2 *x509.Certificate) bool {
|
||||
return bytes.Compare(cert1.Raw, cert2.Raw) == 0
|
||||
}
|
||||
|
||||
func setLocalCRLConfig(ctx context.Context, s logical.Storage, mapping *localCRLConfigEntry) error {
|
||||
json, err := logical.StorageEntryJSON(storageLocalCRLConfig, mapping)
|
||||
if err != nil {
|
||||
|
||||
237
builtin/logical/pki/test_helpers.go
Normal file
237
builtin/logical/pki/test_helpers.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package pki
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/sdk/helper/certutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Setup helpers
|
||||
func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
||||
config := logical.TestBackendConfig()
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
|
||||
var err error
|
||||
b := Backend(config)
|
||||
err = b.Setup(context.Background(), config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Assume for our tests we have performed the migration already.
|
||||
b.pkiStorageVersion.Store(1)
|
||||
return b, config.StorageView
|
||||
}
|
||||
|
||||
func mountPKIEndpoint(t *testing.T, client *api.Client, path string) {
|
||||
var err error
|
||||
err = client.Sys().Mount(path, &api.MountInput{
|
||||
Type: "pki",
|
||||
Config: api.MountConfigInput{
|
||||
DefaultLeaseTTL: "16h",
|
||||
MaxLeaseTTL: "32h",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed mounting pki endpoint")
|
||||
}
|
||||
|
||||
// Signing helpers
|
||||
func requireSignedBy(t *testing.T, cert *x509.Certificate, key crypto.PublicKey) {
|
||||
switch key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
requireRSASignedBy(t, cert, key.(*rsa.PublicKey))
|
||||
case *ecdsa.PublicKey:
|
||||
requireECDSASignedBy(t, cert, key.(*ecdsa.PublicKey))
|
||||
case ed25519.PublicKey:
|
||||
requireED25519SignedBy(t, cert, key.(ed25519.PublicKey))
|
||||
default:
|
||||
require.Fail(t, "unknown public key type %#v", key)
|
||||
}
|
||||
}
|
||||
|
||||
func requireRSASignedBy(t *testing.T, cert *x509.Certificate, key *rsa.PublicKey) {
|
||||
require.Contains(t, []x509.SignatureAlgorithm{x509.SHA256WithRSA, x509.SHA512WithRSA},
|
||||
cert.SignatureAlgorithm, "only sha256 signatures supported")
|
||||
|
||||
var hasher hash.Hash
|
||||
var hashAlgo crypto.Hash
|
||||
|
||||
switch cert.SignatureAlgorithm {
|
||||
case x509.SHA256WithRSA:
|
||||
hasher = sha256.New()
|
||||
hashAlgo = crypto.SHA256
|
||||
case x509.SHA512WithRSA:
|
||||
hasher = sha512.New()
|
||||
hashAlgo = crypto.SHA512
|
||||
}
|
||||
|
||||
hasher.Write(cert.RawTBSCertificate)
|
||||
hashData := hasher.Sum(nil)
|
||||
|
||||
err := rsa.VerifyPKCS1v15(key, hashAlgo, hashData, cert.Signature)
|
||||
require.NoError(t, err, "the certificate was not signed by the expected public rsa key.")
|
||||
}
|
||||
|
||||
func requireECDSASignedBy(t *testing.T, cert *x509.Certificate, key *ecdsa.PublicKey) {
|
||||
require.Contains(t, []x509.SignatureAlgorithm{x509.ECDSAWithSHA256, x509.ECDSAWithSHA512},
|
||||
cert.SignatureAlgorithm, "only ecdsa signatures supported")
|
||||
|
||||
var hasher hash.Hash
|
||||
switch cert.SignatureAlgorithm {
|
||||
case x509.ECDSAWithSHA256:
|
||||
hasher = sha256.New()
|
||||
case x509.ECDSAWithSHA512:
|
||||
hasher = sha512.New()
|
||||
}
|
||||
|
||||
hasher.Write(cert.RawTBSCertificate)
|
||||
hashData := hasher.Sum(nil)
|
||||
|
||||
verify := ecdsa.VerifyASN1(key, hashData, cert.Signature)
|
||||
require.True(t, verify, "the certificate was not signed by the expected public ecdsa key.")
|
||||
}
|
||||
|
||||
func requireED25519SignedBy(t *testing.T, cert *x509.Certificate, key ed25519.PublicKey) {
|
||||
require.Equal(t, x509.PureEd25519, cert.SignatureAlgorithm)
|
||||
ed25519.Verify(key, cert.RawTBSCertificate, cert.Signature)
|
||||
}
|
||||
|
||||
// Certificate helper
|
||||
func parseCert(t *testing.T, pemCert string) *x509.Certificate {
|
||||
block, _ := pem.Decode([]byte(pemCert))
|
||||
require.NotNil(t, block, "failed to decode PEM block")
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
return cert
|
||||
}
|
||||
|
||||
func requireMatchingPublicKeys(t *testing.T, cert *x509.Certificate, key crypto.PublicKey) {
|
||||
certPubKey := cert.PublicKey
|
||||
areEqual, err := certutil.ComparePublicKeysAndType(certPubKey, key)
|
||||
require.NoError(t, err, "failed comparing public keys: %#v", err)
|
||||
require.True(t, areEqual, "public keys mismatched: got: %v, expected: %v", certPubKey, key)
|
||||
}
|
||||
|
||||
func getSelfSigned(t *testing.T, subject, issuer *x509.Certificate, key *rsa.PrivateKey) (string, *x509.Certificate) {
|
||||
t.Helper()
|
||||
selfSigned, err := x509.CreateCertificate(rand.Reader, subject, issuer, key.Public(), key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(selfSigned)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pemSS := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: selfSigned,
|
||||
})))
|
||||
return pemSS, cert
|
||||
}
|
||||
|
||||
// CRL related helpers
|
||||
func getCrlCertificateList(t *testing.T, client *api.Client, mountPoint string) pkix.TBSCertificateList {
|
||||
path := fmt.Sprintf("/v1/%s/crl", mountPoint)
|
||||
return getParsedCrlAtPath(t, client, path).TBSCertList
|
||||
}
|
||||
|
||||
func parseCrlPemBytes(t *testing.T, crlPem []byte) pkix.TBSCertificateList {
|
||||
certList, err := x509.ParseCRL(crlPem)
|
||||
require.NoError(t, err)
|
||||
return certList.TBSCertList
|
||||
}
|
||||
|
||||
func requireSerialNumberInCRL(t *testing.T, revokeList pkix.TBSCertificateList, serialNum string) {
|
||||
serialsInList := make([]string, 0, len(revokeList.RevokedCertificates))
|
||||
for _, revokeEntry := range revokeList.RevokedCertificates {
|
||||
formattedSerial := certutil.GetHexFormatted(revokeEntry.SerialNumber.Bytes(), ":")
|
||||
serialsInList = append(serialsInList, formattedSerial)
|
||||
if formattedSerial == serialNum {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("the serial number %s, was not found in the CRL list containing: %v", serialNum, serialsInList)
|
||||
}
|
||||
|
||||
func getParsedCrl(t *testing.T, client *api.Client, mountPoint string) *pkix.CertificateList {
|
||||
path := fmt.Sprintf("/v1/%s/crl", mountPoint)
|
||||
return getParsedCrlAtPath(t, client, path)
|
||||
}
|
||||
|
||||
func getParsedCrlForIssuer(t *testing.T, client *api.Client, mountPoint string, issuer string) *pkix.CertificateList {
|
||||
path := fmt.Sprintf("/v1/%v/issuer/%v/crl/der", mountPoint, issuer)
|
||||
crl := getParsedCrlAtPath(t, client, path)
|
||||
|
||||
// Now fetch the issuer as well and verify the certificate
|
||||
path = fmt.Sprintf("/v1/%v/issuer/%v/der", mountPoint, issuer)
|
||||
req := client.NewRequest("GET", path)
|
||||
resp, err := client.RawRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
certBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(certBytes) == 0 {
|
||||
t.Fatalf("expected certificate in response body")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cert == nil {
|
||||
t.Fatalf("expected parsed certificate")
|
||||
}
|
||||
|
||||
if err := cert.CheckCRLSignature(crl); err != nil {
|
||||
t.Fatalf("expected valid signature on CRL for issuer %v: %v", issuer, crl)
|
||||
}
|
||||
|
||||
return crl
|
||||
}
|
||||
|
||||
func getParsedCrlAtPath(t *testing.T, client *api.Client, path string) *pkix.CertificateList {
|
||||
req := client.NewRequest("GET", path)
|
||||
resp, err := client.RawRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
crlBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(crlBytes) == 0 {
|
||||
t.Fatalf("expected CRL in response body")
|
||||
}
|
||||
|
||||
crl, err := x509.ParseDERCRL(crlBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return crl
|
||||
}
|
||||
Reference in New Issue
Block a user