Skip to content

Commit 7a75a55

Browse files
committed
[release-branch.go1.6] crypto/x509: read Darwin trust settings for root CAs
Darwin separately stores bits indicating whether a root certificate should be trusted; this changes Go to read and use those when initializing SystemCertPool. Unfortunately, the trust API is very slow. To avoid a delay of up to 0.5s in initializing the system cert pool, we assume that the trust settings found in kSecTrustSettingsDomainSystem will always indicate trust. (That is, all root certs Apple distributes are trusted.) This is not guaranteed by the API but is true in practice. In the non-cgo codepath, we do not have that benefit, so we must check the trust status of every certificate. This causes about 0.5s of delay in initializing the SystemCertPool. On OS X 10.11 and older, the "security" command requires a certificate to be provided in a file and not on stdin, so the non-cgo codepath creates temporary files for each certificate, further slowing initialization. Updates #18141. Change-Id: If681c514047afe5e1a68de6c9d40ceabbce54755 Reviewed-on: https://go-review.googlesource.com/33721 Run-TryBot: Quentin Smith <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Russ Cox <[email protected]> Reviewed-on: https://go-review.googlesource.com/33728
1 parent 7ae77e2 commit 7a75a55

File tree

4 files changed

+296
-18
lines changed

4 files changed

+296
-18
lines changed

src/crypto/x509/cert_pool.go

+15
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ func (s *CertPool) findVerifiedParents(cert *Certificate) (parents []int, errCer
5252
return
5353
}
5454

