crypto/x509: respect VerifyOptions.KeyUsages on Windows

When using the platform verifier on Windows (because Roots is nil) we
were always enforcing server auth EKUs if DNSName was set, and none
otherwise. If an application was setting KeyUsages, they were not being
respected.

Started correctly surfacing IncompatibleUsage errors from the system
verifier, as those are the ones applications will see if they are
affected by this change.

Also refactored verify_test.go to make it easier to add tests for this,
and replaced the EKULeaf chain with a new one that doesn't have a SHA-1
signature.

Thanks to Niall Newman for reporting this.

Fixes #39360
Fixes CVE-2020-14039

Change-Id: If5c00d615f2944f7d57007891aae1307f9571c32
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/774414
Reviewed-by: Katie Hockman <katiehockman@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/242597
Run-TryBot: Katie Hockman <katie@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/src/crypto/x509/root_windows.go b/src/crypto/x509/root_windows.go
index 34d5853..1e0f3ac 100644
--- a/src/crypto/x509/root_windows.go
+++ b/src/crypto/x509/root_windows.go
@@ -88,6 +88,9 @@
 		switch status {
 		case syscall.CERT_TRUST_IS_NOT_TIME_VALID:
 			return CertificateInvalidError{c, Expired, ""}
+		case syscall.CERT_TRUST_IS_NOT_VALID_FOR_USAGE:
+			return CertificateInvalidError{c, IncompatibleUsage, ""}
+		// TODO(filippo): surface more error statuses.
 		default:
 			return UnknownAuthorityError{c, nil, nil}
 		}
@@ -138,11 +141,19 @@
 	return nil
 }
 
+// windowsExtKeyUsageOIDs are the C NUL-terminated string representations of the
+// OIDs for use with the Windows API.
+var windowsExtKeyUsageOIDs = make(map[ExtKeyUsage][]byte, len(extKeyUsageOIDs))
+
+func init() {
+	for _, eku := range extKeyUsageOIDs {
+		windowsExtKeyUsageOIDs[eku.extKeyUsage] = []byte(eku.oid.String() + "\x00")
+	}
+}
+
 // systemVerify is like Verify, except that it uses CryptoAPI calls
 // to build certificate chains and verify them.
 func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
