1be0ebae8a
Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
1048 lines
30 KiB
Go
1048 lines
30 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package certutil
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
mathrand "math/rand"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fatih/structs"
|
|
)
|
|
|
|
// Tests converting back and forth between a CertBundle and a ParsedCertBundle.
|
|
//
|
|
// Also tests the GetSubjKeyID, GetHexFormatted, ParseHexFormatted and
|
|
// ParsedCertBundle.getSigner functions.
|
|
func TestCertBundleConversion(t *testing.T) {
|
|
cbuts := []*CertBundle{
|
|
refreshRSACertBundle(),
|
|
refreshRSACertBundleWithChain(),
|
|
refreshRSA8CertBundle(),
|
|
refreshRSA8CertBundleWithChain(),
|
|
refreshECCertBundle(),
|
|
refreshECCertBundleWithChain(),
|
|
refreshEC8CertBundle(),
|
|
refreshEC8CertBundleWithChain(),
|
|
refreshEd255198CertBundle(),
|
|
refreshEd255198CertBundleWithChain(),
|
|
}
|
|
|
|
for i, cbut := range cbuts {
|
|
pcbut, err := cbut.ToParsedCertBundle()
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Errorf("Error converting to parsed cert bundle: %s", err)
|
|
continue
|
|
}
|
|
|
|
err = compareCertBundleToParsedCertBundle(cbut, pcbut)
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Errorf(err.Error())
|
|
}
|
|
|
|
cbut, err := pcbut.ToCertBundle()
|
|
if err != nil {
|
|
t.Fatalf("Error converting to cert bundle: %s", err)
|
|
}
|
|
|
|
err = compareCertBundleToParsedCertBundle(cbut, pcbut)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkCertBundleParsing(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
cbuts := []*CertBundle{
|
|
refreshRSACertBundle(),
|
|
refreshRSACertBundleWithChain(),
|
|
refreshRSA8CertBundle(),
|
|
refreshRSA8CertBundleWithChain(),
|
|
refreshECCertBundle(),
|
|
refreshECCertBundleWithChain(),
|
|
refreshEC8CertBundle(),
|
|
refreshEC8CertBundleWithChain(),
|
|
refreshEd255198CertBundle(),
|
|
refreshEd255198CertBundleWithChain(),
|
|
}
|
|
|
|
for i, cbut := range cbuts {
|
|
pcbut, err := cbut.ToParsedCertBundle()
|
|
if err != nil {
|
|
b.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
b.Errorf("Error converting to parsed cert bundle: %s", err)
|
|
continue
|
|
}
|
|
|
|
cbut, err = pcbut.ToCertBundle()
|
|
if err != nil {
|
|
b.Fatalf("Error converting to cert bundle: %s", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCertBundleParsing(t *testing.T) {
|
|
cbuts := []*CertBundle{
|
|
refreshRSACertBundle(),
|
|
refreshRSACertBundleWithChain(),
|
|
refreshRSA8CertBundle(),
|
|
refreshRSA8CertBundleWithChain(),
|
|
refreshECCertBundle(),
|
|
refreshECCertBundleWithChain(),
|
|
refreshEC8CertBundle(),
|
|
refreshEC8CertBundleWithChain(),
|
|
refreshEd255198CertBundle(),
|
|
refreshEd255198CertBundleWithChain(),
|
|
}
|
|
|
|
for i, cbut := range cbuts {
|
|
jsonString, err := json.Marshal(cbut)
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Fatalf("Error marshaling testing certbundle to JSON: %s", err)
|
|
}
|
|
pcbut, err := ParsePKIJSON(jsonString)
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Fatalf("Error during JSON bundle handling: %s", err)
|
|
}
|
|
err = compareCertBundleToParsedCertBundle(cbut, pcbut)
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
dataMap := structs.New(cbut).Map()
|
|
pcbut, err = ParsePKIMap(dataMap)
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Fatalf("Error during JSON bundle handling: %s", err)
|
|
}
|
|
err = compareCertBundleToParsedCertBundle(cbut, pcbut)
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
pcbut, err = ParsePEMBundle(cbut.ToPEMBundle())
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Fatalf("Error during JSON bundle handling: %s", err)
|
|
}
|
|
err = compareCertBundleToParsedCertBundle(cbut, pcbut)
|
|
if err != nil {
|
|
t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
|
|
t.Fatalf(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func compareCertBundleToParsedCertBundle(cbut *CertBundle, pcbut *ParsedCertBundle) error {
|
|
if cbut == nil {
|
|
return fmt.Errorf("got nil bundle")
|
|
}
|
|
if pcbut == nil {
|
|
return fmt.Errorf("got nil parsed bundle")
|
|
}
|
|
|
|
switch {
|
|
case pcbut.Certificate == nil:
|
|
return fmt.Errorf("parsed bundle has nil certificate")
|
|
case pcbut.PrivateKey == nil:
|
|
return fmt.Errorf("parsed bundle has nil private key")
|
|
}
|
|
|
|
switch cbut.PrivateKey {
|
|
case privRSAKeyPem:
|
|
if pcbut.PrivateKeyType != RSAPrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong private key type: %v, should be 'rsa' (%v)", pcbut.PrivateKeyType, RSAPrivateKey)
|
|
}
|
|
case privRSA8KeyPem:
|
|
if pcbut.PrivateKeyType != RSAPrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'rsa' (%v)", pcbut.PrivateKeyType, RSAPrivateKey)
|
|
}
|
|
case privECKeyPem:
|
|
if pcbut.PrivateKeyType != ECPrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong private key type: %v, should be 'ec' (%v)", pcbut.PrivateKeyType, ECPrivateKey)
|
|
}
|
|
case privEC8KeyPem:
|
|
if pcbut.PrivateKeyType != ECPrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'ec' (%v)", pcbut.PrivateKeyType, ECPrivateKey)
|
|
}
|
|
case privEd255198KeyPem:
|
|
if pcbut.PrivateKeyType != Ed25519PrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'ed25519' (%v)", pcbut.PrivateKeyType, ECPrivateKey)
|
|
}
|
|
default:
|
|
return fmt.Errorf("parsed bundle has unknown private key type")
|
|
}
|
|
|
|
subjKeyID, err := GetSubjKeyID(pcbut.PrivateKey)
|
|
if err != nil {
|
|
return fmt.Errorf("error when getting subject key id: %s", err)
|
|
}
|
|
if bytes.Compare(subjKeyID, pcbut.Certificate.SubjectKeyId) != 0 {
|
|
return fmt.Errorf("parsed bundle private key does not match subject key id\nGot\n%#v\nExpected\n%#v\nCert\n%#v", subjKeyID, pcbut.Certificate.SubjectKeyId, *pcbut.Certificate)
|
|
}
|
|
|
|
switch {
|
|
case len(pcbut.CAChain) > 0 && len(cbut.CAChain) == 0:
|
|
return fmt.Errorf("parsed bundle ca chain has certs when cert bundle does not")
|
|
case len(pcbut.CAChain) == 0 && len(cbut.CAChain) > 0:
|
|
return fmt.Errorf("cert bundle ca chain has certs when parsed cert bundle does not")
|
|
}
|
|
|
|
cb, err := pcbut.ToCertBundle()
|
|
if err != nil {
|
|
return fmt.Errorf("thrown error during parsed bundle conversion: %s\n\nInput was: %#v", err, *pcbut)
|
|
}
|
|
|
|
switch {
|
|
case len(cb.Certificate) == 0:
|
|
return fmt.Errorf("bundle has nil certificate")
|
|
case len(cb.PrivateKey) == 0:
|
|
return fmt.Errorf("bundle has nil private key")
|
|
case len(cb.CAChain[0]) == 0:
|
|
return fmt.Errorf("bundle has nil issuing CA")
|
|
}
|
|
|
|
switch pcbut.PrivateKeyType {
|
|
case RSAPrivateKey:
|
|
if cb.PrivateKey != privRSAKeyPem && cb.PrivateKey != privRSA8KeyPem {
|
|
return fmt.Errorf("bundle private key does not match")
|
|
}
|
|
case ECPrivateKey:
|
|
if cb.PrivateKey != privECKeyPem && cb.PrivateKey != privEC8KeyPem {
|
|
return fmt.Errorf("bundle private key does not match")
|
|
}
|
|
case Ed25519PrivateKey:
|
|
if cb.PrivateKey != privEd255198KeyPem {
|
|
return fmt.Errorf("bundle private key does not match")
|
|
}
|
|
default:
|
|
return fmt.Errorf("certBundle has unknown private key type")
|
|
}
|
|
|
|
if cb.SerialNumber != GetHexFormatted(pcbut.Certificate.SerialNumber.Bytes(), ":") {
|
|
return fmt.Errorf("bundle serial number does not match")
|
|
}
|
|
|
|
if !bytes.Equal(pcbut.Certificate.SerialNumber.Bytes(), ParseHexFormatted(cb.SerialNumber, ":")) {
|
|
return fmt.Errorf("failed re-parsing hex formatted number %s", cb.SerialNumber)
|
|
}
|
|
|
|
switch {
|
|
case len(pcbut.CAChain) > 0 && len(cb.CAChain) == 0:
|
|
return fmt.Errorf("parsed bundle ca chain has certs when cert bundle does not")
|
|
case len(pcbut.CAChain) == 0 && len(cb.CAChain) > 0:
|
|
return fmt.Errorf("cert bundle ca chain has certs when parsed cert bundle does not")
|
|
case !reflect.DeepEqual(cbut.CAChain, cb.CAChain):
|
|
return fmt.Errorf("cert bundle ca chain does not match: %#v\n\n%#v", cbut.CAChain, cb.CAChain)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func TestCSRBundleConversion(t *testing.T) {
|
|
csrbuts := []*CSRBundle{
|
|
refreshRSACSRBundle(),
|
|
refreshECCSRBundle(),
|
|
refreshEd25519CSRBundle(),
|
|
}
|
|
|
|
for _, csrbut := range csrbuts {
|
|
pcsrbut, err := csrbut.ToParsedCSRBundle()
|
|
if err != nil {
|
|
t.Fatalf("Error converting to parsed CSR bundle: %v", err)
|
|
}
|
|
|
|
err = compareCSRBundleToParsedCSRBundle(csrbut, pcsrbut)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
csrbut, err = pcsrbut.ToCSRBundle()
|
|
if err != nil {
|
|
t.Fatalf("Error converting to CSR bundle: %v", err)
|
|
}
|
|
|
|
err = compareCSRBundleToParsedCSRBundle(csrbut, pcsrbut)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func compareCSRBundleToParsedCSRBundle(csrbut *CSRBundle, pcsrbut *ParsedCSRBundle) error {
|
|
if csrbut == nil {
|
|
return fmt.Errorf("got nil bundle")
|
|
}
|
|
if pcsrbut == nil {
|
|
return fmt.Errorf("got nil parsed bundle")
|
|
}
|
|
|
|
switch {
|
|
case pcsrbut.CSR == nil:
|
|
return fmt.Errorf("parsed bundle has nil csr")
|
|
case pcsrbut.PrivateKey == nil:
|
|
return fmt.Errorf("parsed bundle has nil private key")
|
|
}
|
|
|
|
switch csrbut.PrivateKey {
|
|
case privRSAKeyPem:
|
|
if pcsrbut.PrivateKeyType != RSAPrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong private key type")
|
|
}
|
|
case privECKeyPem:
|
|
if pcsrbut.PrivateKeyType != ECPrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong private key type")
|
|
}
|
|
case privEd255198KeyPem:
|
|
if pcsrbut.PrivateKeyType != Ed25519PrivateKey {
|
|
return fmt.Errorf("parsed bundle has wrong private key type")
|
|
}
|
|
default:
|
|
return fmt.Errorf("parsed bundle has unknown private key type")
|
|
}
|
|
|
|
csrb, err := pcsrbut.ToCSRBundle()
|
|
if err != nil {
|
|
return fmt.Errorf("Thrown error during parsed bundle conversion: %s\n\nInput was: %#v", err, *pcsrbut)
|
|
}
|
|
|
|
switch {
|
|
case len(csrb.CSR) == 0:
|
|
return fmt.Errorf("bundle has nil certificate")
|
|
case len(csrb.PrivateKey) == 0:
|
|
return fmt.Errorf("bundle has nil private key")
|
|
}
|
|
|
|
switch csrb.PrivateKeyType {
|
|
case "rsa":
|
|
if pcsrbut.PrivateKeyType != RSAPrivateKey {
|
|
return fmt.Errorf("bundle has wrong private key type")
|
|
}
|
|
if csrb.PrivateKey != privRSAKeyPem {
|
|
return fmt.Errorf("bundle rsa private key does not match\nGot\n%#v\nExpected\n%#v", csrb.PrivateKey, privRSAKeyPem)
|
|
}
|
|
case "ec":
|
|
if pcsrbut.PrivateKeyType != ECPrivateKey {
|
|
return fmt.Errorf("bundle has wrong private key type")
|
|
}
|
|
if csrb.PrivateKey != privECKeyPem {
|
|
return fmt.Errorf("bundle ec private key does not match")
|
|
}
|
|
case "ed25519":
|
|
if pcsrbut.PrivateKeyType != Ed25519PrivateKey {
|
|
return fmt.Errorf("bundle has wrong private key type")
|
|
}
|
|
if csrb.PrivateKey != privEd255198KeyPem {
|
|
return fmt.Errorf("bundle ed25519 private key does not match")
|
|
}
|
|
default:
|
|
return fmt.Errorf("bundle has unknown private key type")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func TestTLSConfig(t *testing.T) {
|
|
cbut := refreshRSACertBundle()
|
|
|
|
pcbut, err := cbut.ToParsedCertBundle()
|
|
if err != nil {
|
|
t.Fatalf("Error getting parsed cert bundle: %s", err)
|
|
}
|
|
|
|
usages := []TLSUsage{
|
|
TLSUnknown,
|
|
TLSClient,
|
|
TLSServer,
|
|
TLSClient | TLSServer,
|
|
}
|
|
|
|
for _, usage := range usages {
|
|
tlsConfig, err := pcbut.GetTLSConfig(usage)
|
|
if err != nil {
|
|
t.Fatalf("Error getting tls config: %s", err)
|
|
}
|
|
if tlsConfig == nil {
|
|
t.Fatalf("Got nil tls.Config")
|
|
}
|
|
|
|
if len(tlsConfig.Certificates) != 1 {
|
|
t.Fatalf("Unexpected length in config.Certificates")
|
|
}
|
|
|
|
// Length should be 2, since we passed in a CA
|
|
if len(tlsConfig.Certificates[0].Certificate) != 2 {
|
|
t.Fatalf("Did not find both certificates in config.Certificates.Certificate")
|
|
}
|
|
|
|
if tlsConfig.Certificates[0].Leaf != pcbut.Certificate {
|
|
t.Fatalf("Leaf certificate does not match parsed bundle's certificate")
|
|
}
|
|
|
|
if tlsConfig.Certificates[0].PrivateKey != pcbut.PrivateKey {
|
|
t.Fatalf("Config's private key does not match parsed bundle's private key")
|
|
}
|
|
|
|
switch usage {
|
|
case TLSServer | TLSClient:
|
|
if len(tlsConfig.ClientCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.ClientCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
|
|
t.Fatalf("CA certificate not in client cert pool as expected")
|
|
}
|
|
if len(tlsConfig.RootCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.RootCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
|
|
t.Fatalf("CA certificate not in root cert pool as expected")
|
|
}
|
|
case TLSServer:
|
|
if len(tlsConfig.ClientCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.ClientCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
|
|
t.Fatalf("CA certificate not in client cert pool as expected")
|
|
}
|
|
if tlsConfig.RootCAs != nil {
|
|
t.Fatalf("Found root pools in config object when not expected")
|
|
}
|
|
case TLSClient:
|
|
if len(tlsConfig.RootCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.RootCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
|
|
t.Fatalf("CA certificate not in root cert pool as expected")
|
|
}
|
|
if tlsConfig.ClientCAs != nil {
|
|
t.Fatalf("Found root pools in config object when not expected")
|
|
}
|
|
default:
|
|
if tlsConfig.RootCAs != nil || tlsConfig.ClientCAs != nil {
|
|
t.Fatalf("Found root pools in config object when not expected")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewCertPool(t *testing.T) {
|
|
caExample := `-----BEGIN CERTIFICATE-----
|
|
MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
|
|
a3ViZUNBMB4XDTE5MTIxMDIzMDUxOVoXDTI5MTIwODIzMDUxOVowFTETMBEGA1UE
|
|
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANFi
|
|
/RIdMHd865X6JygTb9riX01DA3QnR+RoXDXNnj8D3LziLG2n8ItXMJvWbU3sxxyy
|
|
nX9HxJ0SIeexj1cYzdQBtJDjO1/PeuKc4CZ7zCukCAtHz8mC7BDPOU7F7pggpcQ0
|
|
/t/pa2m22hmCu8aDF9WlUYHtJpYATnI/A5vz/VFLR9daxmkl59Qo3oHITj7vAzSx
|
|
/75r9cibpQyJ+FhiHOZHQWYY2JYw2g4v5hm5hg5SFM9yFcZ75ISI9ebyFFIl9iBY
|
|
zAk9jqv1mXvLr0Q39AVwMTamvGuap1oocjM9NIhQvaFL/DNqF1ouDQjCf5u2imLc
|
|
TraO1/2KO8fqwOZCOrMCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
|
|
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
|
|
DQEBCwUAA4IBAQBtVZCwCPqUUUpIClAlE9nc2fo2bTs9gsjXRmqdQ5oaSomSLE93
|
|
aJWYFuAhxPXtlApbLYZfW2m1sM3mTVQN60y0uE4e1jdSN1ErYQ9slJdYDAMaEmOh
|
|
iSexj+Nd1scUiMHV9lf3ps5J8sYeCpwZX3sPmw7lqZojTS12pANBDcigsaj5RRyN
|
|
9GyP3WkSQUsTpWlDb9Fd+KNdkCVw7nClIpBPA2KW4BQKw/rNSvOFD61mbzc89lo0
|
|
Q9IFGQFFF8jO18lbyWqnRBGXcS4/G7jQ3S7C121d14YLUeAYOM7pJykI1g4CLx9y
|
|
vitin0L6nprauWkKO38XgM4T75qKZpqtiOcT
|
|
-----END CERTIFICATE-----
|
|
`
|
|
if _, err := NewCertPool(bytes.NewReader([]byte(caExample))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestGetPublicKeySize(t *testing.T) {
|
|
rsa, err := rsa.GenerateKey(rand.Reader, 3072)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if GetPublicKeySize(&rsa.PublicKey) != 3072 {
|
|
t.Fatal("unexpected rsa key size")
|
|
}
|
|
ecdsa, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if GetPublicKeySize(&ecdsa.PublicKey) != 384 {
|
|
t.Fatal("unexpected ecdsa key size")
|
|
}
|
|
ed25519, _, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if GetPublicKeySize(ed25519) != 256 {
|
|
t.Fatal("unexpected ed25519 key size")
|
|
}
|
|
// Skipping DSA as too slow
|
|
}
|
|
|
|
func refreshRSA8CertBundle() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
return &CertBundle{
|
|
Certificate: certRSAPem,
|
|
PrivateKey: privRSA8KeyPem,
|
|
CAChain: []string{issuingCaChainPem[0]},
|
|
}
|
|
}
|
|
|
|
func refreshRSA8CertBundleWithChain() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
ret := refreshRSA8CertBundle()
|
|
ret.CAChain = issuingCaChainPem
|
|
return ret
|
|
}
|
|
|
|
func refreshRSACertBundle() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
return &CertBundle{
|
|
Certificate: certRSAPem,
|
|
CAChain: []string{issuingCaChainPem[0]},
|
|
PrivateKey: privRSAKeyPem,
|
|
}
|
|
}
|
|
|
|
func refreshRSACertBundleWithChain() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
ret := refreshRSACertBundle()
|
|
ret.CAChain = issuingCaChainPem
|
|
return ret
|
|
}
|
|
|
|
func refreshECCertBundle() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
return &CertBundle{
|
|
Certificate: certECPem,
|
|
CAChain: []string{issuingCaChainPem[0]},
|
|
PrivateKey: privECKeyPem,
|
|
}
|
|
}
|
|
|
|
func refreshECCertBundleWithChain() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
ret := refreshECCertBundle()
|
|
ret.CAChain = issuingCaChainPem
|
|
return ret
|
|
}
|
|
|
|
func refreshEd255198CertBundle() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
return &CertBundle{
|
|
Certificate: certEd25519Pem,
|
|
PrivateKey: privEd255198KeyPem,
|
|
CAChain: []string{issuingCaChainPem[0]},
|
|
}
|
|
}
|
|
|
|
func refreshEd255198CertBundleWithChain() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
ret := refreshEd255198CertBundle()
|
|
ret.CAChain = issuingCaChainPem
|
|
return ret
|
|
}
|
|
|
|
func refreshEd25519CSRBundle() *CSRBundle {
|
|
initTest.Do(setCerts)
|
|
return &CSRBundle{
|
|
CSR: csrEd25519Pem,
|
|
PrivateKey: privEd255198KeyPem,
|
|
}
|
|
}
|
|
|
|
func refreshRSACSRBundle() *CSRBundle {
|
|
initTest.Do(setCerts)
|
|
return &CSRBundle{
|
|
CSR: csrRSAPem,
|
|
PrivateKey: privRSAKeyPem,
|
|
}
|
|
}
|
|
|
|
func refreshECCSRBundle() *CSRBundle {
|
|
initTest.Do(setCerts)
|
|
return &CSRBundle{
|
|
CSR: csrECPem,
|
|
PrivateKey: privECKeyPem,
|
|
}
|
|
}
|
|
|
|
func refreshEC8CertBundle() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
return &CertBundle{
|
|
Certificate: certECPem,
|
|
PrivateKey: privEC8KeyPem,
|
|
CAChain: []string{issuingCaChainPem[0]},
|
|
}
|
|
}
|
|
|
|
func refreshEC8CertBundleWithChain() *CertBundle {
|
|
initTest.Do(setCerts)
|
|
ret := refreshEC8CertBundle()
|
|
ret.CAChain = issuingCaChainPem
|
|
return ret
|
|
}
|
|
|
|
func setCerts() {
|
|
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subjKeyID, err := GetSubjKeyID(caKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caCertTemplate := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "root.localhost",
|
|
},
|
|
SubjectKeyId: subjKeyID,
|
|
DNSNames: []string{"root.localhost"},
|
|
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, caKey.Public(), caKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caCert, err := x509.ParseCertificate(caBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
caCertPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: caBytes,
|
|
}
|
|
caCertPEM := strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
|
|
|
|
intKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subjKeyID, err = GetSubjKeyID(intKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
intCertTemplate := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "int.localhost",
|
|
},
|
|
SubjectKeyId: subjKeyID,
|
|
DNSNames: []string{"int.localhost"},
|
|
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
intBytes, err := x509.CreateCertificate(rand.Reader, intCertTemplate, caCert, intKey.Public(), caKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
intCert, err := x509.ParseCertificate(intBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
intCertPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: intBytes,
|
|
}
|
|
intCertPEM := strings.TrimSpace(string(pem.EncodeToMemory(intCertPEMBlock)))
|
|
|
|
// EC generation
|
|
{
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subjKeyID, err := GetSubjKeyID(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
certTemplate := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "localhost",
|
|
},
|
|
SubjectKeyId: subjKeyID,
|
|
DNSNames: []string{"localhost"},
|
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageServerAuth,
|
|
x509.ExtKeyUsageClientAuth,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
}
|
|
csrTemplate := &x509.CertificateRequest{
|
|
Subject: pkix.Name{
|
|
CommonName: "localhost",
|
|
},
|
|
DNSNames: []string{"localhost"},
|
|
}
|
|
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
csrPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE REQUEST",
|
|
Bytes: csrBytes,
|
|
}
|
|
csrECPem = strings.TrimSpace(string(pem.EncodeToMemory(csrPEMBlock)))
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, intCert, key.Public(), intKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
certPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certBytes,
|
|
}
|
|
certECPem = strings.TrimSpace(string(pem.EncodeToMemory(certPEMBlock)))
|
|
marshaledKey, err := x509.MarshalECPrivateKey(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyPEMBlock := &pem.Block{
|
|
Type: "EC PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
privECKeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
marshaledKey, err = x509.MarshalPKCS8PrivateKey(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyPEMBlock = &pem.Block{
|
|
Type: "PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
privEC8KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
}
|
|
|
|
// RSA generation
|
|
{
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subjKeyID, err := GetSubjKeyID(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
certTemplate := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "localhost",
|
|
},
|
|
SubjectKeyId: subjKeyID,
|
|
DNSNames: []string{"localhost"},
|
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageServerAuth,
|
|
x509.ExtKeyUsageClientAuth,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
}
|
|
csrTemplate := &x509.CertificateRequest{
|
|
Subject: pkix.Name{
|
|
CommonName: "localhost",
|
|
},
|
|
DNSNames: []string{"localhost"},
|
|
}
|
|
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
csrPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE REQUEST",
|
|
Bytes: csrBytes,
|
|
}
|
|
csrRSAPem = strings.TrimSpace(string(pem.EncodeToMemory(csrPEMBlock)))
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, intCert, key.Public(), intKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
certPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certBytes,
|
|
}
|
|
certRSAPem = strings.TrimSpace(string(pem.EncodeToMemory(certPEMBlock)))
|
|
marshaledKey := x509.MarshalPKCS1PrivateKey(key)
|
|
keyPEMBlock := &pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
privRSAKeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
marshaledKey, err = x509.MarshalPKCS8PrivateKey(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyPEMBlock = &pem.Block{
|
|
Type: "PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
privRSA8KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
}
|
|
|
|
// Ed25519 generation
|
|
{
|
|
pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subjKeyID, err := GetSubjKeyID(privkey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
certTemplate := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "localhost",
|
|
},
|
|
SubjectKeyId: subjKeyID,
|
|
DNSNames: []string{"localhost"},
|
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageServerAuth,
|
|
x509.ExtKeyUsageClientAuth,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
}
|
|
csrTemplate := &x509.CertificateRequest{
|
|
Subject: pkix.Name{
|
|
CommonName: "localhost",
|
|
},
|
|
DNSNames: []string{"localhost"},
|
|
}
|
|
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privkey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
csrPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE REQUEST",
|
|
Bytes: csrBytes,
|
|
}
|
|
csrEd25519Pem = strings.TrimSpace(string(pem.EncodeToMemory(csrPEMBlock)))
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, intCert, pubkey, intKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
certPEMBlock := &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certBytes,
|
|
}
|
|
certEd25519Pem = strings.TrimSpace(string(pem.EncodeToMemory(certPEMBlock)))
|
|
marshaledKey, err := x509.MarshalPKCS8PrivateKey(privkey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyPEMBlock := &pem.Block{
|
|
Type: "PRIVATE KEY",
|
|
Bytes: marshaledKey,
|
|
}
|
|
privEd255198KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
|
|
}
|
|
|
|
issuingCaChainPem = []string{intCertPEM, caCertPEM}
|
|
}
|
|
|
|
func TestComparePublicKeysAndType(t *testing.T) {
|
|
rsa1 := genRsaKey(t).Public()
|
|
rsa2 := genRsaKey(t).Public()
|
|
eddsa1 := genEdDSA(t).Public()
|
|
eddsa2 := genEdDSA(t).Public()
|
|
ed25519_1, _ := genEd25519Key(t)
|
|
ed25519_2, _ := genEd25519Key(t)
|
|
|
|
type args struct {
|
|
key1Iface crypto.PublicKey
|
|
key2Iface crypto.PublicKey
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{name: "RSA_Equal", args: args{key1Iface: rsa1, key2Iface: rsa1}, want: true, wantErr: false},
|
|
{name: "RSA_NotEqual", args: args{key1Iface: rsa1, key2Iface: rsa2}, want: false, wantErr: false},
|
|
{name: "EDDSA_Equal", args: args{key1Iface: eddsa1, key2Iface: eddsa1}, want: true, wantErr: false},
|
|
{name: "EDDSA_NotEqual", args: args{key1Iface: eddsa1, key2Iface: eddsa2}, want: false, wantErr: false},
|
|
{name: "ED25519_Equal", args: args{key1Iface: ed25519_1, key2Iface: ed25519_1}, want: true, wantErr: false},
|
|
{name: "ED25519_NotEqual", args: args{key1Iface: ed25519_1, key2Iface: ed25519_2}, want: false, wantErr: false},
|
|
{name: "Mismatched_RSA", args: args{key1Iface: rsa1, key2Iface: ed25519_2}, want: false, wantErr: false},
|
|
{name: "Mismatched_EDDSA", args: args{key1Iface: ed25519_1, key2Iface: rsa1}, want: false, wantErr: false},
|
|
{name: "Mismatched_ED25519", args: args{key1Iface: ed25519_1, key2Iface: rsa1}, want: false, wantErr: false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := ComparePublicKeysAndType(tt.args.key1Iface, tt.args.key2Iface)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ComparePublicKeysAndType() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("ComparePublicKeysAndType() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNotAfterValues(t *testing.T) {
|
|
if ErrNotAfterBehavior != 0 {
|
|
t.Fatalf("Expected ErrNotAfterBehavior=%v to have value 0", ErrNotAfterBehavior)
|
|
}
|
|
|
|
if TruncateNotAfterBehavior != 1 {
|
|
t.Fatalf("Expected TruncateNotAfterBehavior=%v to have value 1", TruncateNotAfterBehavior)
|
|
}
|
|
|
|
if PermitNotAfterBehavior != 2 {
|
|
t.Fatalf("Expected PermitNotAfterBehavior=%v to have value 2", PermitNotAfterBehavior)
|
|
}
|
|
}
|
|
|
|
func TestSignatureAlgorithmRoundTripping(t *testing.T) {
|
|
for leftName, value := range SignatureAlgorithmNames {
|
|
if leftName == "pureed25519" && value == x509.PureEd25519 {
|
|
continue
|
|
}
|
|
|
|
rightName, present := InvSignatureAlgorithmNames[value]
|
|
if !present {
|
|
t.Fatalf("%v=%v is present in SignatureAlgorithmNames but not in InvSignatureAlgorithmNames", leftName, value)
|
|
}
|
|
|
|
if strings.ToLower(rightName) != leftName {
|
|
t.Fatalf("%v=%v is present in SignatureAlgorithmNames but inverse for %v has different name: %v", leftName, value, value, rightName)
|
|
}
|
|
}
|
|
|
|
for leftValue, name := range InvSignatureAlgorithmNames {
|
|
rightValue, present := SignatureAlgorithmNames[strings.ToLower(name)]
|
|
if !present {
|
|
t.Fatalf("%v=%v is present in InvSignatureAlgorithmNames but not in SignatureAlgorithmNames", leftValue, name)
|
|
}
|
|
|
|
if rightValue != leftValue {
|
|
t.Fatalf("%v=%v is present in InvSignatureAlgorithmNames but forwards for %v has different value: %v", leftValue, name, name, rightValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestParseBasicConstraintExtension Verify extension generation and parsing of x509 basic constraint extensions
|
|
// works as expected.
|
|
func TestBasicConstraintExtension(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
isCA bool
|
|
maxPathLen int
|
|
}{
|
|
{"empty-seq", false, -1},
|
|
{"just-ca-true", true, -1},
|
|
{"just-ca-with-maxpathlen", true, 2},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ext, err := CreateBasicConstraintExtension(tt.isCA, tt.maxPathLen)
|
|
if err != nil {
|
|
t.Fatalf("failed generating basic extension: %v", err)
|
|
}
|
|
|
|
gotIsCa, gotMaxPathLen, err := ParseBasicConstraintExtension(ext)
|
|
if err != nil {
|
|
t.Fatalf("failed parsing basic extension: %v", err)
|
|
}
|
|
|
|
if tt.isCA != gotIsCa {
|
|
t.Fatalf("expected isCa (%v) got isCa (%v)", tt.isCA, gotIsCa)
|
|
}
|
|
|
|
if tt.maxPathLen != gotMaxPathLen {
|
|
t.Fatalf("expected maxPathLen (%v) got maxPathLen (%v)", tt.maxPathLen, gotMaxPathLen)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("bad-extension-oid", func(t *testing.T) {
|
|
// Test invalid type errors out
|
|
_, _, err := ParseBasicConstraintExtension(pkix.Extension{})
|
|
if err == nil {
|
|
t.Fatalf("should have failed parsing non-basic constraint extension")
|
|
}
|
|
})
|
|
|
|
t.Run("garbage-value", func(t *testing.T) {
|
|
extraBytes, err := asn1.Marshal("a string")
|
|
if err != nil {
|
|
t.Fatalf("failed encoding the struct: %v", err)
|
|
}
|
|
ext := pkix.Extension{
|
|
Id: ExtensionBasicConstraintsOID,
|
|
Value: extraBytes,
|
|
}
|
|
_, _, err = ParseBasicConstraintExtension(ext)
|
|
if err == nil {
|
|
t.Fatalf("should have failed parsing basic constraint with extra information")
|
|
}
|
|
})
|
|
}
|
|
|
|
func genRsaKey(t *testing.T) *rsa.PrivateKey {
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return key
|
|
}
|
|
|
|
func genEdDSA(t *testing.T) *ecdsa.PrivateKey {
|
|
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return key
|
|
}
|
|
|
|
func genEd25519Key(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
|
|
key, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return key, priv
|
|
}
|
|
|
|
var (
|
|
initTest sync.Once
|
|
privRSA8KeyPem string
|
|
privRSAKeyPem string
|
|
csrRSAPem string
|
|
certRSAPem string
|
|
privEd255198KeyPem string
|
|
csrEd25519Pem string
|
|
certEd25519Pem string
|
|
privECKeyPem string
|
|
csrECPem string
|
|
privEC8KeyPem string
|
|
certECPem string
|
|
issuingCaChainPem []string
|
|
)
|