55+
func (s *CertPool) contains(cert *Certificate) bool {
56+
if s == nil {
57+
return false
58+
}
59+
60+
candidates := s.byName[string(cert.RawSubject)]
61+
for _, c := range candidates {
62+
if s.certs[c].Equal(cert) {
63+
return true
64+
}
65+
}
66+
67+
return false
68+
}
69+
5570
// AddCert adds a certificate to a pool.
5671
func (s *CertPool) AddCert(cert *Certificate) {
5772
if cert == nil {

src/crypto/x509/root_cgo_darwin.go

+169-15
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,28 @@
77
package x509
88

99
/*
10-
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1060
10+
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1080
1111
#cgo LDFLAGS: -framework CoreFoundation -framework Security
1212
13+
#include <errno.h>
14+
#include <sys/sysctl.h>
15+
1316
#include <CoreFoundation/CoreFoundation.h>
1417
#include <Security/Security.h>
1518
16-
// FetchPEMRoots fetches the system's list of trusted X.509 root certificates.
17-
//
18-
// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root
19-
// certificates of the system. On failure, the function returns -1.
20-
//
21-
// Note: The CFDataRef returned in pemRoots must be released (using CFRelease) after
22-
// we've consumed its content.
23-
int FetchPEMRoots(CFDataRef *pemRoots) {
19+
// FetchPEMRoots_MountainLion is the version of FetchPEMRoots from Go 1.6
20+
// which still works on OS X 10.8 (Mountain Lion).
21+
// It lacks support for admin & user cert domains.
22+
// See golang.org/issue/16473
23+
int FetchPEMRoots_MountainLion(CFDataRef *pemRoots) {
2424
if (pemRoots == NULL) {
2525
return -1;
2626
}
27-
2827
CFArrayRef certs = NULL;
2928
OSStatus err = SecTrustCopyAnchorCertificates(&certs);
3029
if (err != noErr) {
3130
return -1;
3231
}
33-
3432
CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
3533
int i, ncerts = CFArrayGetCount(certs);
3634
for (i = 0; i < ncerts; i++) {
@@ -39,24 +37,164 @@ int FetchPEMRoots(CFDataRef *pemRoots) {
3937
if (cert == NULL) {
4038
continue;
4139
}
42-
4340
// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
4441
// Once we support weak imports via cgo we should prefer that, and fall back to this
4542
// for older systems.
4643
err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
4744
if (err != noErr) {
4845
continue;
4946
}
50-
5147
if (data != NULL) {
5248
CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data));
5349
CFRelease(data);
5450
}
5551
}
56-
5752
CFRelease(certs);
53+
*pemRoots = combinedData;
54+
return 0;
55+
}
56+
57+
// useOldCode reports whether the running machine is OS X 10.8 Mountain Lion
58+
// or older. We only support Mountain Lion and higher, but we'll at least try our
59+
// best on older machines and continue to use the old code path.
60+
//
61+
// See golang.org/issue/16473
62+
int useOldCode() {
63+
char str[256];
64+
size_t size = sizeof(str);
65+
memset(str, 0, size);
66+
sysctlbyname("kern.osrelease", str, &size, NULL, 0);
67+
// OS X 10.8 is osrelease "12.*", 10.7 is 11.*, 10.6 is 10.*.
68+
// We never supported things before that.
69+
return memcmp(str, "12.", 3) == 0 || memcmp(str, "11.", 3) == 0 || memcmp(str, "10.", 3) == 0;
70+
}
5871
72+
// FetchPEMRoots fetches the system's list of trusted X.509 root certificates.
73+
//
74+
// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root
75+
// certificates of the system. On failure, the function returns -1.
76+
// Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots.
77+
//
78+
// Note: The CFDataRef returned in pemRoots and untrustedPemRoots must
79+
// be released (using CFRelease) after we've consumed its content.
80+
int FetchPEMRoots(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots) {
81+
if (useOldCode()) {
82+
return FetchPEMRoots_MountainLion(pemRoots);
83+
}
84+
85+
// Get certificates from all domains, not just System, this lets
86+
// the user add CAs to their "login" keychain, and Admins to add
87+
// to the "System" keychain
88+
SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem,
89+
kSecTrustSettingsDomainAdmin,
90+
kSecTrustSettingsDomainUser };
91+
92+
int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain);
93+
if (pemRoots == NULL) {
94+
return -1;
95+
}
96+
97+
// kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"),
98+
// but the Go linker's internal linking mode can't handle CFSTR relocations.
99+
// Create our own dynamic string instead and release it below.
100+
CFStringRef policy = CFStringCreateWithCString(NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8);
101+
102+
CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
103+
CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
104+
for (int i = 0; i < numDomains; i++) {
105+
CFArrayRef certs = NULL;
106+
OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs);
107+
if (err != noErr) {
108+
continue;
109+
}
110+
111+
CFIndex numCerts = CFArrayGetCount(certs);
112+
for (int j = 0; j < numCerts; j++) {
113+
CFDataRef data = NULL;
114+
CFErrorRef errRef = NULL;
115+
CFArrayRef trustSettings = NULL;
116+
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j);
117+
if (cert == NULL) {
118+
continue;
119+
}
120+
// We only want trusted certs.
121+
int untrusted = 0;
122+
if (i != 0) {
123+
// Certs found in the system domain are always trusted. If the user
124+
// configures "Never Trust" on such a cert, it will also be found in the
125+
// admin or user domain, causing it to be added to untrustedPemRoots. The
126+
// Go code will then clean this up.
127+
128+
// Trust may be stored in any of the domains. According to Apple's
129+
// SecTrustServer.c, "user trust settings overrule admin trust settings",
130+
// so take the last trust settings array we find.
131+
// Skip the system domain since it is always trusted.
132+
for (int k = 1; k < numDomains; k++) {
133+
CFArrayRef domainTrustSettings = NULL;
134+
err = SecTrustSettingsCopyTrustSettings(cert, domains[k], &domainTrustSettings);
135+
if (err == errSecSuccess && domainTrustSettings != NULL) {
136+
if (trustSettings) {
137+
CFRelease(trustSettings);
138+
}
139+
trustSettings = domainTrustSettings;
140+
}
141+
}
142+
if (trustSettings == NULL) {
143+
// "this certificate must be verified to a known trusted certificate"; aka not a root.
144+
continue;
145+
}
146+
for (CFIndex k = 0; k < CFArrayGetCount(trustSettings); k++) {
147+
CFNumberRef cfNum;
148+
CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, k);
149+
if (CFDictionaryGetValueIfPresent(tSetting, policy, (const void**)&cfNum)){
150+
SInt32 result = 0;
151+
CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result);
152+
// TODO: The rest of the dictionary specifies conditions for evaluation.
153+
if (result == kSecTrustSettingsResultDeny) {
154+
untrusted = 1;
155+
}
156+
}
157+
}
158+
CFRelease(trustSettings);
159+
}
160+
// We only want to add Root CAs, so make sure Subject and Issuer Name match
161+
CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, &errRef);
162+
if (errRef != NULL) {
163+
CFRelease(errRef);
164+
continue;
165+
}
166+
CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, &errRef);
167+
if (errRef != NULL) {
168+
CFRelease(subjectName);
169+
CFRelease(errRef);
170+
continue;
171+
}
172+
Boolean equal = CFEqual(subjectName, issuerName);
173+
CFRelease(subjectName);
174+
CFRelease(issuerName);
175+
if (!equal) {
176+
continue;
177+
}
178+
179+
// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
180+
// Once we support weak imports via cgo we should prefer that, and fall back to this
181+
// for older systems.
182+
err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
183+
if (err != noErr) {
184+
continue;
185+
}
186+
187+
if (data != NULL) {
188+
CFMutableDataRef appendTo = untrusted ? combinedUntrustedData : combinedData;
189+
CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data));
190+
CFRelease(data);
191+
}
192+
}
193+
CFRelease(certs);
194+
}
195+
CFRelease(policy);
59196
*pemRoots = combinedData;
197+
*untrustedPemRoots = combinedUntrustedData;
60198
return 0;
61199
}
62200
*/
@@ -67,13 +205,29 @@ func initSystemRoots() {
67205
roots := NewCertPool()
68206

69207
var data C.CFDataRef = nil
70-
err := C.FetchPEMRoots(&data)
208+
var untrustedData C.CFDataRef = nil
209+
err := C.FetchPEMRoots(&data, &untrustedData)
71210
if err == -1 {
72211
return
73212
}
74213

75214
defer C.CFRelease(C.CFTypeRef(data))
76215
buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
77216
roots.AppendCertsFromPEM(buf)
217+
if untrustedData == nil {
218+
systemRoots = roots
219+
return
220+
}
221+
defer C.CFRelease(C.CFTypeRef(untrustedData))
222+
buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData)))
223+
untrustedRoots := NewCertPool()
224+
untrustedRoots.AppendCertsFromPEM(buf)
225+
226+
trustedRoots := NewCertPool()
227+
for _, c := range roots.certs {
228+
if !untrustedRoots.contains(c) {
229+
trustedRoots.AddCert(c)
230+
}
231+
}
78232
systemRoots = roots
79233
}

