open-vault/builtin/logical/pki/test_helpers.go

242 lines
6.7 KiB
Go
Raw Normal View History

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
Benchmark chain building (#15315) * Refactor chain building test cases to be shared This will allow us to execute these test cases and then benchmark just the chain building, separate from the certificate creation (and without the consistency tests). Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Benchmark chain building code Using the existing test cases (and a few special ones), generate some simple chains and benchmark how long chain building takes. We switch from generating a cluster (slow) to directly calling createBackendWithStorage(), which improves test execution time too: $ go test -count=1 -run=Test_CAChainBuilding github.com/hashicorp/vault/builtin/logical/pki ok github.com/hashicorp/vault/builtin/logical/pki 0.764s (previously it was 5-10 seconds, for fewer tests). Additionally, we now have benchmarks: $ go test -v -run=BenchmarkChainBuilding -bench=. github.com/hashicorp/vault/builtin/logical/pki goos: linux goarch: amd64 pkg: github.com/hashicorp/vault/builtin/logical/pki cpu: Intel(R) Core(TM) i9-10885H CPU @ 2.40GHz BenchmarkChainBuilding BenchmarkChainBuilding/test-case-0 BenchmarkChainBuilding/test-case-0-16 616 1921783 ns/op BenchmarkChainBuilding/test-case-1 BenchmarkChainBuilding/test-case-1-16 1191 998201 ns/op BenchmarkChainBuilding/test-case-2 BenchmarkChainBuilding/test-case-2-16 547 2229810 ns/op BenchmarkChainBuilding/test-case-3 BenchmarkChainBuilding/test-case-3-16 525 2264951 ns/op BenchmarkChainBuilding/test-case-4 BenchmarkChainBuilding/test-case-4-16 1732 693686 ns/op BenchmarkChainBuilding/test-case-5 BenchmarkChainBuilding/test-case-5-16 51700 23230 ns/op BenchmarkChainBuilding/test-case-6 BenchmarkChainBuilding/test-case-6-16 9343 124523 ns/op BenchmarkChainBuilding/test-case-7 BenchmarkChainBuilding/test-case-7-16 5106 234902 ns/op BenchmarkChainBuilding/test-case-8 BenchmarkChainBuilding/test-case-8-16 2334 494382 ns/op PASS ok github.com/hashicorp/vault/builtin/logical/pki 12.707s Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2022-05-11 17:29:57 +00:00
func createBackendWithStorage(t testing.TB) (*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
}
Benchmark chain building (#15315) * Refactor chain building test cases to be shared This will allow us to execute these test cases and then benchmark just the chain building, separate from the certificate creation (and without the consistency tests). Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Benchmark chain building code Using the existing test cases (and a few special ones), generate some simple chains and benchmark how long chain building takes. We switch from generating a cluster (slow) to directly calling createBackendWithStorage(), which improves test execution time too: $ go test -count=1 -run=Test_CAChainBuilding github.com/hashicorp/vault/builtin/logical/pki ok github.com/hashicorp/vault/builtin/logical/pki 0.764s (previously it was 5-10 seconds, for fewer tests). Additionally, we now have benchmarks: $ go test -v -run=BenchmarkChainBuilding -bench=. github.com/hashicorp/vault/builtin/logical/pki goos: linux goarch: amd64 pkg: github.com/hashicorp/vault/builtin/logical/pki cpu: Intel(R) Core(TM) i9-10885H CPU @ 2.40GHz BenchmarkChainBuilding BenchmarkChainBuilding/test-case-0 BenchmarkChainBuilding/test-case-0-16 616 1921783 ns/op BenchmarkChainBuilding/test-case-1 BenchmarkChainBuilding/test-case-1-16 1191 998201 ns/op BenchmarkChainBuilding/test-case-2 BenchmarkChainBuilding/test-case-2-16 547 2229810 ns/op BenchmarkChainBuilding/test-case-3 BenchmarkChainBuilding/test-case-3-16 525 2264951 ns/op BenchmarkChainBuilding/test-case-4 BenchmarkChainBuilding/test-case-4-16 1732 693686 ns/op BenchmarkChainBuilding/test-case-5 BenchmarkChainBuilding/test-case-5-16 51700 23230 ns/op BenchmarkChainBuilding/test-case-6 BenchmarkChainBuilding/test-case-6-16 9343 124523 ns/op BenchmarkChainBuilding/test-case-7 BenchmarkChainBuilding/test-case-7-16 5106 234902 ns/op BenchmarkChainBuilding/test-case-8 BenchmarkChainBuilding/test-case-8-16 2334 494382 ns/op PASS ok github.com/hashicorp/vault/builtin/logical/pki 12.707s Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2022-05-11 17:29:57 +00:00
func mountPKIEndpoint(t testing.TB, 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) bool {
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 true
}
}
if t != nil {
t.Fatalf("the serial number %s, was not found in the CRL list containing: %v", serialNum, serialsInList)
}
return false
}
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
}