golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/cmd/macos-roots-test/root_cgo_darwin.go (about) 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 package main 6 7 /* 8 #cgo CFLAGS: -mmacosx-version-min=10.10 -D__MAC_OS_X_VERSION_MAX_ALLOWED=101300 9 #cgo LDFLAGS: -framework CoreFoundation -framework Security 10 11 #include <errno.h> 12 #include <sys/sysctl.h> 13 14 #include <CoreFoundation/CoreFoundation.h> 15 #include <Security/Security.h> 16 17 void CFReleaseIfNotNULL(CFTypeRef cf) { 18 if (cf != NULL) CFRelease(cf); 19 } 20 21 static bool isSSLPolicy(SecPolicyRef policyRef) { 22 if (!policyRef) { 23 return false; 24 } 25 CFDictionaryRef properties = SecPolicyCopyProperties(policyRef); 26 if (properties == NULL) { 27 return false; 28 } 29 CFTypeRef value = NULL; 30 if (CFDictionaryGetValueIfPresent(properties, kSecPolicyOid, (const void **)&value)) { 31 CFRelease(properties); 32 return CFEqual(value, kSecPolicyAppleSSL); 33 } 34 CFRelease(properties); 35 return false; 36 } 37 38 // sslTrustSettingsResult obtains the final kSecTrustSettingsResult value 39 // for a certificate in the user or admin domain, combining usage constraints 40 // for the SSL SecTrustSettingsPolicy, ignoring SecTrustSettingsKeyUsage, 41 // kSecTrustSettingsAllowedError and kSecTrustSettingsPolicyString. 42 // https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting 43 static SInt32 sslTrustSettingsResult(SecCertificateRef cert) { 44 CFArrayRef trustSettings = NULL; 45 OSStatus err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings); 46 47 // According to Apple's SecTrustServer.c, "user trust settings overrule admin trust settings", 48 // but the rules of the override are unclear. Let's assume admin trust settings are applicable 49 // if and only if user trust settings fail to load or are NULL. 50 if (err != errSecSuccess || trustSettings == NULL) { 51 CFReleaseIfNotNULL(trustSettings); 52 err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainAdmin, &trustSettings); 53 } 54 55 // > no trust settings [...] means "this certificate must be verified to a known trusted certificate” 56 if (err != errSecSuccess || trustSettings == NULL) { 57 CFReleaseIfNotNULL(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 CFReleaseIfNotNULL(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 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 CFNumberRef cfNum; 93 if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsResult, (const void**)&cfNum)) { 94 CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result); 95 } else { 96 // > If the value of the kSecTrustSettingsResult component is not 97 // > kSecTrustSettingsResultUnspecified for a usage constraints dictionary that has 98 // > no constraints, the default value kSecTrustSettingsResultTrustRoot is assumed. 99 result = kSecTrustSettingsResultTrustRoot; 100 } 101 102 break; 103 } 104 105 // If trust settings are present, but none of them match the policy... 106 // the docs don't tell us what to do. 107 // 108 // "Trust settings for a given use apply if any of the dictionaries in the 109 // certificate’s trust settings array satisfies the specified use." suggests 110 // that it's as if there were no trust settings at all, so we should probably 111 // fallback to the admin trust settings. TODO. 112 if (result == 0) { 113 result = kSecTrustSettingsResultUnspecified; 114 } 115 116 CFRelease(_kSecTrustSettingsResult); 117 CFRelease(trustSettings); 118 119 return result; 120 } 121 122 // FetchPEMRoots fetches the system's list of trusted X.509 root certificates 123 // for the kSecTrustSettingsPolicy SSL. 124 // 125 // On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root 126 // certificates of the system. On failure, the function returns -1. 127 // Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots. 128 // 129 // Note: The CFDataRef returned in pemRoots and untrustedPemRoots must 130 // be released (using CFRelease) after we've consumed its content. 131 int _FetchPEMRoots(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots, bool debugDarwinRoots) { 132 int i; 133 134 if (debugDarwinRoots) { 135 printf("crypto/x509: kSecTrustSettingsResultInvalid = %d\n", kSecTrustSettingsResultInvalid); 136 printf("crypto/x509: kSecTrustSettingsResultTrustRoot = %d\n", kSecTrustSettingsResultTrustRoot); 137 printf("crypto/x509: kSecTrustSettingsResultTrustAsRoot = %d\n", kSecTrustSettingsResultTrustAsRoot); 138 printf("crypto/x509: kSecTrustSettingsResultDeny = %d\n", kSecTrustSettingsResultDeny); 139 printf("crypto/x509: kSecTrustSettingsResultUnspecified = %d\n", kSecTrustSettingsResultUnspecified); 140 } 141 142 // Get certificates from all domains, not just System, this lets 143 // the user add CAs to their "login" keychain, and Admins to add 144 // to the "System" keychain 145 SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem, 146 kSecTrustSettingsDomainAdmin, 147 kSecTrustSettingsDomainUser }; 148 149 int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain); 150 if (pemRoots == NULL) { 151 return -1; 152 } 153 154 CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0); 155 CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0); 156 for (i = 0; i < numDomains; i++) { 157 int j; 158 CFArrayRef certs = NULL; 159 OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs); 160 if (err != noErr) { 161 continue; 162 } 163 164 CFIndex numCerts = CFArrayGetCount(certs); 165 for (j = 0; j < numCerts; j++) { 166 CFDataRef data = NULL; 167 CFArrayRef trustSettings = NULL; 168 SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j); 169 if (cert == NULL) { 170 continue; 171 } 172 173 SInt32 result; 174 if (domains[i] == kSecTrustSettingsDomainSystem) { 175 // Certs found in the system domain are always trusted. If the user 176 // configures "Never Trust" on such a cert, it will also be found in the 177 // admin or user domain, causing it to be added to untrustedPemRoots. The 178 // Go code will then clean this up. 179 result = kSecTrustSettingsResultTrustAsRoot; 180 } else { 181 result = sslTrustSettingsResult(cert); 182 if (debugDarwinRoots) { 183 CFErrorRef errRef = NULL; 184 CFStringRef summary = SecCertificateCopyShortDescription(NULL, cert, &errRef); 185 if (errRef != NULL) { 186 printf("crypto/x509: SecCertificateCopyShortDescription failed\n"); 187 CFRelease(errRef); 188 continue; 189 } 190 191 CFIndex length = CFStringGetLength(summary); 192 CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; 193 char *buffer = malloc(maxSize); 194 if (CFStringGetCString(summary, buffer, maxSize, kCFStringEncodingUTF8)) { 195 printf("crypto/x509: %s returned %d\n", buffer, result); 196 } 197 free(buffer); 198 CFRelease(summary); 199 } 200 } 201 202 CFMutableDataRef appendTo; 203 if (result == kSecTrustSettingsResultTrustRoot) { 204 // "can only be applied to root (self-signed) certificates", so 205 // make sure Subject and Issuer Name match. 206 CFErrorRef errRef = NULL; 207 CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, &errRef); 208 if (errRef != NULL) { 209 CFRelease(errRef); 210 continue; 211 } 212 CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, &errRef); 213 if (errRef != NULL) { 214 CFRelease(subjectName); 215 CFRelease(errRef); 216 continue; 217 } 218 Boolean equal = CFEqual(subjectName, issuerName); 219 CFRelease(subjectName); 220 CFRelease(issuerName); 221 if (!equal) { 222 continue; 223 } 224 225 appendTo = combinedData; 226 } else if (result == kSecTrustSettingsResultTrustAsRoot) { 227 // In theory "can only be applied to non-root certificates", but ignore 228 // this for now, also because it's the state we assume for the system domain. 229 appendTo = combinedData; 230 } else if (result == kSecTrustSettingsResultDeny) { 231 appendTo = combinedUntrustedData; 232 } else if (result == kSecTrustSettingsResultUnspecified) { 233 continue; 234 } else { 235 continue; 236 } 237 238 err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); 239 if (err != noErr) { 240 continue; 241 } 242 if (data != NULL) { 243 CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data)); 244 CFRelease(data); 245 } 246 } 247 CFRelease(certs); 248 } 249 *pemRoots = combinedData; 250 *untrustedPemRoots = combinedUntrustedData; 251 return 0; 252 } 253 */ 254 import "C" 255 import ( 256 "crypto/x509" 257 "errors" 258 "unsafe" 259 ) 260 261 func loadSystemRoots() (*x509.CertPool, error) { 262 roots := x509.NewCertPool() 263 264 var data C.CFDataRef = 0 265 var untrustedData C.CFDataRef = 0 266 err := C._FetchPEMRoots(&data, &untrustedData, C.bool(debugDarwinRoots)) 267 if err == -1 { 268 // TODO: better error message 269 return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo") 270 } 271 272 defer C.CFRelease(C.CFTypeRef(data)) 273 buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data))) 274 roots.AppendCertsFromPEM(buf) 275 if untrustedData == 0 { 276 return roots, nil 277 } 278 defer C.CFRelease(C.CFTypeRef(untrustedData)) 279 buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData))) 280 untrustedRoots := x509.NewCertPool() 281 untrustedRoots.AppendCertsFromPEM(buf) 282 283 trustedRoots := x509.NewCertPool() 284 for _, c := range (*CertPool)(unsafe.Pointer(roots)).certs { 285 if !(*CertPool)(unsafe.Pointer(untrustedRoots)).contains(c) { 286 trustedRoots.AddCert(c) 287 } 288 } 289 return trustedRoots, nil 290 }