mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
925 lines
28 KiB
Go
925 lines
28 KiB
Go
package bundler
|
|
|
|
// This test file contains mostly tests on checking Bundle.Status when bundling under different circumstances.
|
|
import (
|
|
"bytes"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cloudflare/cfssl/errors"
|
|
"github.com/cloudflare/cfssl/helpers"
|
|
"github.com/cloudflare/cfssl/ubiquity"
|
|
)
|
|
|
|
const (
|
|
testCaBundle = "testdata/ca-bundle.pem"
|
|
testIntCaBundle = "testdata/int-bundle.pem"
|
|
testNSSRootBundle = "testdata/nss.pem"
|
|
testMetadata = "testdata/ca-bundle.crt.metadata"
|
|
testCFSSLRootBundle = "testdata/ca.pem"
|
|
testCAFile = "testdata/ca.pem"
|
|
testCAKeyFile = "testdata/ca.key"
|
|
testCFSSLIntBundle = "testdata/intermediates.crt"
|
|
emptyPEM = "testdata/empty.pem"
|
|
interL1SHA1 = "testdata/inter-L1-sha1.pem"
|
|
interL1Key = "testdata/inter-L1.key"
|
|
interL2SHA2 = "testdata/inter-L2.pem"
|
|
interL2Key = "testdata/inter-L2.key"
|
|
)
|
|
|
|
// Simply create a bundler
|
|
func TestNewBundler(t *testing.T) {
|
|
newBundler(t)
|
|
}
|
|
|
|
func TestNewBundlerMissingCA(t *testing.T) {
|
|
badFile := "testdata/no_such_file.pem"
|
|
_, err := NewBundler(badFile, testIntCaBundle)
|
|
if err == nil {
|
|
t.Fatal("Should fail with error code 4001")
|
|
}
|
|
|
|
// generate a function checking error content
|
|
errorCheck := ExpectErrorMessage(`"code":4001`)
|
|
errorCheck(t, err)
|
|
}
|
|
|
|
func TestNewBundlerMissingIntermediate(t *testing.T) {
|
|
badFile := "testdata/no_such_file.pem"
|
|
_, err := NewBundler(testCaBundle, badFile)
|
|
if err == nil {
|
|
t.Fatal("Should fail with error code 3001")
|
|
}
|
|
|
|
// generate a function checking error content
|
|
errorCheck := ExpectErrorMessage(`"code":3001`)
|
|
errorCheck(t, err)
|
|
}
|
|
|
|
// JSON object of a bundle
|
|
type bundleObject struct {
|
|
Bundle string `json:"bundle"`
|
|
Root string `json:"root"`
|
|
Cert string `json:"crt"`
|
|
Key string `json:"key"`
|
|
KeyType string `json:"key_type"`
|
|
KeySize int `json:"key_size"`
|
|
Issuer string `json:"issuer"`
|
|
Subject string `json:"subject"`
|
|
Expires string `json:"expires"`
|
|
Hostnames []string `json:"hostnames"`
|
|
OCSPSupport bool `json:"ocsp_support"`
|
|
CRLSupport bool `json:"crl_support"`
|
|
OCSP []string `json:"ocsp"`
|
|
Signature string `json:"signature"`
|
|
Status BundleStatus
|
|
}
|
|
|
|
var godaddyIssuerString = `/Country=US/Organization=The Go Daddy Group, Inc./OrganizationalUnit=Go Daddy Class 2 Certification Authority`
|
|
var godaddySubjectString = `/Country=US/Province=Arizona/Locality=Scottsdale/Organization=GoDaddy.com, Inc./OrganizationalUnit=http://certificates.godaddy.com/repository/CommonName=Go Daddy Secure Certification Authority/SerialNumber=07969287`
|
|
|
|
// Test marshal to JSON
|
|
// Also serves as a JSON format regression test.
|
|
func TestBundleMarshalJSON(t *testing.T) {
|
|
b := newBundler(t)
|
|
bundle, _ := b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Optimal, "")
|
|
bytes, err := json.Marshal(bundle)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var obj bundleObject
|
|
err = json.Unmarshal(bytes, &obj)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if obj.Bundle == "" {
|
|
t.Fatal("bundle is empty.")
|
|
}
|
|
if obj.Bundle != string(GoDaddyIntermediateCert) {
|
|
t.Fatal("bundle is incorrect:", obj.Bundle)
|
|
}
|
|
|
|
if obj.Key != "" {
|
|
t.Fatal("key is not empty:", obj.Key)
|
|
}
|
|
|
|
if obj.Root != string(GoDaddyRootCert) {
|
|
t.Fatal("Root is not recovered")
|
|
}
|
|
|
|
if obj.Cert != string(GoDaddyIntermediateCert) {
|
|
t.Fatal("Cert is not recovered")
|
|
}
|
|
|
|
if obj.KeyType != "2048-bit RSA" {
|
|
t.Fatal("Incorrect key type:", obj.KeyType)
|
|
}
|
|
|
|
if obj.KeySize != 2048 {
|
|
t.Fatal("Incorrect key size:", obj.KeySize)
|
|
}
|
|
|
|
if obj.Issuer != godaddyIssuerString {
|
|
t.Fatal("Incorrect issuer:", obj.Issuer)
|
|
}
|
|
|
|
if obj.Subject != godaddySubjectString {
|
|
t.Fatal("Incorrect subject:", obj.Subject)
|
|
}
|
|
|
|
if obj.Expires != "2026-11-16T01:54:37Z" {
|
|
t.Fatal("Incorrect expiration time:", obj.Expires)
|
|
}
|
|
|
|
if len(obj.Hostnames) != 1 || obj.Hostnames[0] != "Go Daddy Secure Certification Authority" {
|
|
t.Fatal("Incorrect hostnames:", obj.Hostnames)
|
|
}
|
|
|
|
if obj.OCSPSupport != true {
|
|
t.Fatal("Incorrect OCSP support flag:", obj.OCSPSupport)
|
|
}
|
|
|
|
if obj.CRLSupport != true {
|
|
t.Fatal("Incorrect CRL support flag:", obj.CRLSupport)
|
|
}
|
|
|
|
if len(obj.OCSP) != 1 || obj.OCSP[0] != `http://ocsp.godaddy.com` {
|
|
t.Fatal("Incorrect ocsp server list:", obj.OCSP)
|
|
}
|
|
|
|
if obj.Signature != "SHA1WithRSA" {
|
|
t.Fatal("Incorrect cert signature method:", obj.Signature)
|
|
}
|
|
}
|
|
|
|
func TestBundleWithECDSAKeyMarshalJSON(t *testing.T) {
|
|
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
|
|
bundle, _ := b.BundleFromFile(leafECDSA256, leafKeyECDSA256, Optimal, "")
|
|
jsonBytes, err := json.Marshal(bundle)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var obj map[string]interface{}
|
|
err = json.Unmarshal(jsonBytes, &obj)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
key := obj["key"].(string)
|
|
keyBytes, _ := ioutil.ReadFile(leafKeyECDSA256)
|
|
keyBytes = bytes.Trim(keyBytes, " \n")
|
|
if key != string(keyBytes) {
|
|
t.Fatal("key is not recovered.")
|
|
}
|
|
|
|
cert := obj["crt"].(string)
|
|
certBytes, _ := ioutil.ReadFile(leafECDSA256)
|
|
certBytes = bytes.Trim(certBytes, " \n")
|
|
if cert != string(certBytes) {
|
|
t.Fatal("cert is not recovered.")
|
|
}
|
|
|
|
keyType := obj["key_type"]
|
|
if keyType != "256-bit ECDSA" {
|
|
t.Fatal("Incorrect key type:", keyType)
|
|
}
|
|
|
|
}
|
|
|
|
func TestBundleWithRSAKeyMarshalJSON(t *testing.T) {
|
|
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
|
|
bundle, _ := b.BundleFromFile(leafRSA2048, leafKeyRSA2048, Optimal, "")
|
|
jsonBytes, err := json.Marshal(bundle)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var obj map[string]interface{}
|
|
err = json.Unmarshal(jsonBytes, &obj)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
key := obj["key"].(string)
|
|
keyBytes, _ := ioutil.ReadFile(leafKeyRSA2048)
|
|
keyBytes = bytes.Trim(keyBytes, " \n")
|
|
if key != string(keyBytes) {
|
|
t.Error("key is", key)
|
|
t.Error("keyBytes is", string(keyBytes))
|
|
t.Fatal("key is not recovered.")
|
|
}
|
|
|
|
cert := obj["crt"].(string)
|
|
certBytes, _ := ioutil.ReadFile(leafRSA2048)
|
|
certBytes = bytes.Trim(certBytes, " \n")
|
|
if cert != string(certBytes) {
|
|
t.Fatal("cert is not recovered.")
|
|
}
|
|
|
|
keyType := obj["key_type"]
|
|
if keyType != "2048-bit RSA" {
|
|
t.Fatal("Incorrect key type:", keyType)
|
|
}
|
|
|
|
}
|
|
|
|
// Test marshal to JSON on hostnames
|
|
func TestBundleHostnamesMarshalJSON(t *testing.T) {
|
|
b := newBundler(t)
|
|
|
|
bundle, _ := b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Optimal, "")
|
|
expected := []byte(`["Go Daddy Secure Certification Authority"]`)
|
|
hostnames, _ := json.Marshal(bundle.Hostnames)
|
|
if !bytes.Equal(hostnames, expected) {
|
|
t.Fatal("Hostnames construction failed for godaddy root cert.", string(hostnames))
|
|
}
|
|
|
|
}
|
|
|
|
// Tests on verifying the rebundle flag and error code in Bundle.Status when rebundling.
|
|
func TestRebundleFromPEM(t *testing.T) {
|
|
newBundler := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, interL1, "")
|
|
newBundle, err := newBundler.BundleFromPEMorDER(expiredBundlePEM, nil, Optimal, "")
|
|
if err != nil {
|
|
t.Fatalf("Re-bundle failed. %s", err.Error())
|
|
}
|
|
newChain := newBundle.Chain
|
|
|
|
if len(newChain) != 2 {
|
|
t.Fatalf("Expected bundle chain length is 2. Got %d.", len(newChain))
|
|
}
|
|
|
|
expiredChain, _ := helpers.ParseCertificatesPEM(expiredBundlePEM)
|
|
for i, cert := range newChain {
|
|
old := expiredChain[i]
|
|
if i == 0 {
|
|
if !bytes.Equal(old.Signature, cert.Signature) {
|
|
t.Fatal("Leaf cert should be the same.")
|
|
}
|
|
} else {
|
|
if bytes.Equal(old.Signature, cert.Signature) {
|
|
t.Fatal("Intermediate cert should be different.")
|
|
}
|
|
}
|
|
}
|
|
// The status must be {Code: ExpiringBit is not set, IsRebundled:true, ExpiringSKIs:{}}
|
|
if len(newBundle.Status.ExpiringSKIs) != 0 || !newBundle.Status.IsRebundled || newBundle.Status.Code&errors.BundleExpiringBit != 0 {
|
|
t.Fatal("Rebundle Status is incorrect.")
|
|
}
|
|
|
|
}
|
|
|
|
// Test on verifying ubiquitous messaging in Bundle.Status.
|
|
func TestUbiquitousBundle(t *testing.T) {
|
|
L1Cert := readCert(interL1)
|
|
// Simulate the case that L1Cert is added to trust store by one platform but not yet in another.
|
|
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
|
|
b.RootPool.AddCert(L1Cert)
|
|
// Prepare Platforms.
|
|
platformA := ubiquity.Platform{Name: "MacroSoft", Weight: 100, HashAlgo: "SHA2", KeyAlgo: "ECDSA256", KeyStoreFile: testCFSSLRootBundle}
|
|
platformA.ParseAndLoad()
|
|
platformB := ubiquity.Platform{Name: "Godzilla", Weight: 100, HashAlgo: "SHA2", KeyAlgo: "ECDSA256", KeyStoreFile: testCFSSLRootBundle}
|
|
platformB.ParseAndLoad()
|
|
platformA.KeyStore.Add(L1Cert)
|
|
ubiquity.Platforms = []ubiquity.Platform{platformA, platformB}
|
|
|
|
// Optimal bundle algorithm will picks up the new root and shorten the chain.
|
|
optimalBundle, err := b.BundleFromFile(leafECDSA256, "", Optimal, "")
|
|
if err != nil {
|
|
t.Fatal("Optimal bundle failed:", err)
|
|
}
|
|
if len(optimalBundle.Chain) != 2 {
|
|
t.Fatal("Optimal bundle failed the chain length test. Chain length:", len(optimalBundle.Chain))
|
|
}
|
|
// The only trust platform is "Macrosoft".
|
|
if len(optimalBundle.Status.Untrusted) != 1 {
|
|
t.Fatal("Optimal bundle status has incorrect untrusted platforms", optimalBundle.Status.Untrusted)
|
|
}
|
|
checkUbiquityWarningAndCode(t, optimalBundle, true)
|
|
|
|
// Ubiquitous bundle will remain the same.
|
|
ubiquitousBundle, err := b.BundleFromFile(leafECDSA256, "", Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal("Ubiquitous bundle failed")
|
|
|
|
}
|
|
if len(ubiquitousBundle.Chain) != 3 {
|
|
t.Fatal("Ubiquitous bundle failed")
|
|
}
|
|
// Should be trusted by both platforms.
|
|
if len(ubiquitousBundle.Status.Untrusted) != 0 {
|
|
t.Fatal("Ubiquitous bundle status has incorrect untrusted platforms", len(ubiquitousBundle.Status.Untrusted))
|
|
}
|
|
checkUbiquityWarningAndCode(t, ubiquitousBundle, false)
|
|
}
|
|
|
|
func TestUbiquityBundleWithoutMetadata(t *testing.T) {
|
|
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
|
|
L1Cert := readCert(interL1)
|
|
b.RootPool.AddCert(L1Cert)
|
|
|
|
// Without platform info, ubiquitous bundling falls back to optimal bundling.
|
|
ubiquity.Platforms = nil
|
|
nuBundle, err := b.BundleFromFile(leafECDSA256, "", Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal("Ubiquitous-fall-back-to-optimal bundle failed: ", err)
|
|
|
|
}
|
|
if len(nuBundle.Chain) != 2 {
|
|
t.Fatal("Ubiquitous-fall-back-to-optimal bundle failed")
|
|
}
|
|
// Should be trusted by all (i.e. zero) platforms.
|
|
if len(nuBundle.Status.Untrusted) != 0 {
|
|
t.Fatal("Ubiquitous-fall-back-to-optimal bundle status has incorrect untrusted platforms", len(nuBundle.Status.Untrusted))
|
|
}
|
|
checkUbiquityWarningAndCode(t, nuBundle, true)
|
|
}
|
|
|
|
func checkUbiquityWarningAndCode(t *testing.T, bundle *Bundle, expected bool) {
|
|
found := false
|
|
for _, msg := range bundle.Status.Messages {
|
|
if strings.Contains(msg, untrustedWarningStub) || strings.Contains(msg, ubiquityWarning) {
|
|
found = true
|
|
}
|
|
}
|
|
if found != expected {
|
|
t.Fatal("Expected ubiquity warning: ", expected, " Found ubiquity warning:", found)
|
|
}
|
|
|
|
// check status code
|
|
if expected && bundle.Status.Code&errors.BundleNotUbiquitousBit == 0 {
|
|
t.Fatal("Bundle status doesn't set BundleNotUbiquitousBit :", bundle.Status.Code)
|
|
}
|
|
}
|
|
|
|
// Regression test on bundle with all flavors:
|
|
// Ubiquitous bundle optimizes bundle length given the platform ubiquity is the same; Force bundle
|
|
// with return the same bundle; Optimal bundle always chooses shortest bundle length.
|
|
func TestForceBundle(t *testing.T) {
|
|
// create a CA signer and signs a new intermediate with SHA-2
|
|
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
|
|
interL1Bytes := signCSRFile(caSigner, interL1CSR, t)
|
|
|
|
// create a inter L1 signer
|
|
interL1KeyBytes, err := ioutil.ReadFile(interL1Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
interL1Signer := makeCASigner(interL1Bytes, interL1KeyBytes, x509.SHA256WithRSA, t)
|
|
|
|
// sign a level 2 intermediate
|
|
interL2Bytes := signCSRFile(interL1Signer, interL2CSR, t)
|
|
|
|
// create a inter L2 signer
|
|
interL2KeyBytes, err := ioutil.ReadFile(interL2Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
interL2Signer := makeCASigner(interL2Bytes, interL2KeyBytes, x509.ECDSAWithSHA256, t)
|
|
|
|
// interL2 sign a leaf cert
|
|
leafBytes := signCSRFile(interL2Signer, leafCSR, t)
|
|
|
|
// create two platforms
|
|
// both trust the CA cert and L1 intermediate
|
|
caBytes, err := ioutil.ReadFile(testCAFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ca, _ := helpers.ParseCertificatePEM(caBytes)
|
|
interL1, _ := helpers.ParseCertificatePEM(interL1Bytes)
|
|
platformA := ubiquity.Platform{
|
|
Name: "A",
|
|
Weight: 100,
|
|
KeyStore: make(ubiquity.CertSet),
|
|
HashUbiquity: ubiquity.SHA2Ubiquity,
|
|
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
|
|
}
|
|
platformB := ubiquity.Platform{
|
|
Name: "B",
|
|
Weight: 100,
|
|
KeyStore: make(ubiquity.CertSet),
|
|
HashUbiquity: ubiquity.SHA2Ubiquity,
|
|
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
|
|
}
|
|
|
|
platformA.KeyStore.Add(ca)
|
|
platformA.KeyStore.Add(interL1)
|
|
platformB.KeyStore.Add(ca)
|
|
platformB.KeyStore.Add(interL1)
|
|
ubiquity.Platforms = []ubiquity.Platform{platformA, platformB}
|
|
|
|
caBundle := string(caBytes) + string(interL1Bytes)
|
|
interBundle := string(interL2Bytes) + string(interL1Bytes)
|
|
fullChain := string(leafBytes) + string(interL2Bytes) + string(interL1Bytes)
|
|
|
|
// create bundler
|
|
b, err := NewBundlerFromPEM([]byte(caBundle), []byte(interBundle))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The input PEM bundle is 3-cert chain.
|
|
bundle, err := b.BundleFromPEMorDER([]byte(fullChain), nil, Force, "")
|
|
if err != nil {
|
|
t.Fatal("Force bundle failed:", err)
|
|
}
|
|
if len(bundle.Chain) != 3 {
|
|
t.Fatal("Force bundle failed:")
|
|
}
|
|
if len(bundle.Status.Untrusted) != 0 {
|
|
t.Fatal("Force bundle failed:")
|
|
}
|
|
|
|
// With ubiquity flavor, we should have a shorter chain, given L1 is ubiquitous trusted.
|
|
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal("Ubiquitous bundle failed:", err)
|
|
}
|
|
if len(bundle.Chain) != 2 {
|
|
t.Fatal("Ubiquitous bundle failed:")
|
|
}
|
|
if len(bundle.Status.Untrusted) != 0 {
|
|
t.Fatal("Ubiquitous bundle failed:")
|
|
}
|
|
|
|
// With optimal flavor, we should have a shorter chain as well.
|
|
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Optimal, "")
|
|
if err != nil {
|
|
t.Fatal("Optimal bundle failed:", err)
|
|
}
|
|
if len(bundle.Chain) != 2 {
|
|
t.Fatal("Optimal bundle failed:")
|
|
}
|
|
if len(bundle.Status.Untrusted) != 0 {
|
|
t.Fatal("Optimal bundle failed:")
|
|
}
|
|
}
|
|
|
|
func TestUpdateIntermediate(t *testing.T) {
|
|
// create a CA signer and signs a new intermediate with SHA-2
|
|
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
|
|
sha2InterBytes := signCSRFile(caSigner, interL1CSR, t)
|
|
|
|
interKeyBytes, err := ioutil.ReadFile(interL1Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a intermediate signer from intermediate cert/key
|
|
sha2InterSigner := makeCASigner(sha2InterBytes, interKeyBytes, x509.SHA256WithRSA, t)
|
|
// sign a leaf cert
|
|
leafBytes := signCSRFile(sha2InterSigner, leafCSR, t)
|
|
|
|
// read CA cert bytes
|
|
caCertBytes, err := ioutil.ReadFile(testCAFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// create a bundler with the test root CA and no intermediates
|
|
b, err := NewBundlerFromPEM(caCertBytes, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a cert bundle: leaf + inter
|
|
chainBytes := string(leafBytes) + string(sha2InterBytes)
|
|
bundle, err := b.BundleFromPEMorDER([]byte(chainBytes), nil, Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal("Valid bundle should be accepted. error:", err)
|
|
}
|
|
if bundle.Status.IsRebundled {
|
|
t.Fatal("rebundle should never happen here", bundle.Status)
|
|
}
|
|
|
|
// Now bundle with the leaf cert
|
|
bundle2, err := b.BundleFromPEMorDER(leafBytes, nil, Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal("Valid bundle should be accepted. error:", err)
|
|
}
|
|
if !bundle2.Status.IsRebundled {
|
|
t.Fatal("rebundle should happen here")
|
|
}
|
|
}
|
|
|
|
func TestForceBundleNoFallback(t *testing.T) {
|
|
// create a CA signer and signs a new intermediate with SHA-2
|
|
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
|
|
sha2InterBytes := signCSRFile(caSigner, interL1CSR, t)
|
|
|
|
interKeyBytes, err := ioutil.ReadFile(interL1Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a intermediate signer from intermediate cert/key
|
|
sha2InterSigner := makeCASigner(sha2InterBytes, interKeyBytes, x509.SHA256WithRSA, t)
|
|
// sign a leaf cert
|
|
leafBytes := signCSRFile(sha2InterSigner, leafCSR, t)
|
|
|
|
// read CA cert bytes
|
|
caCertBytes, err := ioutil.ReadFile(testCAFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// create a bundler with the test root CA and the new intermediate
|
|
b, err := NewBundlerFromPEM(caCertBytes, sha2InterBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Now bundle with the leaf cert with Force
|
|
bundle, err := b.BundleFromPEMorDER(leafBytes, nil, Force, "")
|
|
if err != nil {
|
|
t.Fatal("Valid bundle should be generated, error:", err)
|
|
}
|
|
|
|
// Force bundle fallback to creating a valid bundle
|
|
if len(bundle.Chain) != 1 {
|
|
t.Fatal("incorrect bundling")
|
|
}
|
|
if bundle.Status.IsRebundled {
|
|
t.Fatal("rebundle should happen here")
|
|
}
|
|
|
|
}
|
|
|
|
// Regression test: ubiquity bundle test with SHA2-homogeneous preference should not override root ubiquity.
|
|
func TestSHA2HomogeneityAgainstUbiquity(t *testing.T) {
|
|
// create a CA signer and signs a new intermediate with SHA-1
|
|
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA1WithRSA, t)
|
|
interL1Bytes := signCSRFile(caSigner, interL1CSR, t)
|
|
|
|
// create a inter L1 signer
|
|
interL1KeyBytes, err := ioutil.ReadFile(interL1Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
interL1Signer := makeCASigner(interL1Bytes, interL1KeyBytes, x509.SHA256WithRSA, t)
|
|
|
|
// sign a level 2 intermediate
|
|
interL2Bytes := signCSRFile(interL1Signer, interL2CSR, t)
|
|
|
|
// create a inter L2 signer
|
|
interL2KeyBytes, err := ioutil.ReadFile(interL2Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
interL2Signer := makeCASigner(interL2Bytes, interL2KeyBytes, x509.ECDSAWithSHA256, t)
|
|
|
|
// interL2 sign a leaf cert
|
|
leafBytes := signCSRFile(interL2Signer, leafCSR, t)
|
|
|
|
// create two platforms
|
|
// platform A trusts the CA cert and L1 intermediate
|
|
// platform B trusts the CA cert
|
|
caBytes, err := ioutil.ReadFile(testCAFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ca, _ := helpers.ParseCertificatePEM(caBytes)
|
|
interL1, _ := helpers.ParseCertificatePEM(interL1Bytes)
|
|
platformA := ubiquity.Platform{
|
|
Name: "A",
|
|
Weight: 100,
|
|
KeyStore: make(ubiquity.CertSet),
|
|
HashUbiquity: ubiquity.SHA2Ubiquity,
|
|
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
|
|
}
|
|
platformB := ubiquity.Platform{
|
|
Name: "B",
|
|
Weight: 100,
|
|
KeyStore: make(ubiquity.CertSet),
|
|
HashUbiquity: ubiquity.SHA2Ubiquity,
|
|
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
|
|
}
|
|
|
|
platformA.KeyStore.Add(ca)
|
|
platformA.KeyStore.Add(interL1)
|
|
platformB.KeyStore.Add(ca)
|
|
ubiquity.Platforms = []ubiquity.Platform{platformA, platformB}
|
|
|
|
caBundle := string(caBytes) + string(interL1Bytes)
|
|
interBundle := string(interL2Bytes) + string(interL1Bytes)
|
|
fullChain := string(leafBytes) + string(interL2Bytes) + string(interL1Bytes)
|
|
|
|
// create bundler
|
|
b, err := NewBundlerFromPEM([]byte(caBundle), []byte(interBundle))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The input PEM bundle is 3-cert chain.
|
|
bundle, err := b.BundleFromPEMorDER([]byte(fullChain), nil, Force, "")
|
|
if err != nil {
|
|
t.Fatal("Force bundle failed:", err)
|
|
}
|
|
if len(bundle.Chain) != 3 {
|
|
t.Fatal("Force bundle failed:")
|
|
}
|
|
if len(bundle.Status.Untrusted) != 0 {
|
|
t.Fatal("Force bundle failed:")
|
|
}
|
|
|
|
// With ubiquity flavor, we should not sacrifice trust store ubiquity and rebundle with a shorter chain
|
|
// with SHA2 homogenity.
|
|
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal("Ubiquitous bundle failed:", err)
|
|
}
|
|
if len(bundle.Chain) != 3 {
|
|
t.Fatal("Ubiquitous bundle failed:")
|
|
}
|
|
if len(bundle.Status.Untrusted) != 0 {
|
|
t.Fatal("Ubiquitous bundle failed:")
|
|
}
|
|
|
|
// With optimal flavor, we should have a shorter chain.
|
|
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Optimal, "")
|
|
if err != nil {
|
|
t.Fatal("Optimal bundle failed:", err)
|
|
}
|
|
if len(bundle.Chain) != 2 {
|
|
t.Fatal("Optimal bundle failed:")
|
|
}
|
|
if len(bundle.Status.Untrusted) == 0 {
|
|
t.Fatal("Optimal bundle failed:")
|
|
}
|
|
|
|
}
|
|
|
|
func checkSHA2WarningAndCode(t *testing.T, bundle *Bundle, expected bool) {
|
|
found := false
|
|
for _, msg := range bundle.Status.Messages {
|
|
if strings.Contains(msg, sha2Warning) {
|
|
found = true
|
|
}
|
|
}
|
|
if found != expected {
|
|
t.Fatal("Expected ubiquity warning: ", expected, " Found ubiquity warning:", found)
|
|
}
|
|
// check status code
|
|
if bundle.Status.Code&errors.BundleNotUbiquitousBit == 0 {
|
|
t.Fatal("Bundle status code is incorrect:", bundle.Status.Code)
|
|
}
|
|
}
|
|
|
|
func checkECDSAWarningAndCode(t *testing.T, bundle *Bundle, expected bool) {
|
|
found := false
|
|
for _, msg := range bundle.Status.Messages {
|
|
if strings.Contains(msg, ecdsaWarning) {
|
|
found = true
|
|
}
|
|
}
|
|
if found != expected {
|
|
t.Fatal("Expected ubiquity warning: ", expected, " Found ubiquity warning:", found)
|
|
}
|
|
// check status code
|
|
if bundle.Status.Code&errors.BundleNotUbiquitousBit == 0 {
|
|
t.Fatal("Bundle status code is incorrect:", bundle.Status.Code)
|
|
}
|
|
}
|
|
|
|
// Regression test on SHA-2 Warning
|
|
// Riot Games once bundle a cert issued by DigiCert SHA2 High Assurance Server CA. The resulting
|
|
// bundle uses SHA-256 which is not supported in Windows XP SP2. We should present a warning
|
|
// on this.
|
|
func TestSHA2Warning(t *testing.T) {
|
|
// create a CA signer and signs a new intermediate with SHA-2
|
|
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
|
|
sha2InterBytes := signCSRFile(caSigner, interL1CSR, t)
|
|
|
|
// read CA cert bytes
|
|
caCertBytes, err := ioutil.ReadFile(testCAFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a bundler with the test root CA and no intermediates
|
|
b, err := NewBundlerFromPEM(caCertBytes, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
optimalBundle, err := b.BundleFromPEMorDER(sha2InterBytes, nil, Optimal, "")
|
|
if err != nil {
|
|
t.Fatal("Optimal bundle failed:", err)
|
|
}
|
|
checkSHA2WarningAndCode(t, optimalBundle, true)
|
|
|
|
// Ubiquitous bundle will include a 2nd intermediate CA.
|
|
ubiquitousBundle, err := b.BundleFromPEMorDER(sha2InterBytes, nil, Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal("Ubiquitous bundle failed")
|
|
|
|
}
|
|
checkSHA2WarningAndCode(t, ubiquitousBundle, true)
|
|
}
|
|
|
|
// Regression test on ECDSA Warning
|
|
// A test bundle that contains ECDSA384 and SHA-2. Expect ECDSA warning and SHA-2 warning.
|
|
func TestECDSAWarning(t *testing.T) {
|
|
b := newCustomizedBundlerFromFile(t, testCAFile, interL1SHA1, "")
|
|
|
|
optimalBundle, err := b.BundleFromFile(interL2SHA2, "", Optimal, "")
|
|
if err != nil {
|
|
t.Fatal("Optimal bundle failed:", err)
|
|
}
|
|
|
|
checkSHA2WarningAndCode(t, optimalBundle, true)
|
|
checkECDSAWarningAndCode(t, optimalBundle, true)
|
|
}
|
|
|
|
// === Helper function block ===
|
|
|
|
// readCert read a PEM file and returns a cert.
|
|
func readCert(filename string) *x509.Certificate {
|
|
bytes, _ := ioutil.ReadFile(filename)
|
|
cert, _ := helpers.ParseCertificatePEM(bytes)
|
|
return cert
|
|
}
|
|
|
|
// newBundler is a helper function that returns a new Bundler. If it fails to do so,
|
|
// it fails the test suite immediately.
|
|
func newBundler(t *testing.T) (b *Bundler) {
|
|
b, err := NewBundler(testCaBundle, testIntCaBundle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// newBundler creates bundler from byte slices of CA certs and intermediate certs in PEM format
|
|
func newBundlerFromPEM(t *testing.T, caBundlePEM, intBundlePEM []byte) (b *Bundler) {
|
|
b, err := NewBundlerFromPEM(caBundlePEM, intBundlePEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// newCustomizedBundleCreator is a helper function that returns a new Bundler
|
|
// takes specified CA bundle, intermediate bundle, and any additional intermdiate certs to generate a bundler.
|
|
func newCustomizedBundlerFromFile(t *testing.T, caBundle, intBundle, adhocInters string) (b *Bundler) {
|
|
b, err := NewBundler(caBundle, intBundle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if adhocInters != "" {
|
|
moreIntersPEM, err := ioutil.ReadFile(adhocInters)
|
|
if err != nil {
|
|
t.Fatalf("Read additional intermediates failed. %v",
|
|
err)
|
|
}
|
|
intermediates, err := helpers.ParseCertificatesPEM(moreIntersPEM)
|
|
if err != nil {
|
|
t.Fatalf("Parsing additional intermediates failed. %s", err.Error())
|
|
}
|
|
for _, c := range intermediates {
|
|
b.IntermediatePool.AddCert(c)
|
|
}
|
|
|
|
}
|
|
return
|
|
|
|
}
|
|
|
|
// newBundlerWithoutInters is a helper function that returns a bundler with an empty
|
|
// intermediate cert pool. Such bundlers can help testing error handling in cert
|
|
// bundling.
|
|
func newBundlerWithoutInters(t *testing.T) (b *Bundler) {
|
|
b = newBundler(t)
|
|
// Re-assign an empty intermediate cert pool
|
|
b.IntermediatePool = x509.NewCertPool()
|
|
return
|
|
}
|
|
|
|
// newBundlerWithoutRoots is a helper function that returns a bundler with an empty
|
|
// root cert pool. Such bundlers can help testing error handling in cert
|
|
// bundling.
|
|
func newBundlerWithoutRoots(t *testing.T) (b *Bundler) {
|
|
b = newBundler(t)
|
|
// Re-assign an empty root cert pool
|
|
b.RootPool = x509.NewCertPool()
|
|
return
|
|
}
|
|
|
|
func newBundlerWithoutRootsAndInters(t *testing.T) *Bundler {
|
|
b, err := NewBundler("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// A helper function that returns a errorCallback function which expects certain error content in
|
|
// an error message.
|
|
func ExpectErrorMessage(expectedErrorContent string) func(*testing.T, error) {
|
|
return func(t *testing.T, err error) {
|
|
if err == nil {
|
|
t.Fatalf("Expected error has %s. Got nothing.", expectedErrorContent)
|
|
} else if !strings.Contains(err.Error(), expectedErrorContent) {
|
|
t.Fatalf("Expected error has %s. Got %s", expectedErrorContent, err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
// A helper function that returns a errorCallback function which inspect error message for
|
|
// all expected messages.
|
|
func ExpectErrorMessages(expectedContents []string) func(*testing.T, error) {
|
|
return func(t *testing.T, err error) {
|
|
if err == nil {
|
|
t.Fatalf("Expected error has %s. Got nothing.", expectedContents)
|
|
} else {
|
|
for _, expected := range expectedContents {
|
|
if !strings.Contains(err.Error(), expected) {
|
|
t.Fatalf("Expected error has %s. Got %s", expected, err.Error())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A helper function that returns a bundle chain length checking function
|
|
func ExpectBundleLength(expectedLen int) func(*testing.T, *Bundle) {
|
|
return func(t *testing.T, bundle *Bundle) {
|
|
if bundle == nil {
|
|
t.Fatalf("Cert bundle should have a chain of length %d. Got nil.",
|
|
expectedLen)
|
|
} else if len(bundle.Chain) != expectedLen {
|
|
t.Fatalf("Cert bundle should have a chain of length %d. Got chain length %d.",
|
|
expectedLen, len(bundle.Chain))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBundlerWithEmptyRootInfo(t *testing.T) {
|
|
b := newBundlerWithoutRootsAndInters(t)
|
|
|
|
// "force" bundle should be ok
|
|
bundle, err := b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Force, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
checkBundleFunc := ExpectBundleLength(1)
|
|
checkBundleFunc(t, bundle)
|
|
|
|
// force non-verifying bundle should fail.
|
|
_, err = b.BundleFromFile(badBundle, "", Force, "")
|
|
if err == nil {
|
|
t.Fatal("expected error. but no error occurred")
|
|
}
|
|
checkErrorFunc := ExpectErrorMessage("\"code\":1200")
|
|
checkErrorFunc(t, err)
|
|
|
|
// "optimal" and "ubiquitous" bundle should be ok
|
|
bundle, err = b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Ubiquitous, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
checkBundleFunc = ExpectBundleLength(1)
|
|
checkBundleFunc(t, bundle)
|
|
|
|
bundle, err = b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Optimal, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
checkBundleFunc = ExpectBundleLength(1)
|
|
checkBundleFunc(t, bundle)
|
|
|
|
// bundle remote should be ok
|
|
bundle, err = b.BundleFromRemote("www.google.com", "", Ubiquitous)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
checkBundleFunc = ExpectBundleLength(2)
|
|
checkBundleFunc(t, bundle)
|
|
}
|
|
|
|
func TestBundlerClientAuth(t *testing.T) {
|
|
b, err := NewBundler("testdata/client-auth/root.pem", "testdata/client-auth/int.pem")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, leafFile := range []string{"testdata/client-auth/leaf-server.pem", "testdata/client-auth/leaf-client.pem"} {
|
|
if _, err := b.BundleFromFile(leafFile, "", Optimal, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|