1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build cgo && !arm && !arm64 && !ios 6 // +build cgo,!arm,!arm64,!ios 7 8 package x509 9 10 /* 11 #cgo CFLAGS: -mmacosx-version-min=10.10 -D__MAC_OS_X_VERSION_MAX_ALLOWED=101300 12 #cgo LDFLAGS: -framework CoreFoundation -framework Security 13 14 #include <errno.h> 15 #include <sys/sysctl.h> 16 17 #include <CoreFoundation/CoreFoundation.h> 18 #include <Security/Security.h> 19 20 static Boolean isSSLPolicy(SecPolicyRef policyRef) { 21 if (!policyRef) { 22 return false; 23 } 24 CFDictionaryRef properties = SecPolicyCopyProperties(policyRef); 25 if (properties == NULL) { 26 return false; 27 } 28 Boolean isSSL = false; 29 CFTypeRef value = NULL; 30 if (CFDictionaryGetValueIfPresent(properties, kSecPolicyOid, (const void **)&value)) { 31 isSSL = CFEqual(value, kSecPolicyAppleSSL); 32 } 33 CFRelease(properties); 34 return isSSL; 35 } 36 37 // sslTrustSettingsResult obtains the final kSecTrustSettingsResult value 38 // for a certificate in the user or admin domain, combining usage constraints 39 // for the SSL SecTrustSettingsPolicy, ignoring SecTrustSettingsKeyUsage and 40 // kSecTrustSettingsAllowedError. 41 // https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting 42 static SInt32 sslTrustSettingsResult(SecCertificateRef cert) { 43 CFArrayRef trustSettings = NULL; 44 OSStatus err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings); 45 46 // According to Apple's SecTrustServer.c, "user trust settings overrule admin trust settings", 47 // but the rules of the override are unclear. Let's assume admin trust settings are applicable 48 // if and only if user trust settings fail to load or are NULL. 49 if (err != errSecSuccess || trustSettings == NULL) { 50 if (trustSettings != NULL) CFRelease(trustSettings); 51 err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainAdmin, &trustSettings); 52 } 53 54 // > no trust settings [...] means "this certificate must be verified to a known trusted certificate” 55 // (Should this cause a fallback from user to admin domain? It's unclear.) 56 if (err != errSecSuccess || trustSettings == NULL) { 57 if (trustSettings != NULL) CFRelease(trustSettings); 58 return kSecTrustSettingsResultUnspecified; 59 } 60 61 // > An empty trust settings array means "always trust this certificate” with an 62 // > overall trust setting for the certificate of kSecTrustSettingsResultTrustRoot. 63 if (CFArrayGetCount(trustSettings) == 0) { 64 CFRelease(trustSettings); 65 return kSecTrustSettingsResultTrustRoot; 66 } 67 68 // kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"), 69 // but the Go linker's internal linking mode can't handle CFSTR relocations. 70 // Create our own dynamic string instead and release it below. 71 CFStringRef _kSecTrustSettingsResult = CFStringCreateWithCString( 72 NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8); 73 CFStringRef _kSecTrustSettingsPolicy = CFStringCreateWithCString( 74 NULL, "kSecTrustSettingsPolicy", kCFStringEncodingUTF8); 75 CFStringRef _kSecTrustSettingsPolicyString = CFStringCreateWithCString( 76 NULL, "kSecTrustSettingsPolicyString", kCFStringEncodingUTF8); 77 78 CFIndex m; SInt32 result = 0; 79 for (m = 0; m < CFArrayGetCount(trustSettings); m++) { 80 CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, m); 81 82 // First, check if this trust setting is constrained to a non-SSL policy. 83 SecPolicyRef policyRef; 84 if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsPolicy, (const void**)&policyRef)) { 85 if (!isSSLPolicy(policyRef)) { 86 continue; 87 } 88 } 89 90 if (CFDictionaryContainsKey(tSetting, _kSecTrustSettingsPolicyString)) { 91 // Restricted to a hostname, not a root. 92 continue; 93 } 94 95 CFNumberRef cfNum; 96 if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsResult, (const void**)&cfNum)) { 97 CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result); 98 } else { 99 // > If this key is not present, a default value of 100 // > kSecTrustSettingsResultTrustRoot is assumed. 101 result = kSecTrustSettingsResultTrustRoot; 102 } 103 104 // If multiple dictionaries match, we are supposed to "OR" them, 105 // the semantics of which are not clear. Since TrustRoot and TrustAsRoot 106 // are mutually exclusive, Deny should probably override, and Invalid and 107 // Unspecified be overridden, approximate this by stopping at the first 108 // TrustRoot, TrustAsRoot or Deny. 109 if (result == kSecTrustSettingsResultTrustRoot) { 110 break; 111 } else if (result == kSecTrustSettingsResultTrustAsRoot) { 112 break; 113 } else if (result == kSecTrustSettingsResultDeny) { 114 break; 115 } 116 } 117 118 // If trust settings are present, but none of them match the policy... 119 // the docs don't tell us what to do. 120 // 121 // "Trust settings for a given use apply if any of the dictionaries in the 122 // certificate’s trust settings array satisfies the specified use." suggests 123 // that it's as if there were no trust settings at all, so we should probably 124 // fallback to the admin trust settings. TODO. 125 if (result == 0) { 126 result = kSecTrustSettingsResultUnspecified; 127 } 128 129 CFRelease(_kSecTrustSettingsPolicy); 130 CFRelease(_kSecTrustSettingsPolicyString); 131 CFRelease(_kSecTrustSettingsResult); 132 CFRelease(trustSettings); 133 134 return result; 135 } 136 137 // isRootCertificate reports whether Subject and Issuer match. 138 static Boolean isRootCertificate(SecCertificateRef cert, CFErrorRef *errRef) { 139 CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, errRef); 140 if (*errRef != NULL) { 141 return false; 142 } 143 CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, errRef); 144 if (*errRef != NULL) { 145 CFRelease(subjectName); 146 return false; 147 } 148 Boolean equal = CFEqual(subjectName, issuerName); 149 CFRelease(subjectName); 150 CFRelease(issuerName); 151 return equal; 152 } 153 154 // CopyPEMRootsCTX509 fetches the system's list of trusted X.509 root certificates 155 // for the kSecTrustSettingsPolicy SSL. 156 // 157 // On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root 158 // certificates of the system. On failure, the function returns -1. 159 // Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots. 160 // 161 // Note: The CFDataRef returned in pemRoots and untrustedPemRoots must 162 // be released (using CFRelease) after we've consumed its content. 163 static int CopyPEMRootsCTX509(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots, bool debugDarwinRoots) { 164 int i; 165 166 if (debugDarwinRoots) { 167 fprintf(stderr, "crypto/x509: kSecTrustSettingsResultInvalid = %d\n", kSecTrustSettingsResultInvalid); 168 fprintf(stderr, "crypto/x509: kSecTrustSettingsResultTrustRoot = %d\n", kSecTrustSettingsResultTrustRoot); 169 fprintf(stderr, "crypto/x509: kSecTrustSettingsResultTrustAsRoot = %d\n", kSecTrustSettingsResultTrustAsRoot); 170 fprintf(stderr, "crypto/x509: kSecTrustSettingsResultDeny = %d\n", kSecTrustSettingsResultDeny); 171 fprintf(stderr, "crypto/x509: kSecTrustSettingsResultUnspecified = %d\n", kSecTrustSettingsResultUnspecified); 172 } 173 174 // Get certificates from all domains, not just System, this lets 175 // the user add CAs to their "login" keychain, and Admins to add 176 // to the "System" keychain 177 SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem, 178 kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainUser }; 179 180 int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain); 181 if (pemRoots == NULL || untrustedPemRoots == NULL) { 182 return -1; 183 } 184 185 CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0); 186 CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0); 187 for (i = 0; i < numDomains; i++) { 188 int j; 189 CFArrayRef certs = NULL; 190 OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs); 191 if (err != noErr) { 192 continue; 193 } 194 195 CFIndex numCerts = CFArrayGetCount(certs); 196 for (j = 0; j < numCerts; j++) { 197 SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j); 198 if (cert == NULL) { 199 continue; 200 } 201 202 SInt32 result; 203 if (domains[i] == kSecTrustSettingsDomainSystem) { 204 // Certs found in the system domain are always trusted. If the user 205 // configures "Never Trust" on such a cert, it will also be found in the 206 // admin or user domain, causing it to be added to untrustedPemRoots. The 207 // Go code will then clean this up. 208 result = kSecTrustSettingsResultTrustRoot; 209 } else { 210 result = sslTrustSettingsResult(cert); 211 if (debugDarwinRoots) { 212 CFErrorRef errRef = NULL; 213 CFStringRef summary = SecCertificateCopyShortDescription(NULL, cert, &errRef); 214 if (errRef != NULL) { 215 fprintf(stderr, "crypto/x509: SecCertificateCopyShortDescription failed\n"); 216 CFRelease(errRef); 217 continue; 218 } 219 220 CFIndex length = CFStringGetLength(summary); 221 CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; 222 char *buffer = malloc(maxSize); 223 if (CFStringGetCString(summary, buffer, maxSize, kCFStringEncodingUTF8)) { 224 fprintf(stderr, "crypto/x509: %s returned %d\n", buffer, (int)result); 225 } 226 free(buffer); 227 CFRelease(summary); 228 } 229 } 230 231 CFMutableDataRef appendTo; 232 // > Note the distinction between the results kSecTrustSettingsResultTrustRoot 233 // > and kSecTrustSettingsResultTrustAsRoot: The former can only be applied to 234 // > root (self-signed) certificates; the latter can only be applied to 235 // > non-root certificates. 236 if (result == kSecTrustSettingsResultTrustRoot) { 237 CFErrorRef errRef = NULL; 238 if (!isRootCertificate(cert, &errRef) || errRef != NULL) { 239 if (errRef != NULL) CFRelease(errRef); 240 continue; 241 } 242 243 appendTo = combinedData; 244 } else if (result == kSecTrustSettingsResultTrustAsRoot) { 245 CFErrorRef errRef = NULL; 246 if (isRootCertificate(cert, &errRef) || errRef != NULL) { 247 if (errRef != NULL) CFRelease(errRef); 248 continue; 249 } 250 251 appendTo = combinedData; 252 } else if (result == kSecTrustSettingsResultDeny) { 253 appendTo = combinedUntrustedData; 254 } else if (result == kSecTrustSettingsResultUnspecified) { 255 // Certificates with unspecified trust should probably be added to a pool of 256 // intermediates for chain building, or checked for transitive trust and 257 // added to the root pool (which is an imprecise approximation because it 258 // cuts chains short) but we don't support either at the moment. TODO. 259 continue; 260 } else { 261 continue; 262 } 263 264 CFDataRef data = NULL; 265 err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); 266 if (err != noErr) { 267 continue; 268 } 269 if (data != NULL) { 270 CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data)); 271 CFRelease(data); 272 } 273 } 274 CFRelease(certs); 275 } 276 *pemRoots = combinedData; 277 *untrustedPemRoots = combinedUntrustedData; 278 return 0; 279 } 280 */ 281 import "C" 282 import ( 283 "errors" 284 "unsafe" 285 ) 286 287 func loadSystemRoots() (*CertPool, error) { 288 var data, untrustedData C.CFDataRef 289 err := C.CopyPEMRootsCTX509(&data, &untrustedData, C.bool(debugDarwinRoots)) 290 if err == -1 { 291 return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo") 292 } 293 defer C.CFRelease(C.CFTypeRef(data)) 294 defer C.CFRelease(C.CFTypeRef(untrustedData)) 295 296 buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data))) 297 roots := NewCertPool() 298 roots.AppendCertsFromPEM(buf) 299 300 if C.CFDataGetLength(untrustedData) == 0 { 301 return roots, nil 302 } 303 304 buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData))) 305 untrustedRoots := NewCertPool() 306 untrustedRoots.AppendCertsFromPEM(buf) 307 308 trustedRoots := NewCertPool() 309 for _, c := range roots.certs { 310 if !untrustedRoots.contains(c) { 311 trustedRoots.AddCert(c) 312 } 313 } 314 return trustedRoots, nil 315 } 316