244 lines
9.3 KiB
Go
244 lines
9.3 KiB
Go
// 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
|
||
}
|