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  }