vault/helper/testhelpers/pki/pkihelper.go
Steven Clark 7d26c54350
Do not use static certificates for diagnose tests (#29122)
* Do not use static certificates for diagnose tests

* Fix operator command tests, move PKI CA creation code into testhelper lib

* Fix compilation error from refactoring
2024-12-09 14:03:16 +01:00

225 lines
6.0 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pki
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
mathrand2 "math/rand/v2"
"net"
"os"
"path/filepath"
"testing"
"time"
)
// This file contains helper functions for generating CA hierarchies for testing
type LeafWithRoot struct {
RootCa GeneratedCert
Leaf GeneratedCert
CombinedLeafCaFile string
}
type LeafWithIntermediary struct {
RootCa GeneratedCert
IntCa GeneratedCert
Leaf GeneratedCert
CombinedCaFile string
}
type GeneratedCert struct {
KeyFile string
CertFile string
CertPem *pem.Block
Cert *x509.Certificate
Key *ecdsa.PrivateKey
}
// GenerateCertWithIntermediaryRoot generates a leaf certificate signed by an intermediary root CA
func GenerateCertWithIntermediaryRoot(t testing.TB) LeafWithIntermediary {
t.Helper()
tempDir := t.TempDir()
template := &x509.Certificate{
Subject: pkix.Name{
CommonName: "localhost",
},
SerialNumber: big.NewInt(mathrand2.Int64()),
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(60 * 24 * time.Hour),
}
ca := GenerateRootCa(t)
caIntTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "Intermediary CA",
},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
SerialNumber: big.NewInt(mathrand2.Int64()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
BasicConstraintsValid: true,
IsCA: true,
}
caInt := generateCertAndSign(t, caIntTemplate, ca, tempDir, "int_")
leafCert := generateCertAndSign(t, template, caInt, tempDir, "leaf_")
combinedCasFile := filepath.Join(tempDir, "cas.pem")
err := os.WriteFile(combinedCasFile, append(pem.EncodeToMemory(caInt.CertPem), pem.EncodeToMemory(ca.CertPem)...), 0o644)
if err != nil {
t.Fatal(err)
}
return LeafWithIntermediary{
RootCa: ca,
IntCa: caInt,
Leaf: leafCert,
CombinedCaFile: combinedCasFile,
}
}
// generateCertAndSign generates a certificate and associated key signed by a CA
func generateCertAndSign(t testing.TB, template *x509.Certificate, ca GeneratedCert, tempDir string, filePrefix string) GeneratedCert {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
certBytes, err := x509.CreateCertificate(rand.Reader, template, ca.Cert, key.Public(), ca.Key)
if err != nil {
t.Fatal(err)
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
t.Fatal(err)
}
certPEMBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}
certFile := filepath.Join(tempDir, filePrefix+"cert.pem")
err = os.WriteFile(certFile, pem.EncodeToMemory(certPEMBlock), 0o644)
if err != nil {
t.Fatal(err)
}
marshaledKey, err := x509.MarshalECPrivateKey(key)
if err != nil {
t.Fatal(err)
}
keyPEMBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: marshaledKey,
}
keyFile := filepath.Join(tempDir, filePrefix+"key.pem")
err = os.WriteFile(keyFile, pem.EncodeToMemory(keyPEMBlock), 0o644)
if err != nil {
t.Fatal(err)
}
return GeneratedCert{
KeyFile: keyFile,
CertFile: certFile,
CertPem: certPEMBlock,
Cert: cert,
Key: key,
}
}
// GenerateCertWithRoot generates a leaf certificate signed by a root CA
func GenerateCertWithRoot(t testing.TB) LeafWithRoot {
t.Helper()
tempDir := t.TempDir()
leafTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "localhost",
},
SerialNumber: big.NewInt(mathrand2.Int64()),
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(60 * 24 * time.Hour),
}
ca := GenerateRootCa(t)
leafCert := generateCertAndSign(t, leafTemplate, ca, tempDir, "leaf_")
combinedCaLeafFile := filepath.Join(tempDir, "leaf-ca.pem")
err := os.WriteFile(combinedCaLeafFile, append(pem.EncodeToMemory(leafCert.CertPem), pem.EncodeToMemory(ca.CertPem)...), 0o644)
if err != nil {
t.Fatal(err)
}
return LeafWithRoot{
RootCa: ca,
Leaf: leafCert,
CombinedLeafCaFile: combinedCaLeafFile,
}
}
// GenerateRootCa generates a self-signed root CA certificate and key
func GenerateRootCa(t testing.TB) GeneratedCert {
t.Helper()
tempDir := t.TempDir()
caCertTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "Root CA",
},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
SerialNumber: big.NewInt(mathrand2.Int64()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
BasicConstraintsValid: true,
IsCA: true,
}
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, caKey.Public(), caKey)
if err != nil {
t.Fatal(err)
}
caCert, err := x509.ParseCertificate(caBytes)
if err != nil {
t.Fatal(err)
}
caCertPEMBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
}
caFile := filepath.Join(tempDir, "ca_root_cert.pem")
err = os.WriteFile(caFile, pem.EncodeToMemory(caCertPEMBlock), 0o644)
if err != nil {
t.Fatal(err)
}
marshaledCAKey, err := x509.MarshalECPrivateKey(caKey)
if err != nil {
t.Fatal(err)
}
caKeyPEMBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: marshaledCAKey,
}
caKeyFile := filepath.Join(tempDir, "ca_root_key.pem")
err = os.WriteFile(caKeyFile, pem.EncodeToMemory(caKeyPEMBlock), 0o644)
if err != nil {
t.Fatal(err)
}
return GeneratedCert{
CertPem: caCertPEMBlock,
CertFile: caFile,
KeyFile: caKeyFile,
Cert: caCert,
Key: caKey,
}
}