• Filippo Valsorda's avatar
    crypto/x509: re-enable TestSystemRoots · 9536c5fa
    Filippo Valsorda authored
    Now that the cgo and no-cgo paths should be correct and equivalent,
    re-enable the TestSystemRoots test without any margin of error (which
    was tripping anyway when users had too many of a certain edge-case).
    
    As a last quirk, the verify-cert invocation will validate certificates
    that aren't roots, but are signed by valid roots. Ignore them.
    
    Fixes #24652
    
    Change-Id: I6a8ff3c2282136d7122a4e7e387eb8014da0d28a
    Reviewed-on: https://go-review.googlesource.com/c/128117
    TryBot-Result: Gobot Gobot <gobot@golang.org>
    Run-TryBot: Filippo Valsorda <filippo@golang.org>
    Reviewed-by: default avatarAdam Langley <agl@golang.org>
    9536c5fa
root_darwin_test.go 3.8 KB
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package x509

import (
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"testing"
	"time"
)

func TestSystemRoots(t *testing.T) {
	switch runtime.GOARCH {
	case "arm", "arm64":
		t.Skipf("skipping on %s/%s, no system root", runtime.GOOS, runtime.GOARCH)
	}

	t0 := time.Now()
	sysRoots := systemRootsPool() // actual system roots
	sysRootsDuration := time.Since(t0)

	t1 := time.Now()
	execRoots, err := execSecurityRoots() // non-cgo roots
	execSysRootsDuration := time.Since(t1)

	if err != nil {
		t.Fatalf("failed to read system roots: %v", err)
	}

	t.Logf("    cgo sys roots: %v", sysRootsDuration)
	t.Logf("non-cgo sys roots: %v", execSysRootsDuration)

	// On Mavericks, there are 212 bundled certs, at least there was at
	// one point in time on one machine. (Maybe it was a corp laptop
	// with extra certs?) Other OS X users report 135, 142, 145...
	// Let's try requiring at least 100, since this is just a sanity
	// check.
	if want, have := 100, len(sysRoots.certs); have < want {
		t.Errorf("want at least %d system roots, have %d", want, have)
	}

	// Fetch any intermediate certificate that verify-cert might be aware of.
	out, err := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p",
		"/Library/Keychains/System.keychain",
		filepath.Join(os.Getenv("HOME"), "/Library/Keychains/login.keychain"),
		filepath.Join(os.Getenv("HOME"), "/Library/Keychains/login.keychain-db")).Output()
	if err != nil {
		t.Fatal(err)
	}
	allCerts := NewCertPool()
	allCerts.AppendCertsFromPEM(out)

	// Check that the two cert pools are the same.
	sysPool := make(map[string]*Certificate, len(sysRoots.certs))
	for _, c := range sysRoots.certs {
		sysPool[string(c.Raw)] = c
	}
	for _, c := range execRoots.certs {
		if _, ok := sysPool[string(c.Raw)]; ok {
			delete(sysPool, string(c.Raw))
		} else {
			// verify-cert lets in certificates that are not trusted roots, but are
			// signed by trusted roots. This should not be a problem, so confirm that's
			// the case and skip them.
			if _, err := c.Verify(VerifyOptions{
				Roots:         sysRoots,
				Intermediates: allCerts,
				KeyUsages:     []ExtKeyUsage{ExtKeyUsageAny},
			}); err != nil {
				t.Errorf("certificate only present in non-cgo pool: %v (verify error: %v)", c.Subject, err)
			} else {
				t.Logf("signed certificate only present in non-cgo pool (acceptable): %v", c.Subject)
			}
		}
	}
	for _, c := range sysPool {
		// The nocgo codepath uses verify-cert with the ssl policy, which also
		// happens to check EKUs, so some certificates will appear only in the
		// cgo pool. We can't easily make them consistent because the EKU check
		// is only applied to the certificates passed to verify-cert.
		var ekuOk bool
		for _, eku := range c.ExtKeyUsage {
			if eku == ExtKeyUsageServerAuth || eku == ExtKeyUsageNetscapeServerGatedCrypto ||
				eku == ExtKeyUsageMicrosoftServerGatedCrypto || eku == ExtKeyUsageAny {
				ekuOk = true
			}
		}
		if len(c.ExtKeyUsage) == 0 && len(c.UnknownExtKeyUsage) == 0 {
			ekuOk = true
		}
		if !ekuOk {
			t.Logf("off-EKU certificate only present in cgo pool (acceptable): %v", c.Subject)
			continue
		}

		// Same for expired certificates. We don't chain to them anyway.
		now := time.Now()
		if now.Before(c.NotBefore) || now.After(c.NotAfter) {
			t.Logf("expired certificate only present in cgo pool (acceptable): %v", c.Subject)
			continue
		}

		t.Errorf("certificate only present in cgo pool: %v", c.Subject)
	}

	if t.Failed() && debugDarwinRoots {
		cmd := exec.Command("security", "dump-trust-settings")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
		cmd = exec.Command("security", "dump-trust-settings", "-d")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
	}
}