Skip to content

Commit f6be1cf

Browse files
committed
crypto/x509: fix root CA extraction on macOS (cgo path)
The cgo path was not taking policies into account, using the last security setting in the array whatever it was. Also, it was not aware of the defaults for empty security settings, and for security settings without a result type. Finally, certificates restricted to a hostname were considered roots. The API docs for this code are partial and not very clear, so this is a best effort, really. Updates #24652 Change-Id: I8fa2fe4706f44f3d963b32e0615d149e997b537d Reviewed-on: https://go-review.googlesource.com/c/128056 Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Adam Langley <[email protected]> Reviewed-by: Adam Langley <[email protected]>
1 parent fb69478 commit f6be1cf

File tree

2 files changed

+192
-84
lines changed

2 files changed

+192
-84
lines changed

src/crypto/x509/root_cgo_darwin.go

+183-75
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,164 @@ package x509
1616
#include <CoreFoundation/CoreFoundation.h>
1717
#include <Security/Security.h>
1818
19-
// FetchPEMRoots fetches the system's list of trusted X.509 root certificates.
19+
static bool isSSLPolicy(SecPolicyRef policyRef) {
20+
if (!policyRef) {
21+
return false;
22+
}
23+
CFDictionaryRef properties = SecPolicyCopyProperties(policyRef);
24+
if (properties == NULL) {
25+
return false;
26+
}
27+
CFTypeRef value = NULL;
28+
if (CFDictionaryGetValueIfPresent(properties, kSecPolicyOid, (const void **)&value)) {
29+
CFRelease(properties);
30+
return CFEqual(value, kSecPolicyAppleSSL);
31+
}
32+
CFRelease(properties);
33+
return false;
34+
}
35+
36+
// sslTrustSettingsResult obtains the final kSecTrustSettingsResult value
37+
// for a certificate in the user or admin domain, combining usage constraints
38+
// for the SSL SecTrustSettingsPolicy, ignoring SecTrustSettingsKeyUsage and
39+
// kSecTrustSettingsAllowedError.
40+
// https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting
41+
static SInt32 sslTrustSettingsResult(SecCertificateRef cert) {
42+
CFArrayRef trustSettings = NULL;
43+
OSStatus err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings);
44+
45+
// According to Apple's SecTrustServer.c, "user trust settings overrule admin trust settings",
46+
// but the rules of the override are unclear. Let's assume admin trust settings are applicable
47+
// if and only if user trust settings fail to load or are NULL.
48+
if (err != errSecSuccess || trustSettings == NULL) {
49+
if (trustSettings != NULL) CFRelease(trustSettings);
50+
err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainAdmin, &trustSettings);
51+
}
52+
53+
// > no trust settings [...] means "this certificate must be verified to a known trusted certificate”
54+
if (err != errSecSuccess || trustSettings == NULL) {
55+
if (trustSettings != NULL) CFRelease(trustSettings);
56+
return kSecTrustSettingsResultUnspecified;
57+
}
58+
59+
// > An empty trust settings array means "always trust this certificate” with an
60+
// > overall trust setting for the certificate of kSecTrustSettingsResultTrustRoot.
61+
if (CFArrayGetCount(trustSettings) == 0) {
62+
CFRelease(trustSettings);
63+
return kSecTrustSettingsResultTrustRoot;
64+
}
65+
66+
// kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"),
67+
// but the Go linker's internal linking mode can't handle CFSTR relocations.
68+
// Create our own dynamic string instead and release it below.
69+
CFStringRef _kSecTrustSettingsResult = CFStringCreateWithCString(
70+
NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8);
71+
CFStringRef _kSecTrustSettingsPolicy = CFStringCreateWithCString(
72+
NULL, "kSecTrustSettingsPolicy", kCFStringEncodingUTF8);
73+
CFStringRef _kSecTrustSettingsPolicyString = CFStringCreateWithCString(
74+
NULL, "kSecTrustSettingsPolicyString", kCFStringEncodingUTF8);
75+
76+
CFIndex m; SInt32 result = 0;
77+
for (m = 0; m < CFArrayGetCount(trustSettings); m++) {
78+
CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, m);
79+
80+
// First, check if this trust setting applies to our policy. We assume
81+
// only one will. The docs suggest that there might be multiple applying
82+
// but don't explain how to combine them.
83+
SecPolicyRef policyRef;
84+
if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsPolicy, (const void**)&policyRef)) {
85+
if (!isSSLPolicy(policyRef)) {
86+
continue;
87+
}
88+
} else {
89+
continue;
90+
}
91+
92+
if (CFDictionaryContainsKey(tSetting, _kSecTrustSettingsPolicyString)) {
93+
// Restricted to a hostname, not a root.
94+
continue;
95+
}
96+
97+
CFNumberRef cfNum;
98+
if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsResult, (const void**)&cfNum)) {
99+
CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result);
100+
} else {
101+
// > If the value of the kSecTrustSettingsResult component is not
102+
// > kSecTrustSettingsResultUnspecified for a usage constraints dictionary that has
103+
// > no constraints, the default value kSecTrustSettingsResultTrustRoot is assumed.
104+
result = kSecTrustSettingsResultTrustRoot;
105+
}
106+
107+
break;
108+
}
109+
110+
// If trust settings are present, but none of them match the policy...
111+
// the docs don't tell us what to do.
112+
//
113+
// "Trust settings for a given use apply if any of the dictionaries in the
114+
// certificate’s trust settings array satisfies the specified use." suggests
115+
// that it's as if there were no trust settings at all, so we should probably
116+
// fallback to the admin trust settings. TODO.
117+
if (result == 0) {
118+
result = kSecTrustSettingsResultUnspecified;
119+
}
120+
121+
CFRelease(_kSecTrustSettingsPolicy);
122+
CFRelease(_kSecTrustSettingsPolicyString);
123+
CFRelease(_kSecTrustSettingsResult);
124+
CFRelease(trustSettings);
125+
126+
return result;
127+
}
128+
129+
// isRootCertificate reports whether Subject and Issuer match.
130+
static Boolean isRootCertificate(SecCertificateRef cert, CFErrorRef *errRef) {
131+
CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, errRef);
132+
if (*errRef != NULL) {
133+
return false;
134+
}
135+
CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, errRef);
136+
if (*errRef != NULL) {
137+
CFRelease(subjectName);
138+
return false;
139+
}
140+
Boolean equal = CFEqual(subjectName, issuerName);
141+
CFRelease(subjectName);
142+
CFRelease(issuerName);
143+
return equal;
144+
}
145+
146+
// FetchPEMRoots fetches the system's list of trusted X.509 root certificates
147+
// for the kSecTrustSettingsPolicy SSL.
20148
//
21149
// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root
22150
// certificates of the system. On failure, the function returns -1.
23151
// Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots.
24152
//
25153
// Note: The CFDataRef returned in pemRoots and untrustedPemRoots must
26154
// be released (using CFRelease) after we've consumed its content.
27-
int FetchPEMRoots(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots) {
155+
int FetchPEMRoots(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots, bool debugDarwinRoots) {
28156
int i;
29157
158+
if (debugDarwinRoots) {
159+
printf("crypto/x509: kSecTrustSettingsResultInvalid = %d\n", kSecTrustSettingsResultInvalid);
160+
printf("crypto/x509: kSecTrustSettingsResultTrustRoot = %d\n", kSecTrustSettingsResultTrustRoot);
161+
printf("crypto/x509: kSecTrustSettingsResultTrustAsRoot = %d\n", kSecTrustSettingsResultTrustAsRoot);
162+
printf("crypto/x509: kSecTrustSettingsResultDeny = %d\n", kSecTrustSettingsResultDeny);
163+
printf("crypto/x509: kSecTrustSettingsResultUnspecified = %d\n", kSecTrustSettingsResultUnspecified);
164+
}
165+
30166
// Get certificates from all domains, not just System, this lets
31167
// the user add CAs to their "login" keychain, and Admins to add
32168
// to the "System" keychain
33169
SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem,
34-
kSecTrustSettingsDomainAdmin,
35-
kSecTrustSettingsDomainUser };
170+
kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainUser };
36171
37172
int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain);
38173
if (pemRoots == NULL) {
39174
return -1;
40175
}
41176
42-
// kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"),
43-
// but the Go linker's internal linking mode can't handle CFSTR relocations.
44-
// Create our own dynamic string instead and release it below.
45-
CFStringRef policy = CFStringCreateWithCString(NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8);
46-
47177
CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
48178
CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
49179
for (i = 0; i < numDomains; i++) {
@@ -57,102 +187,81 @@ int FetchPEMRoots(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots) {
57187
CFIndex numCerts = CFArrayGetCount(certs);
58188
for (j = 0; j < numCerts; j++) {
59189
CFDataRef data = NULL;
60-
CFErrorRef errRef = NULL;
61190
CFArrayRef trustSettings = NULL;
62191
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j);
63192
if (cert == NULL) {
64193
continue;
65194
}
66-
// We only want trusted certs.
67-
int untrusted = 0;
68-
int trustAsRoot = 0;
69-
int trustRoot = 0;
70-
if (i == 0) {
71-
trustAsRoot = 1;
72-
} else {
73-
int k;
74-
CFIndex m;
75195
196+
SInt32 result;
197+
if (domains[i] == kSecTrustSettingsDomainSystem) {
76198
// Certs found in the system domain are always trusted. If the user
77199
// configures "Never Trust" on such a cert, it will also be found in the
78200
// admin or user domain, causing it to be added to untrustedPemRoots. The
79201
// Go code will then clean this up.
80-
81-
// Trust may be stored in any of the domains. According to Apple's
82-
// SecTrustServer.c, "user trust settings overrule admin trust settings",
83-
// so take the last trust settings array we find.
84-
// Skip the system domain since it is always trusted.
85-
for (k = i; k < numDomains; k++) {
86-
CFArrayRef domainTrustSettings = NULL;
87-
err = SecTrustSettingsCopyTrustSettings(cert, domains[k], &domainTrustSettings);
88-
if (err == errSecSuccess && domainTrustSettings != NULL) {
89-
if (trustSettings) {
90-
CFRelease(trustSettings);
91-
}
92-
trustSettings = domainTrustSettings;
202+
result = kSecTrustSettingsResultTrustRoot;
203+
} else {
204+
result = sslTrustSettingsResult(cert);
205+
if (debugDarwinRoots) {
206+
CFErrorRef errRef = NULL;
207+
CFStringRef summary = SecCertificateCopyShortDescription(NULL, cert, &errRef);
208+
if (errRef != NULL) {
209+
printf("crypto/x509: SecCertificateCopyShortDescription failed\n");
210+
CFRelease(errRef);
211+
continue;
93212
}
94-
}
95-
if (trustSettings == NULL) {
96-
// "this certificate must be verified to a known trusted certificate"; aka not a root.
97-
continue;
98-
}
99-
for (m = 0; m < CFArrayGetCount(trustSettings); m++) {
100-
CFNumberRef cfNum;
101-
CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, m);
102-
if (CFDictionaryGetValueIfPresent(tSetting, policy, (const void**)&cfNum)){
103-
SInt32 result = 0;
104-
CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result);
105-
// TODO: The rest of the dictionary specifies conditions for evaluation.
106-
if (result == kSecTrustSettingsResultDeny) {
107-
untrusted = 1;
108-
} else if (result == kSecTrustSettingsResultTrustAsRoot) {
109-
trustAsRoot = 1;
110-
} else if (result == kSecTrustSettingsResultTrustRoot) {
111-
trustRoot = 1;
112-
}
213+
214+
CFIndex length = CFStringGetLength(summary);
215+
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
216+
char *buffer = malloc(maxSize);
217+
if (CFStringGetCString(summary, buffer, maxSize, kCFStringEncodingUTF8)) {
218+
printf("crypto/x509: %s returned %d\n", buffer, result);
113219
}
220+
free(buffer);
221+
CFRelease(summary);
114222
}
115-
CFRelease(trustSettings);
116223
}
117224
118-
if (trustRoot) {
119-
// We only want to add Root CAs, so make sure Subject and Issuer Name match
120-
CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, &errRef);
121-
if (errRef != NULL) {
122-
CFRelease(errRef);
123-
continue;
124-
}
125-
CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, &errRef);
126-
if (errRef != NULL) {
127-
CFRelease(subjectName);
128-
CFRelease(errRef);
225+
CFMutableDataRef appendTo;
226+
// > Note the distinction between the results kSecTrustSettingsResultTrustRoot
227+
// > and kSecTrustSettingsResultTrustAsRoot: The former can only be applied to
228+
// > root (self-signed) certificates; the latter can only be applied to
229+
// > non-root certificates.
230+
if (result == kSecTrustSettingsResultTrustRoot) {
231+
CFErrorRef errRef = NULL;
232+
if (!isRootCertificate(cert, &errRef) || errRef != NULL) {
233+
if (errRef != NULL) CFRelease(errRef);
129234
continue;
130235
}
131-
Boolean equal = CFEqual(subjectName, issuerName);
132-
CFRelease(subjectName);
133-
CFRelease(issuerName);
134-
if (!equal) {
236+
237+
appendTo = combinedData;
238+
} else if (result == kSecTrustSettingsResultTrustAsRoot) {
239+
CFErrorRef errRef = NULL;
240+
if (isRootCertificate(cert, &errRef) || errRef != NULL) {
241+
if (errRef != NULL) CFRelease(errRef);
135242
continue;
136243
}
244+
245+
appendTo = combinedData;
246+
} else if (result == kSecTrustSettingsResultDeny) {
247+
appendTo = combinedUntrustedData;
248+
} else if (result == kSecTrustSettingsResultUnspecified) {
249+
continue;
250+
} else {
251+
continue;
137252
}
138253
139254
err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
140255
if (err != noErr) {
141256
continue;
142257
}
143-
144258
if (data != NULL) {
145-
if (!trustRoot && !trustAsRoot) {
146-
untrusted = 1;
147-
}
148-
CFMutableDataRef appendTo = untrusted ? combinedUntrustedData : combinedData;
149259
CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data));
150260
CFRelease(data);
151261
}
152262
}
153263
CFRelease(certs);
154264
}
155-
CFRelease(policy);
156265
*pemRoots = combinedData;
157266
*untrustedPemRoots = combinedUntrustedData;
158267
return 0;
@@ -169,9 +278,8 @@ func loadSystemRoots() (*CertPool, error) {
169278

170279
var data C.CFDataRef = 0
171280
var untrustedData C.CFDataRef = 0
172-
err := C.FetchPEMRoots(&data, &untrustedData)
281+
err := C.FetchPEMRoots(&data, &untrustedData, C.bool(debugDarwinRoots))
173282
if err == -1 {
174-
// TODO: better error message
175283
return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo")
176284
}
177285

src/crypto/x509/root_darwin.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"sync"
2222
)
2323

24-
var debugExecDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
24+
var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
2525

2626
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
2727
return nil, nil
@@ -57,7 +57,7 @@ func execSecurityRoots() (*CertPool, error) {
5757
if err != nil {
5858
return nil, err
5959
}
60-
if debugExecDarwinRoots {
60+
if debugDarwinRoots {
6161
println(fmt.Sprintf("crypto/x509: %d certs have a trust policy", len(hasPolicy)))
6262
}
6363

@@ -68,8 +68,8 @@ func execSecurityRoots() (*CertPool, error) {
6868

6969
home, err := os.UserHomeDir()
7070
if err != nil {
71-
if debugExecDarwinRoots {
72-
println("crypto/x509: can't get user home directory: %v", err)
71+
if debugDarwinRoots {
72+
println(fmt.Sprintf("crypto/x509: can't get user home directory: %v", err))
7373
}
7474
} else {
7575
args = append(args,
@@ -147,7 +147,7 @@ func execSecurityRoots() (*CertPool, error) {
147147
close(blockCh)
148148
wg.Wait()
149149

150-
if debugExecDarwinRoots {
150+
if debugDarwinRoots {
151151
mu.Lock()
152152
defer mu.Unlock()
153153
println(fmt.Sprintf("crypto/x509: ran security verify-cert %d times", numVerified))
@@ -175,16 +175,16 @@ func verifyCertWithSystem(block *pem.Block, cert *Certificate) bool {
175175
}
176176
cmd := exec.Command("/usr/bin/security", "verify-cert", "-c", f.Name(), "-l", "-L")
177177
var stderr bytes.Buffer
178-
if debugExecDarwinRoots {
178+
if debugDarwinRoots {
179179
cmd.Stderr = &stderr
180180
}
181181
if err := cmd.Run(); err != nil {
182-
if debugExecDarwinRoots {
182+
if debugDarwinRoots {
183183
println(fmt.Sprintf("crypto/x509: verify-cert rejected %s: %q", cert.Subject, bytes.TrimSpace(stderr.Bytes())))
184184
}
185185
return false
186186
}
187-
if debugExecDarwinRoots {
187+
if debugDarwinRoots {
188188
println(fmt.Sprintf("crypto/x509: verify-cert approved %s", cert.Subject))
189189
}
190190
return true
@@ -217,7 +217,7 @@ func getCertsWithTrustPolicy() (map[string]bool, error) {
217217
// Rather than match on English substrings that are probably
218218
// localized on macOS, just interpret any failure to mean that
219219
// there are no trust settings.
220-
if debugExecDarwinRoots {
220+
if debugDarwinRoots {
221221
println(fmt.Sprintf("crypto/x509: exec %q: %v, %s", cmd.Args, err, stderr.Bytes()))
222222
}
223223
return nil

0 commit comments

Comments
 (0)