aboutsummaryrefslogtreecommitdiff
path: root/src/crypto/x509/root_darwin.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/crypto/x509/root_darwin.go')
-rw-r--r--src/crypto/x509/root_darwin.go243
1 files changed, 243 insertions, 0 deletions
diff --git a/src/crypto/x509/root_darwin.go b/src/crypto/x509/root_darwin.go
new file mode 100644
index 0000000000..ce88de025e
--- /dev/null
+++ b/src/crypto/x509/root_darwin.go
@@ -0,0 +1,243 @@
+// Copyright 2020 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
+
+import (
+ "bytes"
+ macOS "crypto/x509/internal/macos"
+ "fmt"
+ "os"
+ "strings"
+)
+
+var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
+
+func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
+ return nil, nil
+}
+
+// loadSystemRootsWithCgo is set in root_cgo_darwin_amd64.go when cgo is
+// available, and is only used for testing.
+var loadSystemRootsWithCgo func() (*CertPool, error)
+
+func loadSystemRoots() (*CertPool, error) {
+ var trustedRoots []*Certificate
+ untrustedRoots := make(map[string]bool)
+
+ // macOS has three trust domains: one for CAs added by users to their
+ // "login" keychain, one for CAs added by Admins to the "System" keychain,
+ // and one for the CAs that ship with the OS.
+ for _, domain := range []macOS.SecTrustSettingsDomain{
+ macOS.SecTrustSettingsDomainUser,
+ macOS.SecTrustSettingsDomainAdmin,
+ macOS.SecTrustSettingsDomainSystem,
+ } {
+ certs, err := macOS.SecTrustSettingsCopyCertificates(domain)
+ if err == macOS.ErrNoTrustSettings {
+ continue
+ } else if err != nil {
+ return nil, err
+ }
+ defer macOS.CFRelease(certs)
+
+ for i := 0; i < macOS.CFArrayGetCount(certs); i++ {
+ c := macOS.CFArrayGetValueAtIndex(certs, i)
+ cert, err := exportCertificate(c)
+ if err != nil {
+ if debugDarwinRoots {
+ fmt.Fprintf(os.Stderr, "crypto/x509: domain %d, certificate #%d: %v\n", domain, i, err)
+ }
+ continue
+ }
+
+ var result macOS.SecTrustSettingsResult
+ if domain == macOS.SecTrustSettingsDomainSystem {
+ // 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 untrustedRoots.
+ result = macOS.SecTrustSettingsResultTrustRoot
+ } else {
+ result, err = sslTrustSettingsResult(c)
+ if err != nil {
+ if debugDarwinRoots {
+ fmt.Fprintf(os.Stderr, "crypto/x509: trust settings for %v: %v\n", cert.Subject, err)
+ }
+ continue
+ }
+ if debugDarwinRoots {
+ fmt.Fprintf(os.Stderr, "crypto/x509: trust settings for %v: %d\n", cert.Subject, result)
+ }
+ }
+
+ switch result {
+ // "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."
+ case macOS.SecTrustSettingsResultTrustRoot:
+ if isRootCertificate(cert) {
+ trustedRoots = append(trustedRoots, cert)
+ }
+ case macOS.SecTrustSettingsResultTrustAsRoot:
+ if !isRootCertificate(cert) {
+ trustedRoots = append(trustedRoots, cert)
+ }
+
+ case macOS.SecTrustSettingsResultDeny:
+ // Add this certificate to untrustedRoots, which are subtracted
+ // from trustedRoots, so that we don't have to evaluate policies
+ // for every root in the system domain, but still apply user and
+ // admin policies that override system roots.
+ untrustedRoots[string(cert.Raw)] = true
+
+ case macOS.SecTrustSettingsResultUnspecified:
+ // Certificates with unspecified trust should be added to a pool
+ // of intermediates for chain building, but we don't support it
+ // at the moment. This is Issue 35631.
+
+ default:
+ if debugDarwinRoots {
+ fmt.Fprintf(os.Stderr, "crypto/x509: unknown trust setting for %v: %d\n", cert.Subject, result)
+ }
+ }
+ }
+ }
+
+ pool := NewCertPool()
+ for _, cert := range trustedRoots {
+ if !untrustedRoots[string(cert.Raw)] {
+ pool.AddCert(cert)
+ }
+ }
+ return pool, nil
+}
+
+// exportCertificate returns a *Certificate for a SecCertificateRef.
+func exportCertificate(cert macOS.CFRef) (*Certificate, error) {
+ data, err := macOS.SecItemExport(cert)
+ if err != nil {
+ return nil, err
+ }
+ defer macOS.CFRelease(data)
+ der := macOS.CFDataToSlice(data)
+
+ return ParseCertificate(der)
+}
+
+// isRootCertificate reports whether Subject and Issuer match.
+func isRootCertificate(cert *Certificate) bool {
+ return bytes.Equal(cert.RawSubject, cert.RawIssuer)
+}
+
+// sslTrustSettingsResult obtains the final kSecTrustSettingsResult value for a
+// certificate in the user or admin domain, combining usage constraints for the
+// SSL SecTrustSettingsPolicy,
+//
+// It ignores SecTrustSettingsKeyUsage and kSecTrustSettingsAllowedError, and
+// doesn't support kSecTrustSettingsDefaultRootCertSetting.
+//
+// https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting
+func sslTrustSettingsResult(cert macOS.CFRef) (macOS.SecTrustSettingsResult, error) {
+ // In Apple's implementation user trust settings override admin trust settings
+ // (which themselves override system trust settings). If SecTrustSettingsCopyTrustSettings
+ // fails, or returns a NULL trust settings, when looking for the user trust
+ // settings then fallback to checking the admin trust settings.
+ //
+ // See Security-59306.41.2/trust/headers/SecTrustSettings.h for a description of
+ // the trust settings overrides, and SecLegacyAnchorSourceCopyUsageConstraints in
+ // Security-59306.41.2/trust/trustd/SecCertificateSource.c for a concrete example
+ // of how Apple applies the override in the case of NULL trust settings, or non
+ // success errors.
+ trustSettings, err := macOS.SecTrustSettingsCopyTrustSettings(cert, macOS.SecTrustSettingsDomainUser)
+ if err != nil || trustSettings == 0 {
+ if debugDarwinRoots && err != macOS.ErrNoTrustSettings {
+ fmt.Fprintf(os.Stderr, "crypto/x509: SecTrustSettingsCopyTrustSettings for SecTrustSettingsDomainUser failed: %s\n", err)
+ }
+ trustSettings, err = macOS.SecTrustSettingsCopyTrustSettings(cert, macOS.SecTrustSettingsDomainAdmin)
+ }
+ if err != nil || trustSettings == 0 {
+ // If there are neither user nor admin trust settings for a certificate returned
+ // from SecTrustSettingsCopyCertificates Apple returns kSecTrustSettingsResultInvalid,
+ // as this method is intended to return certificates _which have trust settings_.
+ // The most likely case for this being triggered is that the existing trust settings
+ // are invalid and cannot be properly parsed. In this case SecTrustSettingsCopyTrustSettings
+ // returns errSecInvalidTrustSettings. The existing cgo implementation returns
+ // kSecTrustSettingsResultUnspecified in this case, which mostly matches the Apple
+ // implementation because we don't do anything with certificates marked with this
+ // result.
+ //
+ // See SecPVCGetTrustSettingsResult in Security-59306.41.2/trust/trustd/SecPolicyServer.c
+ if debugDarwinRoots && err != macOS.ErrNoTrustSettings {
+ fmt.Fprintf(os.Stderr, "crypto/x509: SecTrustSettingsCopyTrustSettings for SecTrustSettingsDomainAdmin failed: %s\n", err)
+ }
+ return macOS.SecTrustSettingsResultUnspecified, nil
+ }
+ defer macOS.CFRelease(trustSettings)
+
+ // "An empty trust settings array means 'always trust this certificate' with an
+ // overall trust setting for the certificate of kSecTrustSettingsResultTrustRoot."
+ if macOS.CFArrayGetCount(trustSettings) == 0 {
+ return macOS.SecTrustSettingsResultTrustRoot, nil
+ }
+
+ isSSLPolicy := func(policyRef macOS.CFRef) bool {
+ properties := macOS.SecPolicyCopyProperties(policyRef)
+ defer macOS.CFRelease(properties)
+ if v, ok := macOS.CFDictionaryGetValueIfPresent(properties, macOS.SecPolicyOid); ok {
+ return macOS.CFEqual(v, macOS.CFRef(macOS.SecPolicyAppleSSL))
+ }
+ return false
+ }
+
+ for i := 0; i < macOS.CFArrayGetCount(trustSettings); i++ {
+ tSetting := macOS.CFArrayGetValueAtIndex(trustSettings, i)
+
+ // First, check if this trust setting is constrained to a non-SSL policy.
+ if policyRef, ok := macOS.CFDictionaryGetValueIfPresent(tSetting, macOS.SecTrustSettingsPolicy); ok {
+ if !isSSLPolicy(policyRef) {
+ continue
+ }
+ }
+
+ // Then check if it is restricted to a hostname, so not a root.
+ if _, ok := macOS.CFDictionaryGetValueIfPresent(tSetting, macOS.SecTrustSettingsPolicyString); ok {
+ continue
+ }
+
+ cfNum, ok := macOS.CFDictionaryGetValueIfPresent(tSetting, macOS.SecTrustSettingsResultKey)
+ // "If this key is not present, a default value of kSecTrustSettingsResultTrustRoot is assumed."
+ if !ok {
+ return macOS.SecTrustSettingsResultTrustRoot, nil
+ }
+ result, err := macOS.CFNumberGetValue(cfNum)
+ if err != nil {
+ return 0, err
+ }
+
+ // 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.
+ switch r := macOS.SecTrustSettingsResult(result); r {
+ case macOS.SecTrustSettingsResultTrustRoot,
+ macOS.SecTrustSettingsResultTrustAsRoot,
+ macOS.SecTrustSettingsResultDeny:
+ return r, nil
+ }
+ }
+
+ // 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 maybe
+ // fallback to the admin trust settings? TODO(golang.org/issue/38888).
+
+ return macOS.SecTrustSettingsResultUnspecified, nil
+}