-	hasDNSName := opts != nil && len(opts.DNSName) > 0
-
 	storeCtx, err := createStoreContext(c, opts)
 	if err != nil {
 		return nil, err
@@ -152,17 +163,26 @@
 	para := new(syscall.CertChainPara)
 	para.Size = uint32(unsafe.Sizeof(*para))
 
-	// If there's a DNSName set in opts, assume we're verifying
-	// a certificate from a TLS server.
-	if hasDNSName {
-		oids := []*byte{
-			&syscall.OID_PKIX_KP_SERVER_AUTH[0],
-			// Both IE and Chrome allow certificates with
-			// Server Gated Crypto as well. Some certificates
-			// in the wild require them.
-			&syscall.OID_SERVER_GATED_CRYPTO[0],
-			&syscall.OID_SGC_NETSCAPE[0],
+	keyUsages := opts.KeyUsages
+	if len(keyUsages) == 0 {
+		keyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth}
+	}
+	oids := make([]*byte, 0, len(keyUsages))
+	for _, eku := range keyUsages {
+		if eku == ExtKeyUsageAny {
+			oids = nil
+			break
 		}
+		if oid, ok := windowsExtKeyUsageOIDs[eku]; ok {
+			oids = append(oids, &oid[0])
+		}
+		// Like the standard verifier, accept SGC EKUs as equivalent to ServerAuth.
+		if eku == ExtKeyUsageServerAuth {
+			oids = append(oids, &syscall.OID_SERVER_GATED_CRYPTO[0])
+			oids = append(oids, &syscall.OID_SGC_NETSCAPE[0])
+		}
+	}
+	if oids != nil {
 		para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_OR
 		para.RequestedUsage.Usage.Length = uint32(len(oids))
 		para.RequestedUsage.Usage.UsageIdentifiers = &oids[0]
@@ -208,7 +228,7 @@
 		return nil, err
 	}
 
-	if hasDNSName {
+	if opts != nil && len(opts.DNSName) > 0 {
 		err = checkChainSSLServerPolicy(c, chainCtx, opts)
 		if err != nil {
 			return nil, err
diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go
index be11e730..cb8d8f8 100644
--- a/src/crypto/x509/verify.go
+++ b/src/crypto/x509/verify.go
@@ -194,7 +194,7 @@
 // VerifyOptions contains parameters for Certificate.Verify.
 type VerifyOptions struct {
 	// DNSName, if set, is checked against the leaf certificate with
-	// Certificate.VerifyHostname.
+	// Certificate.VerifyHostname or the platform verifier.
 	DNSName string
 
 	// Intermediates is an optional pool of certificates that are not trust
@@ -209,20 +209,16 @@
 	// chain. If zero, the current time is used.
 	CurrentTime time.Time
 
-	// KeyUsage specifies which Extended Key Usage values are acceptable. A leaf
-	// certificate is accepted if it contains any of the listed values. An empty
-	// list means ExtKeyUsageServerAuth. To accept any key usage, include
-	// ExtKeyUsageAny.
-	//
-	// Certificate chains are required to nest these extended key usage values.
-	// (This matches the Windows CryptoAPI behavior, but not the spec.)
+	// KeyUsages specifies which Extended Key Usage values are acceptable. A
+	// chain is accepted if it allows any of the listed values. An empty list
+	// means ExtKeyUsageServerAuth. To accept any key usage, include ExtKeyUsageAny.
 	KeyUsages []ExtKeyUsage
 
 	// MaxConstraintComparisions is the maximum number of comparisons to
 	// perform when checking a given certificate's name constraints. If
 	// zero, a sensible default is used. This limit prevents pathological
 	// certificates from consuming excessive amounts of CPU time when
-	// validating.
+	// validating. It does not apply to the platform verifier.
 	MaxConstraintComparisions int
 }
 
@@ -735,8 +731,9 @@
 // needed. If successful, it returns one or more chains where the first
 // element of the chain is c and the last element is from opts.Roots.
 //
-// If opts.Roots is nil and system roots are unavailable the returned error
-// will be of type SystemRootsError.
+// If opts.Roots is nil, the platform verifier might be used, and
+// verification details might differ from what is described below. If system
+// roots are unavailable the returned error will be of type SystemRootsError.
 //
 // Name constraints in the intermediates will be applied to all names claimed
 // in the chain, not just opts.DNSName. Thus it is invalid for a leaf to claim
@@ -750,9 +747,10 @@
 // it indicates that at least one additional label must be prepended to
 // the constrained name to be considered valid.
 //
-// Extended Key Usage values are enforced down a chain, so an intermediate or
-// root that enumerates EKUs prevents a leaf from asserting an EKU not in that
-// list.
+// Extended Key Usage values are enforced nested down a chain, so an intermediate
+// or root that enumerates EKUs prevents a leaf from asserting an EKU not in that
+// list. (While this is not specified, it is common practice in order to limit
+// the types of certificates a CA can issue.)
 //
 // WARNING: this function doesn't do any revocation checking.
 func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {
diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go
index 650b2d2..76d1ab9 100644
--- a/src/crypto/x509/verify_test.go
+++ b/src/crypto/x509/verify_test.go
@@ -21,34 +21,24 @@
 )
 
 type verifyTest struct {
-	leaf                 string
-	intermediates        []string
-	roots                []string
-	currentTime          int64
-	dnsName              string
-	systemSkip           bool
-	keyUsages            []ExtKeyUsage
-	testSystemRootsError bool
-	sha2                 bool
-	ignoreCN             bool
+	name          string
+	leaf          string
+	intermediates []string
+	roots         []string
+	currentTime   int64
+	dnsName       string
+	systemSkip    bool
+	systemLax     bool
+	keyUsages     []ExtKeyUsage
+	ignoreCN      bool
 
-	errorCallback  func(*testing.T, int, error) bool
+	errorCallback  func(*testing.T, error)
 	expectedChains [][]string
 }
 
 var verifyTests = []verifyTest{
 	{
-		leaf:                 googleLeaf,
-		intermediates:        []string{giag2Intermediate},
-		currentTime:          1395785200,
-		dnsName:              "www.google.com",
-		testSystemRootsError: true,
-
-		// Without any roots specified we should get a system roots
-		// error.
-		errorCallback: expectSystemRootsError,
-	},
-	{
+		name:          "Valid",
 		leaf:          googleLeaf,
 		intermediates: []string{giag2Intermediate},
 		roots:         []string{geoTrustRoot},
@@ -60,6 +50,7 @@
 		},
 	},
 	{
+		name:          "MixedCase",
 		leaf:          googleLeaf,
 		intermediates: []string{giag2Intermediate},
 		roots:         []string{geoTrustRoot},
@@ -71,6 +62,7 @@
 		},
 	},
 	{
+		name:          "HostnameMismatch",
 		leaf:          googleLeaf,
 		intermediates: []string{giag2Intermediate},
 		roots:         []string{geoTrustRoot},
@@ -80,6 +72,7 @@
 		errorCallback: expectHostnameError("certificate is valid for"),
 	},
 	{
+		name:          "IPMissing",
 		leaf:          googleLeaf,
 		intermediates: []string{giag2Intermediate},
 		roots:         []string{geoTrustRoot},
@@ -89,6 +82,7 @@
 		errorCallback: expectHostnameError("doesn't contain any IP SANs"),
 	},
 	{
+		name:          "Expired",
 		leaf:          googleLeaf,
 		intermediates: []string{giag2Intermediate},
 		roots:         []string{geoTrustRoot},
@@ -98,6 +92,7 @@
 		errorCallback: expectExpired,
 	},
 	{
+		name:        "MissingIntermediate",
 		leaf:        googleLeaf,
 		roots:       []string{geoTrustRoot},
 		currentTime: 1395785200,
@@ -109,6 +104,7 @@
 		errorCallback: expectAuthorityUnknown,
 	},
 	{
+		name:          "RootInIntermediates",
 		leaf:          googleLeaf,
 		intermediates: []string{geoTrustRoot, giag2Intermediate},
 		roots:         []string{geoTrustRoot},
@@ -119,31 +115,50 @@
 			{"Google", "Google Internet Authority", "GeoTrust"},
 		},
 		// CAPI doesn't build the chain with the duplicated GeoTrust
-		// entry so the results don't match. Thus we skip this test
-		// until that's fixed.
-		systemSkip: true,
+		// entry so the results don't match.
+		systemLax: true,
 	},
 	{
+		name:          "dnssec-exp",
 		leaf:          dnssecExpLeaf,
 		intermediates: []string{startComIntermediate},
 		roots:         []string{startComRoot},
 		currentTime:   1302726541,
 
-		expectedChains: [][]string{
-			{"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority"},
-		},
-	},
-	{
-		leaf:          dnssecExpLeaf,
-		intermediates: []string{startComIntermediate, startComRoot},
-		roots:         []string{startComRoot},
-		currentTime:   1302726541,
+		// The StartCom root is not trusted by Windows when the default
+		// ServerAuth EKU is requested.
+		systemSkip: true,
 
 		expectedChains: [][]string{
 			{"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority"},
 		},
 	},
 	{
+		name:          "dnssec-exp/AnyEKU",
+		leaf:          dnssecExpLeaf,
+		intermediates: []string{startComIntermediate},
+		roots:         []string{startComRoot},
+		currentTime:   1302726541,
+		keyUsages:     []ExtKeyUsage{ExtKeyUsageAny},
+
+		expectedChains: [][]string{
+			{"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority"},
+		},
+	},
+	{
+		name:          "dnssec-exp/RootInIntermediates",
+		leaf:          dnssecExpLeaf,
+		intermediates: []string{startComIntermediate, startComRoot},
+		roots:         []string{startComRoot},
+		currentTime:   1302726541,
+		systemSkip:    true, // see dnssec-exp test
+
+		expectedChains: [][]string{
+			{"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority"},
+		},
+	},
+	{
+		name:          "InvalidHash",
 		leaf:          googleLeafWithInvalidHash,
 		intermediates: []string{giag2Intermediate},
 		roots:         []string{geoTrustRoot},
@@ -152,50 +167,52 @@
 
 		// The specific error message may not occur when using system
 		// verification.
-		systemSkip:    true,
+		systemLax:     true,
 		errorCallback: expectHashError,
 	},
+	// EKULeaf tests use an unconstrained chain leading to a leaf certificate
+	// with an E-mail Protection EKU but not a Server Auth one, checking that
+	// the EKUs on the leaf are enforced.
 	{
-		// The default configuration should reject an S/MIME chain.
-		leaf:        smimeLeaf,
-		roots:       []string{smimeIntermediate},
-		currentTime: 1339436154,
+		name:          "EKULeaf",
+		leaf:          smimeLeaf,
+		intermediates: []string{smimeIntermediate},
+		roots:         []string{smimeRoot},
+		currentTime:   1594673418,
 
-		// Key usage not implemented for Windows yet.
-		systemSkip:    true,
 		errorCallback: expectUsageError,
 	},
 	{
-		leaf:        smimeLeaf,
-		roots:       []string{smimeIntermediate},
-		currentTime: 1339436154,
-		keyUsages:   []ExtKeyUsage{ExtKeyUsageServerAuth},
+		name:          "EKULeafExplicit",
+		leaf:          smimeLeaf,
+		intermediates: []string{smimeIntermediate},
+		roots:         []string{smimeRoot},
+		currentTime:   1594673418,
+		keyUsages:     []ExtKeyUsage{ExtKeyUsageServerAuth},
 
-		// Key usage not implemented for Windows yet.
-		systemSkip:    true,
 		errorCallback: expectUsageError,
 	},
 	{
-		leaf:        smimeLeaf,
-		roots:       []string{smimeIntermediate},
-		currentTime: 1339436154,
-		keyUsages:   []ExtKeyUsage{ExtKeyUsageEmailProtection},
+		name:          "EKULeafValid",
+		leaf:          smimeLeaf,
+		intermediates: []string{smimeIntermediate},
+		roots:         []string{smimeRoot},
+		currentTime:   1594673418,
+		keyUsages:     []ExtKeyUsage{ExtKeyUsageEmailProtection},
 
-		// Key usage not implemented for Windows yet.
-		systemSkip: true,
 		expectedChains: [][]string{
-			{"Ryan Hurst", "GlobalSign PersonalSign 2 CA - G2"},
+			{"CORPORATIVO FICTICIO ACTIVO", "EAEko Herri Administrazioen CA - CA AAPP Vascas (2)", "IZENPE S.A."},
 		},
 	},
 	{
+		name:          "SGCIntermediate",
 		leaf:          megaLeaf,
 		intermediates: []string{comodoIntermediate1},
 		roots:         []string{comodoRoot},
 		currentTime:   1360431182,
 
-		// CryptoAPI can find alternative validation paths so we don't
-		// perform this test with system validation.
-		systemSkip: true,
+		// CryptoAPI can find alternative validation paths.
+		systemLax: true,
 		expectedChains: [][]string{
 			{"mega.co.nz", "EssentialSSL CA", "COMODO Certification Authority"},
 		},
@@ -203,6 +220,7 @@
 	{
 		// Check that a name constrained intermediate works even when
 		// it lists multiple constraints.
+		name:          "MultipleConstraints",
 		leaf:          nameConstraintsLeaf,
 		intermediates: []string{nameConstraintsIntermediate1, nameConstraintsIntermediate2},
 		roots:         []string{globalSignRoot},
@@ -221,17 +239,16 @@
 	{
 		// Check that SHA-384 intermediates (which are popping up)
 		// work.
+		name:          "SHA-384",
 		leaf:          moipLeafCert,
 		intermediates: []string{comodoIntermediateSHA384, comodoRSAAuthority},
 		roots:         []string{addTrustRoot},
 		currentTime:   1397502195,
 		dnsName:       "api.moip.com.br",
 
-		// CryptoAPI can find alternative validation paths so we don't
-		// perform this test with system validation.
-		systemSkip: true,
+		// CryptoAPI can find alternative validation paths.
+		systemLax: true,
 
-		sha2: true,
 		expectedChains: [][]string{
 			{
 				"api.moip.com.br",
@@ -244,11 +261,12 @@
 	{
 		// Putting a certificate as a root directly should work as a
 		// way of saying “exactly this”.
+		name:        "LeafInRoots",
 		leaf:        selfSigned,
 		roots:       []string{selfSigned},
 		currentTime: 1471624472,
 		dnsName:     "foo.example",
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 
 		expectedChains: [][]string{
 			{"Acme Co"},
@@ -257,11 +275,12 @@
 	{
 		// Putting a certificate as a root directly should not skip
 		// other checks however.
+		name:        "LeafInRootsInvalid",
 		leaf:        selfSigned,
 		roots:       []string{selfSigned},
 		currentTime: 1471624472,
 		dnsName:     "notfoo.example",
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 
 		errorCallback: expectHostnameError("certificate is valid for"),
 	},
@@ -269,87 +288,95 @@
 		// The issuer name in the leaf doesn't exactly match the
 		// subject name in the root. Go does not perform
 		// canonicalization and so should reject this. See issue 14955.
+		name:        "IssuerSubjectMismatch",
 		leaf:        issuerSubjectMatchLeaf,
 		roots:       []string{issuerSubjectMatchRoot},
 		currentTime: 1475787715,
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 
 		errorCallback: expectSubjectIssuerMismatcthError,
 	},
 	{
 		// An X.509 v1 certificate should not be accepted as an
 		// intermediate.
+		name:          "X509v1Intermediate",
 		leaf:          x509v1TestLeaf,
 		intermediates: []string{x509v1TestIntermediate},
 		roots:         []string{x509v1TestRoot},
 		currentTime:   1481753183,
-		systemSkip:    true,
+		systemSkip:    true, // does not chain to a system root
 
 		errorCallback: expectNotAuthorizedError,
 	},
 	{
 		// If any SAN extension is present (even one without any DNS
 		// names), the CN should be ignored.
+		name:        "IgnoreCNWithSANs",
 		leaf:        ignoreCNWithSANLeaf,
 		dnsName:     "foo.example.com",
 		roots:       []string{ignoreCNWithSANRoot},
 		currentTime: 1486684488,
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 
 		errorCallback: expectHostnameError("certificate is not valid for any names"),
 	},
 	{
 		// Test that excluded names are respected.
+		name:          "ExcludedNames",
 		leaf:          excludedNamesLeaf,
 		dnsName:       "bender.local",
 		intermediates: []string{excludedNamesIntermediate},
 		roots:         []string{excludedNamesRoot},
 		currentTime:   1486684488,
-		systemSkip:    true,
+		systemSkip:    true, // does not chain to a system root
 
 		errorCallback: expectNameConstraintsError,
 	},
 	{
 		// Test that unknown critical extensions in a leaf cause a
 		// verify error.
+		name:          "CriticalExtLeaf",
 		leaf:          criticalExtLeafWithExt,
 		dnsName:       "example.com",
 		intermediates: []string{criticalExtIntermediate},
 		roots:         []string{criticalExtRoot},
 		currentTime:   1486684488,
-		systemSkip:    true,
+		systemSkip:    true, // does not chain to a system root
 
 		errorCallback: expectUnhandledCriticalExtension,
 	},
 	{
 		// Test that unknown critical extensions in an intermediate
 		// cause a verify error.
+		name:          "CriticalExtIntermediate",
 		leaf:          criticalExtLeaf,
 		dnsName:       "example.com",
 		intermediates: []string{criticalExtIntermediateWithExt},
 		roots:         []string{criticalExtRoot},
 		currentTime:   1486684488,
-		systemSkip:    true,
+		systemSkip:    true, // does not chain to a system root
 
 		errorCallback: expectUnhandledCriticalExtension,
 	},
 	{
 		// Test that invalid CN are ignored.
+		name:        "InvalidCN",
 		leaf:        invalidCNWithoutSAN,
 		dnsName:     "foo,invalid",
 		roots:       []string{invalidCNRoot},
 		currentTime: 1540000000,
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 
 		errorCallback: expectHostnameError("Common Name is not a valid hostname"),
 	},
 	{
 		// Test that valid CN are respected.
+		name:        "ValidCN",
 		leaf:        validCNWithoutSAN,
 		dnsName:     "foo.example.com",
 		roots:       []string{invalidCNRoot},
 		currentTime: 1540000000,
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 
 		expectedChains: [][]string{
 			{"foo.example.com", "Test root"},
@@ -357,31 +384,34 @@
 	},
 	// Replicate CN tests with ignoreCN = true
 	{
+		name:        "IgnoreCNWithSANs/ignoreCN",
 		leaf:        ignoreCNWithSANLeaf,
 		dnsName:     "foo.example.com",
 		roots:       []string{ignoreCNWithSANRoot},
 		currentTime: 1486684488,
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 		ignoreCN:    true,
 
 		errorCallback: expectHostnameError("certificate is not valid for any names"),
 	},
 	{
+		name:        "InvalidCN/ignoreCN",
 		leaf:        invalidCNWithoutSAN,
 		dnsName:     "foo,invalid",
 		roots:       []string{invalidCNRoot},
 		currentTime: 1540000000,
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 		ignoreCN:    true,
 
-		errorCallback: expectHostnameError("not valid for any names"),
+		errorCallback: expectHostnameError("certificate is not valid for any names"),
 	},
 	{
+		name:        "ValidCN/ignoreCN",
 		leaf:        validCNWithoutSAN,
 		dnsName:     "foo.example.com",
 		roots:       []string{invalidCNRoot},
 		currentTime: 1540000000,
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 		ignoreCN:    true,
 
 		errorCallback: expectHostnameError("certificate relies on legacy Common Name field"),
@@ -389,11 +419,12 @@
 	{
 		// A certificate with an AKID should still chain to a parent without SKID.
 		// See Issue 30079.
+		name:        "AKIDNoSKID",
 		leaf:        leafWithAKID,
 		roots:       []string{rootWithoutSKID},
 		currentTime: 1550000000,
 		dnsName:     "example",
-		systemSkip:  true,
+		systemSkip:  true, // does not chain to a system root
 
 		expectedChains: [][]string{
 			{"Acme LLC", "Acme Co"},
@@ -401,98 +432,70 @@
 	},
 }
 
-func expectHostnameError(msg string) func(*testing.T, int, error) bool {
-	return func(t *testing.T, i int, err error) (ok bool) {
+func expectHostnameError(msg string) func(*testing.T, error) {
+	return func(t *testing.T, err error) {
 		if _, ok := err.(HostnameError); !ok {
-			t.Errorf("#%d: error was not a HostnameError: %v", i, err)
-			return false
+			t.Fatalf("error was not a HostnameError: %v", err)
 		}
 		if !strings.Contains(err.Error(), msg) {
-			t.Errorf("#%d: HostnameError did not contain %q: %v", i, msg, err)
+			t.Fatalf("HostnameError did not contain %q: %v", msg, err)
 		}
-		return true
 	}
 }
 
-func expectExpired(t *testing.T, i int, err error) (ok bool) {
+func expectExpired(t *testing.T, err error) {
 	if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != Expired {
-		t.Errorf("#%d: error was not Expired: %v", i, err)
-		return false
+		t.Fatalf("error was not Expired: %v", err)
 	}
-	return true
 }
 
-func expectUsageError(t *testing.T, i int, err error) (ok bool) {
+func expectUsageError(t *testing.T, err error) {
 	if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != IncompatibleUsage {
-		t.Errorf("#%d: error was not IncompatibleUsage: %v", i, err)
-		return false
+		t.Fatalf("error was not IncompatibleUsage: %v", err)
 	}
-	return true
 }
 
-func expectAuthorityUnknown(t *testing.T, i int, err error) (ok bool) {
+func expectAuthorityUnknown(t *testing.T, err error) {
 	e, ok := err.(UnknownAuthorityError)
 	if !ok {
-		t.Errorf("#%d: error was not UnknownAuthorityError: %v", i, err)
-		return false
+		t.Fatalf("error was not UnknownAuthorityError: %v", err)
 	}
 	if e.Cert == nil {
-		t.Errorf("#%d: error was UnknownAuthorityError, but missing Cert: %v", i, err)
-		return false
+		t.Fatalf("error was UnknownAuthorityError, but missing Cert: %v", err)
 	}
-	return true
 }
 
-func expectSystemRootsError(t *testing.T, i int, err error) bool {
-	if _, ok := err.(SystemRootsError); !ok {
-		t.Errorf("#%d: error was not SystemRootsError: %v", i, err)
-		return false
-	}
-	return true
-}
-
-func expectHashError(t *testing.T, i int, err error) bool {
+func expectHashError(t *testing.T, err error) {
 	if err == nil {
-		t.Errorf("#%d: no error resulted from invalid hash", i)
-		return false
+		t.Fatalf("no error resulted from invalid hash")
 	}
 	if expected := "algorithm unimplemented"; !strings.Contains(err.Error(), expected) {
-		t.Errorf("#%d: error resulting from invalid hash didn't contain '%s', rather it was: %v", i, expected, err)
-		return false
+		t.Fatalf("error resulting from invalid hash didn't contain '%s', rather it was: %v", expected, err)
 	}
-	return true
 }
 
-func expectSubjectIssuerMismatcthError(t *testing.T, i int, err error) (ok bool) {
+func expectSubjectIssuerMismatcthError(t *testing.T, err error) {
 	if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != NameMismatch {
-		t.Errorf("#%d: error was not a NameMismatch: %v", i, err)
-		return false
+		t.Fatalf("error was not a NameMismatch: %v", err)
 	}
-	return true
 }
 
-func expectNameConstraintsError(t *testing.T, i int, err error) (ok bool) {
+func expectNameConstraintsError(t *testing.T, err error) {
 	if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != CANotAuthorizedForThisName {
-		t.Errorf("#%d: error was not a CANotAuthorizedForThisName: %v", i, err)
-		return false
+		t.Fatalf("error was not a CANotAuthorizedForThisName: %v", err)
 	}
-	return true
 }
 
-func expectNotAuthorizedError(t *testing.T, i int, err error) (ok bool) {
+func expectNotAuthorizedError(t *testing.T, err error) {
 	if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != NotAuthorizedToSign {
-		t.Errorf("#%d: error was not a NotAuthorizedToSign: %v", i, err)
-		return false
+		t.Fatalf("error was not a NotAuthorizedToSign: %v", err)
 	}
-	return true
 }
 
-func expectUnhandledCriticalExtension(t *testing.T, i int, err error) (ok bool) {
+func expectUnhandledCriticalExtension(t *testing.T, err error) {
 	if _, ok := err.(UnhandledCriticalExtension); !ok {
-		t.Errorf("#%d: error was not an UnhandledCriticalExtension: %v", i, err)
-		return false
+		t.Fatalf("error was not an UnhandledCriticalExtension: %v", err)
 	}
-	return true
 }
 
 func certificateFromPEM(pemBytes string) (*Certificate, error) {
@@ -503,107 +506,91 @@
 	return ParseCertificate(block.Bytes)
 }
 
-func testVerify(t *testing.T, useSystemRoots bool) {
-	defer func(savedIgnoreCN bool) {
-		ignoreCN = savedIgnoreCN
-	}(ignoreCN)
-	for i, test := range verifyTests {
-		if useSystemRoots && test.systemSkip {
-			continue
-		}
-		if runtime.GOOS == "windows" && test.testSystemRootsError {
-			continue
-		}
+func testVerify(t *testing.T, test verifyTest, useSystemRoots bool) {
+	defer func(savedIgnoreCN bool) { ignoreCN = savedIgnoreCN }(ignoreCN)
 
-		ignoreCN = test.ignoreCN
-		opts := VerifyOptions{
-			Intermediates: NewCertPool(),
-			DNSName:       test.dnsName,
-			CurrentTime:   time.Unix(test.currentTime, 0),
-			KeyUsages:     test.keyUsages,
-		}
+	ignoreCN = test.ignoreCN
+	opts := VerifyOptions{
+		Intermediates: NewCertPool(),
+		DNSName:       test.dnsName,
+		CurrentTime:   time.Unix(test.currentTime, 0),
+		KeyUsages:     test.keyUsages,
+	}
 
-		if !useSystemRoots {
-			opts.Roots = NewCertPool()
-			for j, root := range test.roots {
-				ok := opts.Roots.AppendCertsFromPEM([]byte(root))
-				if !ok {
-					t.Errorf("#%d: failed to parse root #%d", i, j)
-					return
-				}
-			}
-		}
-
-		for j, intermediate := range test.intermediates {
-			ok := opts.Intermediates.AppendCertsFromPEM([]byte(intermediate))
+	if !useSystemRoots {
+		opts.Roots = NewCertPool()
+		for j, root := range test.roots {
+			ok := opts.Roots.AppendCertsFromPEM([]byte(root))
 			if !ok {
-				t.Errorf("#%d: failed to parse intermediate #%d", i, j)
-				return
+				t.Fatalf("failed to parse root #%d", j)
 			}
 		}
+	}
 
-		leaf, err := certificateFromPEM(test.leaf)
-		if err != nil {
-			t.Errorf("#%d: failed to parse leaf: %v", i, err)
-			return
+	for j, intermediate := range test.intermediates {
+		ok := opts.Intermediates.AppendCertsFromPEM([]byte(intermediate))
+		if !ok {
+			t.Fatalf("failed to parse intermediate #%d", j)
 		}
+	}
 
-		var oldSystemRoots *CertPool
-		if test.testSystemRootsError {
-			oldSystemRoots = systemRootsPool()
-			systemRoots = nil
-			opts.Roots = nil
-		}
+	leaf, err := certificateFromPEM(test.leaf)
+	if err != nil {
+		t.Fatalf("failed to parse leaf: %v", err)
+	}
 
-		chains, err := leaf.Verify(opts)
+	chains, err := leaf.Verify(opts)
 
-		if test.testSystemRootsError {
-			systemRoots = oldSystemRoots
-		}
-
-		if test.errorCallback == nil && err != nil {
-			t.Errorf("#%d: unexpected error: %v", i, err)
-		}
-		if test.errorCallback != nil {
-			if !test.errorCallback(t, i, err) {
-				return
+	if test.errorCallback == nil && err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if test.errorCallback != nil {
+		if useSystemRoots && test.systemLax {
+			if err == nil {
+				t.Fatalf("expected error")
 			}
+		} else {
+			test.errorCallback(t, err)
 		}
+	}
 
-		if len(chains) != len(test.expectedChains) {
-			t.Errorf("#%d: wanted %d chains, got %d", i, len(test.expectedChains), len(chains))
-		}
+	if len(chains) != len(test.expectedChains) {
+		t.Errorf("wanted %d chains, got %d", len(test.expectedChains), len(chains))
+	}
 
-		// We check that each returned chain matches a chain from
-		// expectedChains but an entry in expectedChains can't match
-		// two chains.
-		seenChains := make([]bool, len(chains))
-	NextOutputChain:
-		for _, chain := range chains {
-		TryNextExpected:
-			for j, expectedChain := range test.expectedChains {
-				if seenChains[j] {
-					continue
-				}
-				if len(chain) != len(expectedChain) {
-					continue
-				}
-				for k, cert := range chain {
-					if !strings.Contains(nameToKey(&cert.Subject), expectedChain[k]) {
-						continue TryNextExpected
-					}
-				}
-				// we matched
-				seenChains[j] = true
-				continue NextOutputChain
+	// We check that each returned chain matches a chain from
+	// expectedChains but an entry in expectedChains can't match
+	// two chains.
+	seenChains := make([]bool, len(chains))
+NextOutputChain:
+	for _, chain := range chains {
+	TryNextExpected:
+		for j, expectedChain := range test.expectedChains {
+			if seenChains[j] {
+				continue
 			}
-			t.Errorf("#%d: No expected chain matched %s", i, chainToDebugString(chain))
+			if len(chain) != len(expectedChain) {
+				continue
+			}
+			for k, cert := range chain {
+				if !strings.Contains(nameToKey(&cert.Subject), expectedChain[k]) {
+					continue TryNextExpected
+				}
+			}
+			// we matched
+			seenChains[j] = true
+			continue NextOutputChain
 		}
+		t.Errorf("no expected chain matched %s", chainToDebugString(chain))
 	}
 }
 
 func TestGoVerify(t *testing.T) {
-	testVerify(t, false)
+	for _, test := range verifyTests {
+		t.Run(test.name, func(t *testing.T) {
+			testVerify(t, test, false)
+		})
+	}
 }
 
 func TestSystemVerify(t *testing.T) {
@@ -611,7 +598,14 @@
 		t.Skipf("skipping verify test using system APIs on %q", runtime.GOOS)
 	}
 
-	testVerify(t, true)
+	for _, test := range verifyTests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.systemSkip {
+				t.SkipNow()
+			}
+			testVerify(t, test, true)
+		})
+	}
 }
 
 func chainToDebugString(chain []*Certificate) string {
@@ -648,8 +642,7 @@
 PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
 hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
 const giag2Intermediate = `-----BEGIN CERTIFICATE-----
 MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
@@ -674,8 +667,7 @@
 HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto
 WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6
 yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
 const googleLeaf = `-----BEGIN CERTIFICATE-----
 MIIEdjCCA16gAwIBAgIIcR5k4dkoe04wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
@@ -702,8 +694,7 @@
 orKqTuAPzXK7imQk6+OycYABbqCtC/9qmwRd8wwn7sF97DtYfK8WuNHtFalCAwyi
 8LxJJYJCLWoMhZ+V8GZm+FOex5qkQAjnZrtNlbQJ8ro4r+rpKXtmMFFhfa+7L+PA
 Kom08eUK8skxAzfDDijZPh10VtJ66uBoiDPdT+uCBehcBIcmSTrKjFGX
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
 // googleLeafWithInvalidHash is the same as googleLeaf, but the signature
 // algorithm in the certificate contains a nonsense OID.
@@ -732,8 +723,7 @@
 orKqTuAPzXK7imQk6+OycYABbqCtC/9qmwRd8wwn7sF97DtYfK8WuNHtFalCAwyi
 8LxJJYJCLWoMhZ+V8GZm+FOex5qkQAjnZrtNlbQJ8ro4r+rpKXtmMFFhfa+7L+PA
 Kom08eUK8skxAzfDDijZPh10VtJ66uBoiDPdT+uCBehcBIcmSTrKjFGX
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
 const dnssecExpLeaf = `-----BEGIN CERTIFICATE-----
 MIIGzTCCBbWgAwIBAgIDAdD6MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ
@@ -858,58 +848,127 @@
 -----END CERTIFICATE-----`
 
 const smimeLeaf = `-----BEGIN CERTIFICATE-----
-MIIFBjCCA+6gAwIBAgISESFvrjT8XcJTEe6rBlPptILlMA0GCSqGSIb3DQEBBQUA
-MFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSowKAYD
-VQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAyIENBIC0gRzIwHhcNMTIwMTIz
-MTYzNjU5WhcNMTUwMTIzMTYzNjU5WjCBlDELMAkGA1UEBhMCVVMxFjAUBgNVBAgT
-DU5ldyBIYW1zcGhpcmUxEzARBgNVBAcTClBvcnRzbW91dGgxGTAXBgNVBAoTEEds
-b2JhbFNpZ24sIEluYy4xEzARBgNVBAMTClJ5YW4gSHVyc3QxKDAmBgkqhkiG9w0B
-CQEWGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wggEiMA0GCSqGSIb3DQEBAQUA
-A4IBDwAwggEKAoIBAQC4ASSTvavmsFQAob60ukSSwOAL9nT/s99ltNUCAf5fPH5j
-NceMKxaQse2miOmRRIXaykcq1p/TbI70Ztce38r2mbOwqDHHPVi13GxJEyUXWgaR
-BteDMu5OGyWNG1kchVsGWpbstT0Z4v0md5m1BYFnxB20ebJyOR2lXDxsFK28nnKV
-+5eMj76U8BpPQ4SCH7yTMG6y0XXsB3cCrBKr2o3TOYgEKv+oNnbaoMt3UxMt9nSf
-9jyIshjqfnT5Aew3CUNMatO55g5FXXdIukAweg1YSb1ls05qW3sW00T3d7dQs9/7
-NuxCg/A2elmVJSoy8+MLR8JSFEf/aMgjO/TyLg/jAgMBAAGjggGPMIIBizAOBgNV
-HQ8BAf8EBAMCBaAwTQYDVR0gBEYwRDBCBgorBgEEAaAyASgKMDQwMgYIKwYBBQUH
-AgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMCQGA1Ud
-EQQdMBuBGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wCQYDVR0TBAIwADAdBgNV
-HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwQwYDVR0fBDwwOjA4oDagNIYyaHR0
-cDovL2NybC5nbG9iYWxzaWduLmNvbS9ncy9nc3BlcnNvbmFsc2lnbjJnMi5jcmww
-VQYIKwYBBQUHAQEESTBHMEUGCCsGAQUFBzAChjlodHRwOi8vc2VjdXJlLmdsb2Jh
-bHNpZ24uY29tL2NhY2VydC9nc3BlcnNvbmFsc2lnbjJnMi5jcnQwHQYDVR0OBBYE
-FFWiECe0/L72eVYqcWYnLV6SSjzhMB8GA1UdIwQYMBaAFD8V0m18L+cxnkMKBqiU
-bCw7xe5lMA0GCSqGSIb3DQEBBQUAA4IBAQAhQi6hLPeudmf3IBF4IDzCvRI0FaYd
-BKfprSk/H0PDea4vpsLbWpA0t0SaijiJYtxKjlM4bPd+2chb7ejatDdyrZIzmDVy
-q4c30/xMninGKokpYA11/Ve+i2dvjulu65qasrtQRGybAuuZ67lrp/K3OMFgjV5N
-C3AHYLzvNU4Dwc4QQ1BaMOg6KzYSrKbABRZajfrpC9uiePsv7mDIXLx/toBPxWNl
-a5vJm5DrZdn7uHdvBCE6kMykbOLN5pmEK0UIlwKh6Qi5XD0pzlVkEZliFkBMJgub
-d/eF7xeg7TKPWC5xyOFp9SdMolJM7LTC3wnSO3frBAev+q/nGs9Xxyvs
+MIIIPDCCBiSgAwIBAgIQaMDxFS0pOMxZZeOBxoTJtjANBgkqhkiG9w0BAQsFADCB
+nTELMAkGA1UEBhMCRVMxFDASBgNVBAoMC0laRU5QRSBTLkEuMTowOAYDVQQLDDFB
+WlogWml1cnRhZ2lyaSBwdWJsaWtvYSAtIENlcnRpZmljYWRvIHB1YmxpY28gU0NB
+MTwwOgYDVQQDDDNFQUVrbyBIZXJyaSBBZG1pbmlzdHJhemlvZW4gQ0EgLSBDQSBB
+QVBQIFZhc2NhcyAoMikwHhcNMTcwNzEyMDg1MzIxWhcNMjEwNzEyMDg1MzIxWjCC
+AQwxDzANBgNVBAoMBklaRU5QRTE4MDYGA1UECwwvWml1cnRhZ2lyaSBrb3Jwb3Jh
+dGlib2EtQ2VydGlmaWNhZG8gY29ycG9yYXRpdm8xQzBBBgNVBAsMOkNvbmRpY2lv
+bmVzIGRlIHVzbyBlbiB3d3cuaXplbnBlLmNvbSBub2xhIGVyYWJpbGkgamFraXRl
+a28xFzAVBgNVBC4TDi1kbmkgOTk5OTk5ODlaMSQwIgYDVQQDDBtDT1JQT1JBVElW
+TyBGSUNUSUNJTyBBQ1RJVk8xFDASBgNVBCoMC0NPUlBPUkFUSVZPMREwDwYDVQQE
+DAhGSUNUSUNJTzESMBAGA1UEBRMJOTk5OTk5ODlaMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAwVOMwUDfBtsH0XuxYnb+v/L774jMH8valX7RPH8cl2Lb
+SiqSo0RchW2RGA2d1yuYHlpChC9jGmt0X/g66/E/+q2hUJlfJtqVDJFwtFYV4u2S
+yzA3J36V4PRkPQrKxAsbzZriFXAF10XgiHQz9aVeMMJ9GBhmh9+DK8Tm4cMF6i8l
++AuC35KdngPF1x0ealTYrYZplpEJFO7CiW42aLi6vQkDR2R7nmZA4AT69teqBWsK
+0DZ93/f0G/3+vnWwNTBF0lB6dIXoaz8OMSyHLqGnmmAtMrzbjAr/O/WWgbB/BqhR
+qjJQ7Ui16cuDldXaWQ/rkMzsxmsAox0UF+zdQNvXUQIDAQABo4IDBDCCAwAwgccG
+A1UdEgSBvzCBvIYVaHR0cDovL3d3dy5pemVucGUuY29tgQ9pbmZvQGl6ZW5wZS5j
+b22kgZEwgY4xRzBFBgNVBAoMPklaRU5QRSBTLkEuIC0gQ0lGIEEwMTMzNzI2MC1S
+TWVyYy5WaXRvcmlhLUdhc3RlaXogVDEwNTUgRjYyIFM4MUMwQQYDVQQJDDpBdmRh
+IGRlbCBNZWRpdGVycmFuZW8gRXRvcmJpZGVhIDE0IC0gMDEwMTAgVml0b3JpYS1H
+YXN0ZWl6MB4GA1UdEQQXMBWBE2ZpY3RpY2lvQGl6ZW5wZS5ldXMwDgYDVR0PAQH/
+BAQDAgXgMCkGA1UdJQQiMCAGCCsGAQUFBwMCBggrBgEFBQcDBAYKKwYBBAGCNxQC
+AjAdBgNVHQ4EFgQUyeoOD4cgcljKY0JvrNuX2waFQLAwHwYDVR0jBBgwFoAUwKlK
+90clh/+8taaJzoLSRqiJ66MwggEnBgNVHSAEggEeMIIBGjCCARYGCisGAQQB8zkB
+AQEwggEGMDMGCCsGAQUFBwIBFidodHRwOi8vd3d3Lml6ZW5wZS5jb20vcnBhc2Nh
+Y29ycG9yYXRpdm8wgc4GCCsGAQUFBwICMIHBGoG+Wml1cnRhZ2lyaWEgRXVza2Fs
+IEF1dG9ub21pYSBFcmtpZGVnb2tvIHNla3RvcmUgcHVibGlrb2tvIGVyYWt1bmRl
+ZW4gYmFybmUtc2FyZWV0YW4gYmFrYXJyaWsgZXJhYmlsIGRhaXRla2UuIFVzbyBy
+ZXN0cmluZ2lkbyBhbCBhbWJpdG8gZGUgcmVkZXMgaW50ZXJuYXMgZGUgRW50aWRh
+ZGVzIGRlbCBTZWN0b3IgUHVibGljbyBWYXNjbzAyBggrBgEFBQcBAQQmMCQwIgYI
+KwYBBQUHMAGGFmh0dHA6Ly9vY3NwLml6ZW5wZS5jb20wOgYDVR0fBDMwMTAvoC2g
+K4YpaHR0cDovL2NybC5pemVucGUuY29tL2NnaS1iaW4vY3JsaW50ZXJuYTIwDQYJ
+KoZIhvcNAQELBQADggIBAIy5PQ+UZlCRq6ig43vpHwlwuD9daAYeejV0Q+ZbgWAE
+GtO0kT/ytw95ZEJMNiMw3fYfPRlh27ThqiT0VDXZJDlzmn7JZd6QFcdXkCsiuv4+
+ZoXAg/QwnA3SGUUO9aVaXyuOIIuvOfb9MzoGp9xk23SMV3eiLAaLMLqwB5DTfBdt
+BGI7L1MnGJBv8RfP/TL67aJ5bgq2ri4S8vGHtXSjcZ0+rCEOLJtmDNMnTZxancg3
+/H5edeNd+n6Z48LO+JHRxQufbC4mVNxVLMIP9EkGUejlq4E4w6zb5NwCQczJbSWL
+i31rk2orsNsDlyaLGsWZp3JSNX6RmodU4KAUPor4jUJuUhrrm3Spb73gKlV/gcIw
+bCE7mML1Kss3x1ySaXsis6SZtLpGWKkW2iguPWPs0ydV6RPhmsCxieMwPPIJ87vS
+5IejfgyBae7RSuAIHyNFy4uI5xwvwUFf6OZ7az8qtW7ImFOgng3Ds+W9k1S2CNTx
+d0cnKTfA6IpjGo8EeHcxnIXT8NPImWaRj0qqonvYady7ci6U4m3lkNSdXNn1afgw
+mYust+gxVtOZs1gk2MUCgJ1V1X+g7r/Cg7viIn6TLkLrpS1kS1hvMqkl9M+7XqPo
+Qd95nJKOkusQpy99X4dF/lfbYAQnnjnqh3DLD2gvYObXFaAYFaiBKTiMTV2X72F+
 -----END CERTIFICATE-----`
 
 const smimeIntermediate = `-----BEGIN CERTIFICATE-----
-MIIEFjCCAv6gAwIBAgILBAAAAAABL07hL1IwDQYJKoZIhvcNAQEFBQAwVzELMAkG
-A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
-b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
-MDBaFw0xOTA0MTMxMDAwMDBaMFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
-YWxTaWduIG52LXNhMSowKAYDVQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAy
-IENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBa0H5Nez4
-En3dIlFpX7e5E0YndxQ74xOBbz7kdBd+DLX0LOQMjVPU3DAgKL9ujhH+ZhHkURbH
-3X/94TQSUL/z2JjsaQvS0NqyZXHhM5eeuquzOJRzEQ8+odETzHg2G0Erv7yjSeww
-gkwDWDJnYUDlOjYTDUEG6+i+8Mn425reo4I0E277wD542kmVWeW7+oHv5dZo9e1Q
-yWwiKTEP6BEQVVSBgThXMG4traSSDRUt3T1eQTZx5EObpiBEBO4OTqiBTJfg4vEI
-YgkXzKLpnfszTB6YMDpR9/QS6p3ANB3kfAb+t6udSO3WCst0DGrwHDLBFGDR4UeY
-T5KGGnI7cWL7AgMBAAGjgeUwgeIwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
-MAYBAf8CAQAwHQYDVR0OBBYEFD8V0m18L+cxnkMKBqiUbCw7xe5lMEcGA1UdIARA
-MD4wPAYEVR0gADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWdu
-LmNvbS9yZXBvc2l0b3J5LzAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmds
-b2JhbHNpZ24ubmV0L3Jvb3QuY3JsMB8GA1UdIwQYMBaAFGB7ZhpFDZfKiVAvfQTN
-NKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQBDc3nMpMxJMQMcYUCB3+C73UpvwDE8
-eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX
-eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX
-YEvTWbWwGdPytDFPYIl3/6OqNSXSnZ7DxPcdLJq2uyiga8PB/TTIIHYkdM2+1DE0
-7y3rH/7TjwDVD7SLu5/SdOfKskuMPTjOEvz3K161mymW06klVhubCIWOro/Gx1Q2
-2FQOZ7/2k4uYoOdBTSlb8kTAuzZNgIE0rB2BIYCTz/P6zZIKW0ogbRSH
+MIIHNzCCBSGgAwIBAgIQJMXIqlZvjuhMvqcFXOFkpDALBgkqhkiG9w0BAQswODEL
+MAkGA1UEBhMCRVMxFDASBgNVBAoMC0laRU5QRSBTLkEuMRMwEQYDVQQDDApJemVu
+cGUuY29tMB4XDTEwMTAyMDA4MjMzM1oXDTM3MTIxMjIzMDAwMFowgZ0xCzAJBgNV
+BAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjE6MDgGA1UECwwxQVpaIFppdXJ0
+YWdpcmkgcHVibGlrb2EgLSBDZXJ0aWZpY2FkbyBwdWJsaWNvIFNDQTE8MDoGA1UE
+AwwzRUFFa28gSGVycmkgQWRtaW5pc3RyYXppb2VuIENBIC0gQ0EgQUFQUCBWYXNj
+YXMgKDIpMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoIM7nEdI0N1h
+rR5T4xuV/usKDoMIasaiKvfLhbwxaNtTt+a7W/6wV5bv3svQFIy3sUXjjdzV1nG2
+To2wo/YSPQiOt8exWvOapvL21ogiof+kelWnXFjWaKJI/vThHYLgIYEMj/y4HdtU
+ojI646rZwqsb4YGAopwgmkDfUh5jOhV2IcYE3TgJAYWVkj6jku9PLaIsHiarAHjD
+PY8dig8a4SRv0gm5Yk7FXLmW1d14oxQBDeHZ7zOEXfpafxdEDO2SNaRJjpkh8XRr
+PGqkg2y1Q3gT6b4537jz+StyDIJ3omylmlJsGCwqT7p8mEqjGJ5kC5I2VnjXKuNn
+soShc72khWZVUJiJo5SGuAkNE2ZXqltBVm5Jv6QweQKsX6bkcMc4IZok4a+hx8FM
+8IBpGf/I94pU6HzGXqCyc1d46drJgDY9mXa+6YDAJFl3xeXOOW2iGCfwXqhiCrKL
+MYvyMZzqF3QH5q4nb3ZnehYvraeMFXJXDn+Utqp8vd2r7ShfQJz01KtM4hgKdgSg
+jtW+shkVVN5ng/fPN85ovfAH2BHXFfHmQn4zKsYnLitpwYM/7S1HxlT61cdQ7Nnk
+3LZTYEgAoOmEmdheklT40WAYakksXGM5VrzG7x9S7s1Tm+Vb5LSThdHC8bxxwyTb
+KsDRDNJ84N9fPDO6qHnzaL2upQ43PycCAwEAAaOCAdkwggHVMIHHBgNVHREEgb8w
+gbyGFWh0dHA6Ly93d3cuaXplbnBlLmNvbYEPaW5mb0BpemVucGUuY29tpIGRMIGO
+MUcwRQYDVQQKDD5JWkVOUEUgUy5BLiAtIENJRiBBMDEzMzcyNjAtUk1lcmMuVml0
+b3JpYS1HYXN0ZWl6IFQxMDU1IEY2MiBTODFDMEEGA1UECQw6QXZkYSBkZWwgTWVk
+aXRlcnJhbmVvIEV0b3JiaWRlYSAxNCAtIDAxMDEwIFZpdG9yaWEtR2FzdGVpejAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUwKlK90cl
+h/+8taaJzoLSRqiJ66MwHwYDVR0jBBgwFoAUHRxlDqjyJXu0kc/ksbHmvVV0bAUw
+OgYDVR0gBDMwMTAvBgRVHSAAMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly93d3cuaXpl
+bnBlLmNvbS9jcHMwNwYIKwYBBQUHAQEEKzApMCcGCCsGAQUFBzABhhtodHRwOi8v
+b2NzcC5pemVucGUuY29tOjgwOTQwMwYDVR0fBCwwKjAooCagJIYiaHR0cDovL2Ny
+bC5pemVucGUuY29tL2NnaS1iaW4vYXJsMjALBgkqhkiG9w0BAQsDggIBAMbjc3HM
+3DG9ubWPkzsF0QsktukpujbTTcGk4h20G7SPRy1DiiTxrRzdAMWGjZioOP3/fKCS
+M539qH0M+gsySNie+iKlbSZJUyE635T1tKw+G7bDUapjlH1xyv55NC5I6wCXGC6E
+3TEP5B/E7dZD0s9E4lS511ubVZivFgOzMYo1DO96diny/N/V1enaTCpRl1qH1OyL
+xUYTijV4ph2gL6exwuG7pxfRcVNHYlrRaXWfTz3F6NBKyULxrI3P/y6JAtN1GqT4
+VF/+vMygx22n0DufGepBwTQz6/rr1ulSZ+eMnuJiTXgh/BzQnkUsXTb8mHII25iR
+0oYF2qAsk6ecWbLiDpkHKIDHmML21MZE13MS8NSvTHoqJO4LyAmDe6SaeNHtrPlK
+b6mzE1BN2ug+ZaX8wLA5IMPFaf0jKhb/Cxu8INsxjt00brsErCc9ip1VNaH0M4bi
+1tGxfiew2436FaeyUxW7Pl6G5GgkNbuUc7QIoRy06DdU/U38BxW3uyJMY60zwHvS
+FlKAn0OvYp4niKhAJwaBVN3kowmJuOU5Rid+TUnfyxbJ9cttSgzaF3hP/N4zgMEM
+5tikXUskeckt8LUK96EH0QyssavAMECUEb/xrupyRdYWwjQGvNLq6T5+fViDGyOw
+k+lzD44wofy8paAy9uC9Owae0zMEzhcsyRm7
+-----END CERTIFICATE-----`
+
+const smimeRoot = `-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
+MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
+ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
+VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
+b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
+scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
+xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
+LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
+uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
+yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
+rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
+BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
+hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
+QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
+Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
+QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
+BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
+A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
+laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
+awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
+JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
+LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
+VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
+LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
+UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
+QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
+QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
 -----END CERTIFICATE-----`
 
 var megaLeaf = `-----BEGIN CERTIFICATE-----
@@ -1315,50 +1374,7 @@
 cw3ESZzThBwWqvPOtJdpXdm+r57pDW8qD+/0lY8wfImMNkQAyCUCLg/1Lxt/hrBj
 -----END CERTIFICATE-----`
 
-const issuerSubjectMatchRoot = `
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 161640039802297062 (0x23e42c281e55ae6)
-    Signature Algorithm: sha256WithRSAEncryption
-        Issuer: O=Golang, CN=Root ca
-        Validity
-            Not Before: Jan  1 00:00:00 2015 GMT
-            Not After : Jan  1 00:00:00 2025 GMT
-        Subject: O=Golang, CN=Root ca
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (1024 bit)
-                Modulus:
-                    00:e9:0e:7f:11:0c:e6:5a:e6:86:83:70:f6:51:07:
-                    2e:02:78:11:f5:b2:24:92:38:ee:26:62:02:c7:94:
-                    f1:3e:a1:77:6a:c0:8f:d5:22:68:b6:5d:e2:4c:da:
-                    e0:85:11:35:c2:92:72:49:8d:81:b4:88:97:6b:b7:
-                    fc:b2:44:5b:d9:4d:06:70:f9:0c:c6:8f:e9:b3:df:
-                    a3:6a:84:6c:43:59:be:9d:b2:d0:76:9b:c3:d7:fa:
-                    99:59:c3:b8:e5:f3:53:03:bd:49:d6:b3:cc:a2:43:
-                    fe:ad:c2:0b:b9:01:b8:56:29:94:03:24:a7:0d:28:
-                    21:29:a9:ae:94:5b:4a:f9:9f
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Key Usage: critical
-                Certificate Sign
-            X509v3 Extended Key Usage:
-                TLS Web Server Authentication, TLS Web Client Authentication
-            X509v3 Basic Constraints: critical
-                CA:TRUE
-            X509v3 Subject Key Identifier:
-                40:37:D7:01:FB:40:2F:B8:1C:7E:54:04:27:8C:59:01
-    Signature Algorithm: sha256WithRSAEncryption
-         6f:84:df:49:e0:99:d4:71:66:1d:32:86:56:cb:ea:5a:6b:0e:
-         00:6a:d1:5a:6e:1f:06:23:07:ff:cb:d1:1a:74:e4:24:43:0b:
-         aa:2a:a0:73:75:25:82:bc:bf:3f:a9:f8:48:88:ac:ed:3a:94:
-         3b:0d:d3:88:c8:67:44:61:33:df:71:6c:c5:af:ed:16:8c:bf:
-         82:f9:49:bb:e3:2a:07:53:36:37:25:77:de:91:a4:77:09:7f:
-         6f:b2:91:58:c4:05:89:ea:8e:fa:e1:3b:19:ef:f8:f6:94:b7:
-         7b:27:e6:e4:84:dd:2b:f5:93:f5:3c:d8:86:c5:38:01:56:5c:
-         9f:6d
------BEGIN CERTIFICATE-----
+const issuerSubjectMatchRoot = `-----BEGIN CERTIFICATE-----
 MIICIDCCAYmgAwIBAgIIAj5CwoHlWuYwDQYJKoZIhvcNAQELBQAwIzEPMA0GA1UE
 ChMGR29sYW5nMRAwDgYDVQQDEwdSb290IGNhMB4XDTE1MDEwMTAwMDAwMFoXDTI1
 MDEwMTAwMDAwMFowIzEPMA0GA1UEChMGR29sYW5nMRAwDgYDVQQDEwdSb290IGNh
@@ -1373,53 +1389,7 @@
 eyfm5ITdK/WT9TzYhsU4AVZcn20=
 -----END CERTIFICATE-----`
 
-const issuerSubjectMatchLeaf = `
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 16785088708916013734 (0xe8f09d3fe25beaa6)
-    Signature Algorithm: sha256WithRSAEncryption
-        Issuer: O=Golang, CN=Root CA
-        Validity
-            Not Before: Jan  1 00:00:00 2015 GMT
-            Not After : Jan  1 00:00:00 2025 GMT
-        Subject: O=Golang, CN=Leaf
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (1024 bit)
-                Modulus:
-                    00:db:46:7d:93:2e:12:27:06:48:bc:06:28:21:ab:
-                    7e:c4:b6:a2:5d:fe:1e:52:45:88:7a:36:47:a5:08:
-                    0d:92:42:5b:c2:81:c0:be:97:79:98:40:fb:4f:6d:
-                    14:fd:2b:13:8b:c2:a5:2e:67:d8:d4:09:9e:d6:22:
-                    38:b7:4a:0b:74:73:2b:c2:34:f1:d1:93:e5:96:d9:
-                    74:7b:f3:58:9f:6c:61:3c:c0:b0:41:d4:d9:2b:2b:
-                    24:23:77:5b:1c:3b:bd:75:5d:ce:20:54:cf:a1:63:
-                    87:1d:1e:24:c4:f3:1d:1a:50:8b:aa:b6:14:43:ed:
-                    97:a7:75:62:f4:14:c8:52:d7
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Key Usage: critical
-                Digital Signature, Key Encipherment
-            X509v3 Extended Key Usage:
-                TLS Web Server Authentication, TLS Web Client Authentication
-            X509v3 Basic Constraints: critical
-                CA:FALSE
-            X509v3 Subject Key Identifier:
-                9F:91:16:1F:43:43:3E:49:A6:DE:6D:B6:80:D7:9F:60
-            X509v3 Authority Key Identifier:
-                keyid:40:37:D7:01:FB:40:2F:B8:1C:7E:54:04:27:8C:59:01
-
-    Signature Algorithm: sha256WithRSAEncryption
-         8d:86:05:da:89:f5:1d:c5:16:14:41:b9:34:87:2b:5c:38:99:
-         e3:d9:5a:5b:7a:5b:de:0b:5c:08:45:09:6f:1c:9d:31:5f:08:
-         ca:7a:a3:99:da:83:0b:22:be:4f:02:35:91:4e:5d:5c:37:bf:
-         89:22:58:7d:30:76:d2:2f:d0:a0:ee:77:9e:77:c0:d6:19:eb:
-         ec:a0:63:35:6a:80:9b:80:1a:80:de:64:bc:40:38:3c:22:69:
-         ad:46:26:a2:3d:ea:f4:c2:92:49:16:03:96:ae:64:21:b9:7c:
-         ee:64:91:47:81:aa:b4:0c:09:2b:12:1a:b2:f3:af:50:b3:b1:
-         ce:24
------BEGIN CERTIFICATE-----
+const issuerSubjectMatchLeaf = `-----BEGIN CERTIFICATE-----
 MIICODCCAaGgAwIBAgIJAOjwnT/iW+qmMA0GCSqGSIb3DQEBCwUAMCMxDzANBgNV
 BAoTBkdvbGFuZzEQMA4GA1UEAxMHUm9vdCBDQTAeFw0xNTAxMDEwMDAwMDBaFw0y
 NTAxMDEwMDAwMDBaMCAxDzANBgNVBAoTBkdvbGFuZzENMAsGA1UEAxMETGVhZjCB
@@ -1432,11 +1402,9 @@
 hvcNAQELBQADgYEAjYYF2on1HcUWFEG5NIcrXDiZ49laW3pb3gtcCEUJbxydMV8I
 ynqjmdqDCyK+TwI1kU5dXDe/iSJYfTB20i/QoO53nnfA1hnr7KBjNWqAm4AagN5k
 vEA4PCJprUYmoj3q9MKSSRYDlq5kIbl87mSRR4GqtAwJKxIasvOvULOxziQ=
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
-const x509v1TestRoot = `
------BEGIN CERTIFICATE-----
+const x509v1TestRoot = `-----BEGIN CERTIFICATE-----
 MIICIDCCAYmgAwIBAgIIAj5CwoHlWuYwDQYJKoZIhvcNAQELBQAwIzEPMA0GA1UE
 ChMGR29sYW5nMRAwDgYDVQQDEwdSb290IENBMB4XDTE1MDEwMTAwMDAwMFoXDTI1
 MDEwMTAwMDAwMFowIzEPMA0GA1UEChMGR29sYW5nMRAwDgYDVQQDEwdSb290IENB
@@ -1451,8 +1419,7 @@
 /1JmacUUofl+HusHuLkDxmadogI=
 -----END CERTIFICATE-----`
 
-const x509v1TestIntermediate = `
------BEGIN CERTIFICATE-----
+const x509v1TestIntermediate = `-----BEGIN CERTIFICATE-----
 MIIByjCCATMCCQCCdEMsT8ykqTANBgkqhkiG9w0BAQsFADAjMQ8wDQYDVQQKEwZH
 b2xhbmcxEDAOBgNVBAMTB1Jvb3QgQ0EwHhcNMTUwMTAxMDAwMDAwWhcNMjUwMTAx
 MDAwMDAwWjAwMQ8wDQYDVQQKEwZHb2xhbmcxHTAbBgNVBAMTFFguNTA5djEgaW50
@@ -1465,8 +1432,7 @@
 x5Wlq1u3YDL/j6s1nU2dQ3ySB/oP7J+vQ9V4QeM+
 -----END CERTIFICATE-----`
 
-const x509v1TestLeaf = `
------BEGIN CERTIFICATE-----
+const x509v1TestLeaf = `-----BEGIN CERTIFICATE-----
 MIICMzCCAZygAwIBAgIJAPo99mqJJrpJMA0GCSqGSIb3DQEBCwUAMDAxDzANBgNV
 BAoTBkdvbGFuZzEdMBsGA1UEAxMUWC41MDl2MSBpbnRlcm1lZGlhdGUwHhcNMTUw
 MTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjArMQ8wDQYDVQQKEwZHb2xhbmcxGDAW
@@ -1481,8 +1447,7 @@
 /jt8qszOXCv2vYdUTPNuPqufXLWMoirpuXrr1liJDmedCcAHepY/
 -----END CERTIFICATE-----`
 
-const ignoreCNWithSANRoot = `
------BEGIN CERTIFICATE-----
+const ignoreCNWithSANRoot = `-----BEGIN CERTIFICATE-----
 MIIDPzCCAiegAwIBAgIIJkzCwkNrPHMwDQYJKoZIhvcNAQELBQAwMDEQMA4GA1UE
 ChMHVEVTVElORzEcMBoGA1UEAxMTKipUZXN0aW5nKiogUm9vdCBDQTAeFw0xNTAx
 MDEwMDAwMDBaFw0yNTAxMDEwMDAwMDBaMDAxEDAOBgNVBAoTB1RFU1RJTkcxHDAa
@@ -1503,8 +1468,7 @@
 BJ6bvwEAasFiLGP6Zbdmxb2hIA==
 -----END CERTIFICATE-----`
 
-const ignoreCNWithSANLeaf = `
------BEGIN CERTIFICATE-----
+const ignoreCNWithSANLeaf = `-----BEGIN CERTIFICATE-----
 MIIDaTCCAlGgAwIBAgIJAONakvRTxgJhMA0GCSqGSIb3DQEBCwUAMDAxEDAOBgNV
 BAoTB1RFU1RJTkcxHDAaBgNVBAMTEyoqVGVzdGluZyoqIFJvb3QgQ0EwHhcNMTUw
 MTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjAsMRAwDgYDVQQKEwdURVNUSU5HMRgw
@@ -1526,8 +1490,7 @@
 xZbqP3Krgjj4XNaXjg==
 -----END CERTIFICATE-----`
 
-const excludedNamesLeaf = `
------BEGIN CERTIFICATE-----
+const excludedNamesLeaf = `-----BEGIN CERTIFICATE-----
 MIID4DCCAsigAwIBAgIHDUSFtJknhzANBgkqhkiG9w0BAQsFADCBnjELMAkGA1UE
 BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU
 MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5
@@ -1549,11 +1512,9 @@
 qKlWE+0S16+pzsWvKn831uylqwIb8ANBPsCX4aM4muFBHavSWAHgRO+P+yXVw8Q+
 VQDnMHUe5PbZd1/+1KKVs1K/CkBCtoHNHp1d/JT+2zUQJphwja9CcgfFdVhSnHL4
 oEEOFtqVMIuQfR2isi08qW/JGOHc4sFoLYB8hvdaxKWSE19A
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
-const excludedNamesIntermediate = `
------BEGIN CERTIFICATE-----
+const excludedNamesIntermediate = `-----BEGIN CERTIFICATE-----
 MIIDzTCCArWgAwIBAgIHDUSFqYeczDANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UE
 BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU
 MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5
@@ -1577,8 +1538,7 @@
 zMBX1/lk4wkFckeUIlkD55Y=
 -----END CERTIFICATE-----`
 
-const excludedNamesRoot = `
------BEGIN CERTIFICATE-----
+const excludedNamesRoot = `-----BEGIN CERTIFICATE-----
 MIIEGTCCAwGgAwIBAgIHDUSFpInn/zANBgkqhkiG9w0BAQsFADCBozELMAkGA1UE
 BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU
 MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5
@@ -1603,46 +1563,16 @@
 +NQCZDd5eFeU8PpNX7rgaYE4GPq+EEmLVCBYmdctr8QVdqJ//8Xu3+1phjDy
 -----END CERTIFICATE-----`
 
-const invalidCNRoot = `
------BEGIN CERTIFICATE-----
+const invalidCNRoot = `-----BEGIN CERTIFICATE-----
 MIIBFjCBvgIJAIsu4r+jb70UMAoGCCqGSM49BAMCMBQxEjAQBgNVBAsMCVRlc3Qg
 cm9vdDAeFw0xODA3MTExODMyMzVaFw0yODA3MDgxODMyMzVaMBQxEjAQBgNVBAsM
 CVRlc3Qgcm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABF6oDgMg0LV6YhPj
 QXaPXYCc2cIyCdqp0ROUksRz0pOLTc5iY2nraUheRUD1vRRneq7GeXOVNn7uXONg
 oCGMjNwwCgYIKoZIzj0EAwIDRwAwRAIgDSiwgIn8g1lpruYH0QD1GYeoWVunfmrI
 XzZZl0eW/ugCICgOfXeZ2GGy3wIC0352BaC3a8r5AAb2XSGNe+e9wNN6
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
-const invalidCNWithoutSAN = `
-Certificate:
-    Data:
-        Version: 1 (0x0)
-        Serial Number:
-            07:ba:bc:b7:d9:ab:0c:02:fe:50:1d:4e:15:a3:0d:e4:11:16:14:a2
-        Signature Algorithm: ecdsa-with-SHA256
-        Issuer: OU = Test root
-        Validity
-            Not Before: Jul 11 18:35:21 2018 GMT
-            Not After : Jul  8 18:35:21 2028 GMT
-        Subject: CN = "foo,invalid"
-        Subject Public Key Info:
-            Public Key Algorithm: id-ecPublicKey
-                Public-Key: (256 bit)
-                pub:
-                    04:a7:a6:7c:22:33:a7:47:7f:08:93:2d:5f:61:35:
-                    2e:da:45:67:76:f2:97:73:18:b0:01:12:4a:1a:d5:
-                    b7:6f:41:3c:bb:05:69:f4:06:5d:ff:eb:2b:a7:85:
-                    0b:4c:f7:45:4e:81:40:7a:a9:c6:1d:bb:ba:d9:b9:
-                    26:b3:ca:50:90
-                ASN1 OID: prime256v1
-                NIST CURVE: P-256
-    Signature Algorithm: ecdsa-with-SHA256
-         30:45:02:21:00:85:96:75:b6:72:3c:67:12:a0:7f:86:04:81:
-         d2:dd:c8:67:50:d7:5f:85:c0:54:54:fc:e6:6b:45:08:93:d3:
-         2a:02:20:60:86:3e:d6:28:a6:4e:da:dd:6e:95:89:cc:00:76:
-         78:1c:03:80:85:a6:5a:0b:eb:c5:f3:9c:2e:df:ef:6e:fa
------BEGIN CERTIFICATE-----
+const invalidCNWithoutSAN = `-----BEGIN CERTIFICATE-----
 MIIBJDCBywIUB7q8t9mrDAL+UB1OFaMN5BEWFKIwCgYIKoZIzj0EAwIwFDESMBAG
 A1UECwwJVGVzdCByb290MB4XDTE4MDcxMTE4MzUyMVoXDTI4MDcwODE4MzUyMVow
 FjEUMBIGA1UEAwwLZm9vLGludmFsaWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
@@ -1650,38 +1580,9 @@
 90VOgUB6qcYdu7rZuSazylCQMAoGCCqGSM49BAMCA0gAMEUCIQCFlnW2cjxnEqB/
 hgSB0t3IZ1DXX4XAVFT85mtFCJPTKgIgYIY+1iimTtrdbpWJzAB2eBwDgIWmWgvr
 xfOcLt/vbvo=
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
-const validCNWithoutSAN = `
-Certificate:
-    Data:
-        Version: 1 (0x0)
-        Serial Number:
-            07:ba:bc:b7:d9:ab:0c:02:fe:50:1d:4e:15:a3:0d:e4:11:16:14:a4
-        Signature Algorithm: ecdsa-with-SHA256
-        Issuer: OU = Test root
-        Validity
-            Not Before: Jul 11 18:47:24 2018 GMT
-            Not After : Jul  8 18:47:24 2028 GMT
-        Subject: CN = foo.example.com
-        Subject Public Key Info:
-            Public Key Algorithm: id-ecPublicKey
-                Public-Key: (256 bit)
-                pub:
-                    04:a7:a6:7c:22:33:a7:47:7f:08:93:2d:5f:61:35:
-                    2e:da:45:67:76:f2:97:73:18:b0:01:12:4a:1a:d5:
-                    b7:6f:41:3c:bb:05:69:f4:06:5d:ff:eb:2b:a7:85:
-                    0b:4c:f7:45:4e:81:40:7a:a9:c6:1d:bb:ba:d9:b9:
-                    26:b3:ca:50:90
-                ASN1 OID: prime256v1
-                NIST CURVE: P-256
-    Signature Algorithm: ecdsa-with-SHA256
-         30:44:02:20:53:6c:d7:b7:59:61:51:72:a5:18:a3:4b:0d:52:
-         ea:15:fa:d0:93:30:32:54:4b:ed:0f:58:85:b8:a8:1a:82:3b:
-         02:20:14:77:4b:0e:7e:4f:0a:4f:64:26:97:dc:d0:ed:aa:67:
-         1d:37:85:da:b4:87:ba:25:1c:2a:58:f7:23:11:8b:3d
------BEGIN CERTIFICATE-----
+const validCNWithoutSAN = `-----BEGIN CERTIFICATE-----
 MIIBJzCBzwIUB7q8t9mrDAL+UB1OFaMN5BEWFKQwCgYIKoZIzj0EAwIwFDESMBAG
 A1UECwwJVGVzdCByb290MB4XDTE4MDcxMTE4NDcyNFoXDTI4MDcwODE4NDcyNFow
 GjEYMBYGA1UEAwwPZm9vLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
@@ -1689,48 +1590,9 @@
 p4ULTPdFToFAeqnGHbu62bkms8pQkDAKBggqhkjOPQQDAgNHADBEAiBTbNe3WWFR
 cqUYo0sNUuoV+tCTMDJUS+0PWIW4qBqCOwIgFHdLDn5PCk9kJpfc0O2qZx03hdq0
 h7olHCpY9yMRiz0=
------END CERTIFICATE-----
-`
+-----END CERTIFICATE-----`
 
-const (
-	rootWithoutSKID = `
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number:
-            78:29:2a:dc:2f:12:39:7f:c9:33:93:ea:61:39:7d:70
-        Signature Algorithm: ecdsa-with-SHA256
-        Issuer: O = Acme Co
-        Validity
-            Not Before: Feb  4 22:56:34 2019 GMT
-            Not After : Feb  1 22:56:34 2029 GMT
-        Subject: O = Acme Co
-        Subject Public Key Info:
-            Public Key Algorithm: id-ecPublicKey
-                Public-Key: (256 bit)
-                pub:
-                    04:84:a6:8c:69:53:af:87:4b:39:64:fe:04:24:e6:
-                    d8:fc:d6:46:39:35:0e:92:dc:48:08:7e:02:5f:1e:
-                    07:53:5c:d9:e0:56:c5:82:07:f6:a3:e2:ad:f6:ad:
-                    be:a0:4e:03:87:39:67:0c:9c:46:91:68:6b:0e:8e:
-                    f8:49:97:9d:5b
-                ASN1 OID: prime256v1
-                NIST CURVE: P-256
-        X509v3 extensions:
-            X509v3 Key Usage: critical
-                Digital Signature, Key Encipherment, Certificate Sign
-            X509v3 Extended Key Usage:
-                TLS Web Server Authentication
-            X509v3 Basic Constraints: critical
-                CA:TRUE
-            X509v3 Subject Alternative Name:
-                DNS:example
-    Signature Algorithm: ecdsa-with-SHA256
-         30:46:02:21:00:c6:81:61:61:42:8d:37:e7:d0:c3:72:43:44:
-         17:bd:84:ff:88:81:68:9a:99:08:ab:3c:3a:c0:1e:ea:8c:ba:
-         c0:02:21:00:de:c9:fa:e5:5e:c6:e2:db:23:64:43:a9:37:42:
-         72:92:7f:6e:89:38:ea:9e:2a:a7:fd:2f:ea:9a:ff:20:21:e7
------BEGIN CERTIFICATE-----
+const rootWithoutSKID = `-----BEGIN CERTIFICATE-----
 MIIBbzCCARSgAwIBAgIQeCkq3C8SOX/JM5PqYTl9cDAKBggqhkjOPQQDAjASMRAw
 DgYDVQQKEwdBY21lIENvMB4XDTE5MDIwNDIyNTYzNFoXDTI5MDIwMTIyNTYzNFow
 EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABISm
@@ -1739,49 +1601,9 @@
 BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBIGA1UdEQQLMAmCB2V4YW1wbGUwCgYI
 KoZIzj0EAwIDSQAwRgIhAMaBYWFCjTfn0MNyQ0QXvYT/iIFompkIqzw6wB7qjLrA
 AiEA3sn65V7G4tsjZEOpN0Jykn9uiTjqniqn/S/qmv8gIec=
------END CERTIFICATE-----
-`
-	leafWithAKID = `
-	Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number:
-            f0:8a:62:f0:03:84:a2:cf:69:63:ad:71:3b:b6:5d:8c
-        Signature Algorithm: ecdsa-with-SHA256
-        Issuer: O = Acme Co
-        Validity
-            Not Before: Feb  4 23:06:52 2019 GMT
-            Not After : Feb  1 23:06:52 2029 GMT
-        Subject: O = Acme LLC
-        Subject Public Key Info:
-            Public Key Algorithm: id-ecPublicKey
-                Public-Key: (256 bit)
-                pub:
-                    04:5a:4e:4d:fb:ff:17:f7:b6:13:e8:29:45:34:81:
-                    39:ff:8c:9c:d9:8c:0a:9f:dd:b5:97:4c:2b:20:91:
-                    1c:4f:6b:be:53:27:66:ec:4a:ad:08:93:6d:66:36:
-                    0c:02:70:5d:01:ca:7f:c3:29:e9:4f:00:ba:b4:14:
-                    ec:c5:c3:34:b3
-                ASN1 OID: prime256v1
-                NIST CURVE: P-256
-        X509v3 extensions:
-            X509v3 Key Usage: critical
-                Digital Signature, Key Encipherment
-            X509v3 Extended Key Usage:
-                TLS Web Server Authentication
-            X509v3 Basic Constraints: critical
-                CA:FALSE
-            X509v3 Authority Key Identifier:
-                keyid:C2:2B:5F:91:78:34:26:09:42:8D:6F:51:B2:C5:AF:4C:0B:DE:6A:42
+-----END CERTIFICATE-----`
 
-            X509v3 Subject Alternative Name:
-                DNS:example
-    Signature Algorithm: ecdsa-with-SHA256
-         30:44:02:20:64:e0:ba:56:89:63:ce:22:5e:4f:22:15:fd:3c:
-         35:64:9a:3a:6b:7b:9a:32:a0:7f:f7:69:8c:06:f0:00:58:b8:
-         02:20:09:e4:9f:6d:8b:9e:38:e1:b6:01:d5:ee:32:a4:94:65:
-         93:2a:78:94:bb:26:57:4b:c7:dd:6c:3d:40:2b:63:90
------BEGIN CERTIFICATE-----
+const leafWithAKID = `-----BEGIN CERTIFICATE-----
 MIIBjTCCATSgAwIBAgIRAPCKYvADhKLPaWOtcTu2XYwwCgYIKoZIzj0EAwIwEjEQ
 MA4GA1UEChMHQWNtZSBDbzAeFw0xOTAyMDQyMzA2NTJaFw0yOTAyMDEyMzA2NTJa
 MBMxETAPBgNVBAoTCEFjbWUgTExDMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
@@ -1791,9 +1613,7 @@
 ssWvTAveakIwEgYDVR0RBAswCYIHZXhhbXBsZTAKBggqhkjOPQQDAgNHADBEAiBk
 4LpWiWPOIl5PIhX9PDVkmjpre5oyoH/3aYwG8ABYuAIgCeSfbYueOOG2AdXuMqSU
 ZZMqeJS7JldLx91sPUArY5A=
------END CERTIFICATE-----
-`
-)
+-----END CERTIFICATE-----`
 
 var unknownAuthorityErrorTests = []struct {
 	cert     string
@@ -2129,3 +1949,33 @@
 	}
 	t.Logf("verification took %v", time.Since(start))
 }
+
+func TestSystemRootsError(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("Windows does not use (or support) systemRoots")
+	}
+
+	defer func(oldSystemRoots *CertPool) { systemRoots = oldSystemRoots }(systemRootsPool())
+
+	opts := VerifyOptions{
+		Intermediates: NewCertPool(),
+		DNSName:       "www.google.com",
+		CurrentTime:   time.Unix(1395785200, 0),
+	}
+
+	if ok := opts.Intermediates.AppendCertsFromPEM([]byte(giag2Intermediate)); !ok {
+		t.Fatalf("failed to parse intermediate")
+	}
+
+	leaf, err := certificateFromPEM(googleLeaf)
+	if err != nil {
+		t.Fatalf("failed to parse leaf: %v", err)
+	}
+
+	systemRoots = nil
+
+	_, err = leaf.Verify(opts)
+	if _, ok := err.(SystemRootsError); !ok {
+		t.Errorf("error was not SystemRootsError: %v", err)
+	}
+}