open-vault/builtin/logical/pki/test_helpers.go
Alexander Scheel de00d14a51
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 13:29:57 -04:00

238 lines
6.7 KiB
Go

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.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
}
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) {
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
}