5
5
package x509
6
6
7
7
import (
8
+ "os"
9
+ "os/exec"
10
+ "path/filepath"
8
11
"runtime"
9
12
"testing"
10
13
"time"
@@ -16,11 +19,6 @@ func TestSystemRoots(t *testing.T) {
16
19
t .Skipf ("skipping on %s/%s, no system root" , runtime .GOOS , runtime .GOARCH )
17
20
}
18
21
19
- switch runtime .GOOS {
20
- case "darwin" :
21
- t .Skipf ("skipping on %s/%s until golang.org/issue/24652 has been resolved." , runtime .GOOS , runtime .GOARCH )
22
- }
23
-
24
22
t0 := time .Now ()
25
23
sysRoots := systemRootsPool () // actual system roots
26
24
sysRootsDuration := time .Since (t0 )
@@ -36,45 +34,87 @@ func TestSystemRoots(t *testing.T) {
36
34
t .Logf (" cgo sys roots: %v" , sysRootsDuration )
37
35
t .Logf ("non-cgo sys roots: %v" , execSysRootsDuration )
38
36
39
- for _ , tt := range []* CertPool {sysRoots , execRoots } {
40
- if tt == nil {
41
- t .Fatal ("no system roots" )
42
- }
43
- // On Mavericks, there are 212 bundled certs, at least
44
- // there was at one point in time on one machine.
45
- // (Maybe it was a corp laptop with extra certs?)
46
- // Other OS X users report
47
- // 135, 142, 145... Let's try requiring at least 100,
48
- // since this is just a sanity check.
49
- t .Logf ("got %d roots" , len (tt .certs ))
50
- if want , have := 100 , len (tt .certs ); have < want {
51
- t .Fatalf ("want at least %d system roots, have %d" , want , have )
52
- }
37
+ // On Mavericks, there are 212 bundled certs, at least there was at
38
+ // one point in time on one machine. (Maybe it was a corp laptop
39
+ // with extra certs?) Other OS X users report 135, 142, 145...
40
+ // Let's try requiring at least 100, since this is just a sanity
41
+ // check.
42
+ if want , have := 100 , len (sysRoots .certs ); have < want {
43
+ t .Errorf ("want at least %d system roots, have %d" , want , have )
53
44
}
54
45
55
- // Check that the two cert pools are roughly the same;
56
- // |A∩B| > max(|A|, |B|) / 2 should be a reasonably robust check.
46
+ // Fetch any intermediate certificate that verify-cert might be aware of.
47
+ out , err := exec .Command ("/usr/bin/security" , "find-certificate" , "-a" , "-p" ,
48
+ "/Library/Keychains/System.keychain" ,
49
+ filepath .Join (os .Getenv ("HOME" ), "/Library/Keychains/login.keychain" ),
50
+ filepath .Join (os .Getenv ("HOME" ), "/Library/Keychains/login.keychain-db" )).Output ()
51
+ if err != nil {
52
+ t .Fatal (err )
53
+ }
54
+ allCerts := NewCertPool ()
55
+ allCerts .AppendCertsFromPEM (out )
57
56
58
- isect := make (map [string ]bool , len (sysRoots .certs ))
57
+ // Check that the two cert pools are the same.
58
+ sysPool := make (map [string ]* Certificate , len (sysRoots .certs ))
59
59
for _ , c := range sysRoots .certs {
60
- isect [string (c .Raw )] = true
60
+ sysPool [string (c .Raw )] = c
61
61
}
62
-
63
- have := 0
64
62
for _ , c := range execRoots .certs {
65
- if isect [string (c .Raw )] {
66
- have ++
63
+ if _ , ok := sysPool [string (c .Raw )]; ok {
64
+ delete (sysPool , string (c .Raw ))
65
+ } else {
66
+ // verify-cert lets in certificates that are not trusted roots, but are
67
+ // signed by trusted roots. This should not be a problem, so confirm that's
68
+ // the case and skip them.
69
+ if _ , err := c .Verify (VerifyOptions {
70
+ Roots : sysRoots ,
71
+ Intermediates : allCerts ,
72
+ KeyUsages : []ExtKeyUsage {ExtKeyUsageAny },
73
+ }); err != nil {
74
+ t .Errorf ("certificate only present in non-cgo pool: %v (verify error: %v)" , c .Subject , err )
75
+ } else {
76
+ t .Logf ("signed certificate only present in non-cgo pool (acceptable): %v" , c .Subject )
77
+ }
67
78
}
68
79
}
80
+ for _ , c := range sysPool {
81
+ // The nocgo codepath uses verify-cert with the ssl policy, which also
82
+ // happens to check EKUs, so some certificates will appear only in the
83
+ // cgo pool. We can't easily make them consistent because the EKU check
84
+ // is only applied to the certificates passed to verify-cert.
85
+ var ekuOk bool
86
+ for _ , eku := range c .ExtKeyUsage {
87
+ if eku == ExtKeyUsageServerAuth || eku == ExtKeyUsageNetscapeServerGatedCrypto ||
88
+ eku == ExtKeyUsageMicrosoftServerGatedCrypto || eku == ExtKeyUsageAny {
89
+ ekuOk = true
90
+ }
91
+ }
92
+ if len (c .ExtKeyUsage ) == 0 && len (c .UnknownExtKeyUsage ) == 0 {
93
+ ekuOk = true
94
+ }
95
+ if ! ekuOk {
96
+ t .Logf ("off-EKU certificate only present in cgo pool (acceptable): %v" , c .Subject )
97
+ continue
98
+ }
99
+
100
+ // Same for expired certificates. We don't chain to them anyway.
101
+ now := time .Now ()
102
+ if now .Before (c .NotBefore ) || now .After (c .NotAfter ) {
103
+ t .Logf ("expired certificate only present in cgo pool (acceptable): %v" , c .Subject )
104
+ continue
105
+ }
69
106
70
- var want int
71
- if nsys , nexec := len (sysRoots .certs ), len (execRoots .certs ); nsys > nexec {
72
- want = nsys / 2
73
- } else {
74
- want = nexec / 2
107
+ t .Errorf ("certificate only present in cgo pool: %v" , c .Subject )
75
108
}
76
109
77
- if have < want {
78
- t .Errorf ("insufficient overlap between cgo and non-cgo roots; want at least %d, have %d" , want , have )
110
+ if t .Failed () && debugDarwinRoots {
111
+ cmd := exec .Command ("security" , "dump-trust-settings" )
112
+ cmd .Stdout = os .Stdout
113
+ cmd .Stderr = os .Stderr
114
+ cmd .Run ()
115
+ cmd = exec .Command ("security" , "dump-trust-settings" , "-d" )
116
+ cmd .Stdout = os .Stdout
117
+ cmd .Stderr = os .Stderr
118
+ cmd .Run ()
79
119
}
80
120
}
0 commit comments