package bundler

// This test file contains mostly tests on checking Bundle.Status when bundling under different circumstances.
import (
	"bytes"
	"crypto/x509"
	"encoding/json"
	"os"
	"strings"
	"testing"

	"github.com/cloudflare/cfssl/errors"
	"github.com/cloudflare/cfssl/helpers"
	"github.com/cloudflare/cfssl/ubiquity"
)

const (
	// from https://github.com/cloudflare/cfssl_trust/blob/master/ca-bundle.crt
	testCaBundle = "testdata/ca-bundle.pem"
	// from https://github.com/cloudflare/cfssl_trust/blob/master/int-bundle.crt
	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, _ := os.ReadFile(leafKeyECDSA256)
	keyBytes = bytes.Trim(keyBytes, " \n")
	if key != string(keyBytes) {
		t.Fatal("key is not recovered.")
	}

	cert := obj["crt"].(string)
	certBytes, _ := os.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, _ := os.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, _ := os.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) {
	t.Skip("expired cert https://github.com/cloudflare/cfssl/issues/1237")
	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 := os.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 := os.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 := os.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 := os.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 := os.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 := os.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 := os.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 := os.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 := os.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 := os.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 := os.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, _ := os.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 intermediate 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 := os.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) {
	t.Skip("broken relating to https://github.com/cloudflare/cfssl/issues/1230")
	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)
		}
	}
}
