Home | History | Annotate | Download | only in x509
      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 // +build cgo,!arm,!arm64,!ios
      6 
      7 package x509
      8 
      9 /*
     10 #cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1080
     11 #cgo LDFLAGS: -framework CoreFoundation -framework Security
     12 
     13 #include <errno.h>
     14 #include <sys/sysctl.h>
     15 
     16 #include <CoreFoundation/CoreFoundation.h>
     17 #include <Security/Security.h>
     18 
     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) {
     24 	if (pemRoots == NULL) {
     25 		return -1;
     26 	}
     27 	CFArrayRef certs = NULL;
     28 	OSStatus err = SecTrustCopyAnchorCertificates(&certs);
     29 	if (err != noErr) {
     30 		return -1;
     31 	}
     32 	CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
     33 	int i, ncerts = CFArrayGetCount(certs);
     34 	for (i = 0; i < ncerts; i++) {
     35 		CFDataRef data = NULL;
     36 		SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
     37 		if (cert == NULL) {
     38 			continue;
     39 		}
     40 		// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
     41 		// Once we support weak imports via cgo we should prefer that, and fall back to this
     42 		// for older systems.
     43 		err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
     44 		if (err != noErr) {
     45 			continue;
     46 		}
     47 		if (data != NULL) {
     48 			CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data));
     49 			CFRelease(data);
     50 		}
     51 	}
     52 	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 }
     71 
     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);
    196 	*pemRoots = combinedData;
    197 	*untrustedPemRoots = combinedUntrustedData;
    198 	return 0;
    199 }
    200 */
    201 import "C"
    202 import (
    203 	"errors"
    204 	"unsafe"
    205 )
    206 
    207 func loadSystemRoots() (*CertPool, error) {
    208 	roots := NewCertPool()
    209 
    210 	var data C.CFDataRef = nil
    211 	var untrustedData C.CFDataRef = nil
    212 	err := C.FetchPEMRoots(&data, &untrustedData)
    213 	if err == -1 {
    214 		// TODO: better error message
    215 		return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo")
    216 	}
    217 
    218 	defer C.CFRelease(C.CFTypeRef(data))
    219 	buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
    220 	roots.AppendCertsFromPEM(buf)
    221 	if untrustedData == nil {
    222 		return roots, nil
    223 	}
    224 	defer C.CFRelease(C.CFTypeRef(untrustedData))
    225 	buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData)))
    226 	untrustedRoots := NewCertPool()
    227 	untrustedRoots.AppendCertsFromPEM(buf)
    228 
    229 	trustedRoots := NewCertPool()
    230 	for _, c := range roots.certs {
    231 		if !untrustedRoots.contains(c) {
    232 			trustedRoots.AddCert(c)
    233 		}
    234 	}
    235 	return trustedRoots, nil
    236 }
    237