// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build !ios package x509 // This cgo implementation exists only to support side-by-side testing by // TestSystemRoots. It can be removed once we are confident in the no-cgo // implementation. /* #cgo CFLAGS: -mmacosx-version-min=10.11 #cgo LDFLAGS: -framework CoreFoundation -framework Security #include #include #include #include static Boolean isSSLPolicy(SecPolicyRef policyRef) { if (!policyRef) { return false; } CFDictionaryRef properties = SecPolicyCopyProperties(policyRef); if (properties == NULL) { return false; } Boolean isSSL = false; CFTypeRef value = NULL; if (CFDictionaryGetValueIfPresent(properties, kSecPolicyOid, (const void **)&value)) { isSSL = CFEqual(value, kSecPolicyAppleSSL); } CFRelease(properties); return isSSL; } // sslTrustSettingsResult obtains the final kSecTrustSettingsResult value // for a certificate in the user or admin domain, combining usage constraints // for the SSL SecTrustSettingsPolicy, ignoring SecTrustSettingsKeyUsage and // kSecTrustSettingsAllowedError. // https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting static SInt32 sslTrustSettingsResult(SecCertificateRef cert) { CFArrayRef trustSettings = NULL; OSStatus err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings); // According to Apple's SecTrustServer.c, "user trust settings overrule admin trust settings", // but the rules of the override are unclear. Let's assume admin trust settings are applicable // if and only if user trust settings fail to load or are NULL. if (err != errSecSuccess || trustSettings == NULL) { if (trustSettings != NULL) CFRelease(trustSettings); err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainAdmin, &trustSettings); } // > no trust settings [...] means "this certificate must be verified to a known trusted certificate” // (Should this cause a fallback from user to admin domain? It's unclear.) if (err != errSecSuccess || trustSettings == NULL) { if (trustSettings != NULL) CFRelease(trustSettings); return kSecTrustSettingsResultUnspecified; } // > An empty trust settings array means "always trust this certificate” with an // > overall trust setting for the certificate of kSecTrustSettingsResultTrustRoot. if (CFArrayGetCount(trustSettings) == 0) { CFRelease(trustSettings); return kSecTrustSettingsResultTrustRoot; } // kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"), // but the Go linker's internal linking mode can't handle CFSTR relocations. // Create our own dynamic string instead and release it below. CFStringRef _kSecTrustSettingsResult = CFStringCreateWithCString( NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8); CFStringRef _kSecTrustSettingsPolicy = CFStringCreateWithCString( NULL, "kSecTrustSettingsPolicy", kCFStringEncodingUTF8); CFStringRef _kSecTrustSettingsPolicyString = CFStringCreateWithCString( NULL, "kSecTrustSettingsPolicyString", kCFStringEncodingUTF8); CFIndex m; SInt32 result = 0; for (m = 0; m < CFArrayGetCount(trustSettings); m++) { CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, m); // First, check if this trust setting is constrained to a non-SSL policy. SecPolicyRef policyRef; if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsPolicy, (const void**)&policyRef)) { if (!isSSLPolicy(policyRef)) { continue; } } if (CFDictionaryContainsKey(tSetting, _kSecTrustSettingsPolicyString)) { // Restricted to a hostname, not a root. continue; } CFNumberRef cfNum; if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsResult, (const void**)&cfNum)) { CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result); } else { // > If this key is not present, a default value of // > kSecTrustSettingsResultTrustRoot is assumed. result = kSecTrustSettingsResultTrustRoot; } // If multiple dictionaries match, we are supposed to "OR" them, // the semantics of which are not clear. Since TrustRoot and TrustAsRoot // are mutually exclusive, Deny should probably override, and Invalid and // Unspecified be overridden, approximate this by stopping at the first // TrustRoot, TrustAsRoot or Deny. if (result == kSecTrustSettingsResultTrustRoot) { break; } else if (result == kSecTrustSettingsResultTrustAsRoot) { break; } else if (result == kSecTrustSettingsResultDeny) { break; } } // If trust settings are present, but none of them match the policy... // the docs don't tell us what to do. // // "Trust settings for a given use apply if any of the dictionaries in the // certificate’s trust settings array satisfies the specified use." suggests // that it's as if there were no trust settings at all, so we should probably // fallback to the admin trust settings. TODO. if (result == 0) { result = kSecTrustSettingsResultUnspecified; } CFRelease(_kSecTrustSettingsPolicy); CFRelease(_kSecTrustSettingsPolicyString); CFRelease(_kSecTrustSettingsResult); CFRelease(trustSettings); return result; } // isRootCertificate reports whether Subject and Issuer match. static Boolean isRootCertificate(SecCertificateRef cert, CFErrorRef *errRef) { CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, errRef); if (*errRef != NULL) { return false; } CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, errRef); if (*errRef != NULL) { CFRelease(subjectName); return false; } Boolean equal = CFEqual(subjectName, issuerName); CFRelease(subjectName); CFRelease(issuerName); return equal; } // CopyPEMRoots fetches the system's list of trusted X.509 root certificates // for the kSecTrustSettingsPolicy SSL. // // On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root // certificates of the system. On failure, the function returns -1. // Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots. // // Note: The CFDataRef returned in pemRoots and untrustedPemRoots must // be released (using CFRelease) after we've consumed its content. static int CopyPEMRoots(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots, bool debugDarwinRoots) { int i; if (debugDarwinRoots) { fprintf(stderr, "crypto/x509: kSecTrustSettingsResultInvalid = %d\n", kSecTrustSettingsResultInvalid); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultTrustRoot = %d\n", kSecTrustSettingsResultTrustRoot); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultTrustAsRoot = %d\n", kSecTrustSettingsResultTrustAsRoot); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultDeny = %d\n", kSecTrustSettingsResultDeny); fprintf(stderr, "crypto/x509: kSecTrustSettingsResultUnspecified = %d\n", kSecTrustSettingsResultUnspecified); } // Get certificates from all domains, not just System, this lets // the user add CAs to their "login" keychain, and Admins to add // to the "System" keychain SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainUser }; int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain); if (pemRoots == NULL || untrustedPemRoots == NULL) { return -1; } CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0); CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0); for (i = 0; i < numDomains; i++) { int j; CFArrayRef certs = NULL; OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs); if (err != noErr) { continue; } CFIndex numCerts = CFArrayGetCount(certs); for (j = 0; j < numCerts; j++) { SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j); if (cert == NULL) { continue; } SInt32 result; if (domains[i] == kSecTrustSettingsDomainSystem) { // Certs found in the system domain are always trusted. If the user // configures "Never Trust" on such a cert, it will also be found in the // admin or user domain, causing it to be added to untrustedPemRoots. The // Go code will then clean this up. result = kSecTrustSettingsResultTrustRoot; } else { result = sslTrustSettingsResult(cert); if (debugDarwinRoots) { CFErrorRef errRef = NULL; CFStringRef summary = SecCertificateCopyShortDescription(NULL, cert, &errRef); if (errRef != NULL) { fprintf(stderr, "crypto/x509: SecCertificateCopyShortDescription failed\n"); CFRelease(errRef); continue; } CFIndex length = CFStringGetLength(summary); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; char *buffer = malloc(maxSize); if (CFStringGetCString(summary, buffer, maxSize, kCFStringEncodingUTF8)) { fprintf(stderr, "crypto/x509: %s returned %d\n", buffer, (int)result); } free(buffer); CFRelease(summary); } } CFMutableDataRef appendTo; // > Note the distinction between the results kSecTrustSettingsResultTrustRoot // > and kSecTrustSettingsResultTrustAsRoot: The former can only be applied to // > root (self-signed) certificates; the latter can only be applied to // > non-root certificates. if (result == kSecTrustSettingsResultTrustRoot) { CFErrorRef errRef = NULL; if (!isRootCertificate(cert, &errRef) || errRef != NULL) { if (errRef != NULL) CFRelease(errRef); continue; } appendTo = combinedData; } else if (result == kSecTrustSettingsResultTrustAsRoot) { CFErrorRef errRef = NULL; if (isRootCertificate(cert, &errRef) || errRef != NULL) { if (errRef != NULL) CFRelease(errRef); continue; } appendTo = combinedData; } else if (result == kSecTrustSettingsResultDeny) { appendTo = combinedUntrustedData; } else if (result == kSecTrustSettingsResultUnspecified) { // Certificates with unspecified trust should probably be added to a pool of // intermediates for chain building, or checked for transitive trust and // added to the root pool (which is an imprecise approximation because it // cuts chains short) but we don't support either at the moment. TODO. continue; } else { continue; } CFDataRef data = NULL; err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); if (err != noErr) { continue; } if (data != NULL) { CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data)); CFRelease(data); } } CFRelease(certs); } *pemRoots = combinedData; *untrustedPemRoots = combinedUntrustedData; return 0; } */ import "C" import ( "errors" "unsafe" ) func init() { loadSystemRootsWithCgo = _loadSystemRootsWithCgo } func _loadSystemRootsWithCgo() (*CertPool, error) { var data, untrustedData C.CFDataRef err := C.CopyPEMRoots(&data, &untrustedData, C.bool(debugDarwinRoots)) if err == -1 { return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo") } defer C.CFRelease(C.CFTypeRef(data)) defer C.CFRelease(C.CFTypeRef(untrustedData)) buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data))) roots := NewCertPool() roots.AppendCertsFromPEM(buf) if C.CFDataGetLength(untrustedData) == 0 { return roots, nil } buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData))) untrustedRoots := NewCertPool() untrustedRoots.AppendCertsFromPEM(buf) trustedRoots := NewCertPool() for _, c := range roots.certs { if !untrustedRoots.contains(c) { trustedRoots.AddCert(c) } } return trustedRoots, nil }