src/crypto/x509/root_darwin.go

+111-3
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,128 @@
66

77
package x509
88

9-
import "os/exec"
9+
import (
10+
"bytes"
11+
"encoding/pem"
12+
"fmt"
13+
"io/ioutil"
14+
"os"
15+
"os/exec"
16+
"strconv"
17+
"sync"
18+
"syscall"
19+
)
1020

1121
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
1222
return nil, nil
1323
}
1424

25+
// This code is only used when compiling without cgo.
26+
// It is here, instead of root_nocgo_darwin.go, so that tests can check it
27+
// even if the tests are run with cgo enabled.
28+
// The linker will not include these unused functions in binaries built with cgo enabled.
29+
1530
func execSecurityRoots() (*CertPool, error) {
1631
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
1732
data, err := cmd.Output()
1833
if err != nil {
1934
return nil, err
2035
}
2136

22-
roots := NewCertPool()
23-
roots.AppendCertsFromPEM(data)
37+
var (
38+
mu sync.Mutex
39+
roots = NewCertPool()
40+
)
41+
add := func(cert *Certificate) {
42+
mu.Lock()
43+
defer mu.Unlock()
44+
roots.AddCert(cert)
45+
}
46+
blockCh := make(chan *pem.Block)
47+
var wg sync.WaitGroup
48+
for i := 0; i < 4; i++ {
49+
wg.Add(1)
50+
go func() {
51+
defer wg.Done()
52+
for block := range blockCh {
53+
verifyCertWithSystem(block, add)
54+
}
55+
}()
56+
}
57+
for len(data) > 0 {
58+
var block *pem.Block
59+
block, data = pem.Decode(data)
60+
if block == nil {
61+
break
62+
}
63+
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
64+
continue
65+
}
66+
blockCh <- block
67+
}
68+
close(blockCh)
69+
wg.Wait()
2470
return roots, nil
2571
}
72+
73+
func verifyCertWithSystem(block *pem.Block, add func(*Certificate)) {
74+
data := pem.EncodeToMemory(block)
75+
var cmd *exec.Cmd
76+
if needsTmpFiles() {
77+
f, err := ioutil.TempFile("", "cert")
78+
if err != nil {
79+
fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
80+
return
81+
}
82+
defer os.Remove(f.Name())
83+
if _, err := f.Write(data); err != nil {
84+
fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
85+
return
86+
}
87+
if err := f.Close(); err != nil {
88+
fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
89+
return
90+
}
91+
cmd = exec.Command("/usr/bin/security", "verify-cert", "-c", f.Name(), "-l")
92+
} else {
93+
cmd = exec.Command("/usr/bin/security", "verify-cert", "-c", "/dev/stdin", "-l")
94+
cmd.Stdin = bytes.NewReader(data)
95+
}
96+
if cmd.Run() == nil {
97+
// Non-zero exit means untrusted
98+
cert, err := ParseCertificate(block.Bytes)
99+
if err != nil {
100+
return
101+
}
102+
103+
add(cert)
104+
}
105+
}
106+
107+
var versionCache struct {
108+
sync.Once
109+
major int
110+
}
111+
112+
// needsTmpFiles reports whether the OS is <= 10.11 (which requires real
113+
// files as arguments to the security command).
114+
func needsTmpFiles() bool {
115+
versionCache.Do(func() {
116+
release, err := syscall.Sysctl("kern.osrelease")
117+
if err != nil {
118+
return
119+
}
120+
for i, c := range release {
121+
if c == '.' {
122+
release = release[:i]
123+
break
124+
}
125+
}
126+
major, err := strconv.Atoi(release)
127+
if err != nil {
128+
return
129+
}
130+
versionCache.major = major
131+
})
132+
return versionCache.major <= 15
133+
}

0 commit comments

Comments
 (0)