mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-04 15:35:38 +00:00
315 lines
11 KiB
Go
315 lines
11 KiB
Go
|
// 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 cgo,!arm64,!ios
|
|||
|
|
|||
|
package x509
|
|||
|
|
|||
|
/*
|
|||
|
#cgo CFLAGS: -mmacosx-version-min=10.11
|
|||
|
#cgo LDFLAGS: -framework CoreFoundation -framework Security
|
|||
|
|
|||
|
#include <errno.h>
|
|||
|
#include <sys/sysctl.h>
|
|||
|
|
|||
|
#include <CoreFoundation/CoreFoundation.h>
|
|||
|
#include <Security/Security.h>
|
|||
|
|
|||
|
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 loadSystemRoots() (*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
|
|||
|
}
|