1
0
mirror of https://github.com/golang/go synced 2024-11-24 06:40:17 -07:00

crypto/x509: use platform verifier on darwin

When VerifyOptions.Roots is nil, default to using the platform X.509
certificate verification APIs on darwin, rather than using the Go
verifier. Since our oldest supported version of macOS is 10.12, we are
able to use the modern verification APIs, and don't need to resort to
the complex chain building trickery employed by chromium et al.

Unfortunately there is not a clean way to programmatically add test
roots to the system trust store that the builders would tolerate. The
most obvious solution, using 'security add-trusted-cert' requires human
interaction for authorization. We could also manually add anchors to
the constructed SecTrustRef, but that would require adding a whole
bunch of plumbing for test functionality, and would mean we weren't
really testing the actual non-test path. The path I've chosen here is
to just utilize existing valid, and purposefully invalid, trusted
chains, from google.com and the badssl.com test suite. This requires
external network access, but most accurately reflects real world
contexts.

This change removes the x509.SystemCertPool() functionality, which will
be ammended in a follow-up change which supports the suggested hybrid
pool approach described in #46287.

Updates #46287
Fixes #42414
Fixes #38888
Fixes #35631
Fixes #19561

Change-Id: I17f0d6c5cb3ef8a1f2731ce3296478b28d30df46
Reviewed-on: https://go-review.googlesource.com/c/go/+/353132
Trust: Roland Shoemaker <roland@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
This commit is contained in:
Roland Shoemaker 2021-09-29 11:31:01 -07:00
parent 8f923a4e3c
commit feb024f415
10 changed files with 411 additions and 227 deletions

View File

@ -106,6 +106,8 @@ func SystemCertPool() (*CertPool, error) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Issue 16736, 18609: // Issue 16736, 18609:
return nil, errors.New("crypto/x509: system root pool is not available on Windows") return nil, errors.New("crypto/x509: system root pool is not available on Windows")
} else if runtime.GOOS == "darwin" {
return nil, errors.New("crypto/x509: system root pool is not available on macOS")
} }
if sysRoots := systemRootsPool(); sysRoots != nil { if sysRoots := systemRootsPool(); sysRoots != nil {

View File

@ -14,6 +14,7 @@ import (
"internal/abi" "internal/abi"
"reflect" "reflect"
"runtime" "runtime"
"time"
"unsafe" "unsafe"
) )
@ -35,11 +36,37 @@ func CFDataToSlice(data CFRef) []byte {
return out return out
} }
// CFStringToString returns a Go string representation of the passed
// in CFString.
func CFStringToString(ref CFRef) string {
data := CFStringCreateExternalRepresentation(ref)
b := CFDataToSlice(data)
CFRelease(data)
return string(b)
}
// TimeToCFDateRef converts a time.Time into an apple CFDateRef
func TimeToCFDateRef(t time.Time) CFRef {
secs := t.Sub(time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)).Seconds()
ref := CFDateCreate(int(secs))
return ref
}
type CFString CFRef type CFString CFRef
const kCFAllocatorDefault = 0 const kCFAllocatorDefault = 0
const kCFStringEncodingUTF8 = 0x08000100 const kCFStringEncodingUTF8 = 0x08000100
//go:cgo_import_dynamic x509_CFDataCreate CFDataCreate "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
func BytesToCFData(b []byte) CFRef {
p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&b)).Data)
ret := syscall(abi.FuncPCABI0(x509_CFDataCreate_trampoline), kCFAllocatorDefault, uintptr(p), uintptr(len(b)), 0, 0, 0)
runtime.KeepAlive(p)
return CFRef(ret)
}
func x509_CFDataCreate_trampoline()
//go:cgo_import_dynamic x509_CFStringCreateWithBytes CFStringCreateWithBytes "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation" //go:cgo_import_dynamic x509_CFStringCreateWithBytes CFStringCreateWithBytes "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
// StringToCFString returns a copy of the UTF-8 contents of s as a new CFString. // StringToCFString returns a copy of the UTF-8 contents of s as a new CFString.
@ -126,5 +153,55 @@ func CFRelease(ref CFRef) {
} }
func x509_CFRelease_trampoline() func x509_CFRelease_trampoline()
//go:cgo_import_dynamic x509_CFArrayCreateMutable CFArrayCreateMutable "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
func CFArrayCreateMutable() CFRef {
ret := syscall(abi.FuncPCABI0(x509_CFArrayCreateMutable_trampoline), kCFAllocatorDefault, 0, 0 /* kCFTypeArrayCallBacks */, 0, 0, 0)
return CFRef(ret)
}
func x509_CFArrayCreateMutable_trampoline()
//go:cgo_import_dynamic x509_CFArrayAppendValue CFArrayAppendValue "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
func CFArrayAppendValue(array CFRef, val CFRef) {
syscall(abi.FuncPCABI0(x509_CFArrayAppendValue_trampoline), uintptr(array), uintptr(val), 0, 0, 0, 0)
}
func x509_CFArrayAppendValue_trampoline()
//go:cgo_import_dynamic x509_CFDateCreate CFDateCreate "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
func CFDateCreate(seconds int) CFRef {
ret := syscall(abi.FuncPCABI0(x509_CFDateCreate_trampoline), kCFAllocatorDefault, uintptr(seconds), 0, 0, 0, 0)
return CFRef(ret)
}
func x509_CFDateCreate_trampoline()
//go:cgo_import_dynamic x509_CFErrorCopyDescription CFErrorCopyDescription "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
func CFErrorCopyDescription(errRef CFRef) CFRef {
ret := syscall(abi.FuncPCABI0(x509_CFErrorCopyDescription_trampoline), uintptr(errRef), 0, 0, 0, 0, 0)
return CFRef(ret)
}
func x509_CFErrorCopyDescription_trampoline()
//go:cgo_import_dynamic x509_CFStringCreateExternalRepresentation CFStringCreateExternalRepresentation "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
func CFStringCreateExternalRepresentation(strRef CFRef) CFRef {
ret := syscall(abi.FuncPCABI0(x509_CFStringCreateExternalRepresentation_trampoline), kCFAllocatorDefault, uintptr(strRef), kCFStringEncodingUTF8, 0, 0, 0)
return CFRef(ret)
}
func x509_CFStringCreateExternalRepresentation_trampoline()
// syscall is implemented in the runtime package (runtime/sys_darwin.go) // syscall is implemented in the runtime package (runtime/sys_darwin.go)
func syscall(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr func syscall(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr
// ReleaseCFArray iterates through an array, releasing its contents, and then
// releases the array itself. This is necessary because we cannot, easily, set the
// CFArrayCallBacks argument when creating CFArrays.
func ReleaseCFArray(array CFRef) {
for i := 0; i < CFArrayGetCount(array); i++ {
ref := CFArrayGetValueAtIndex(array, i)
CFRelease(ref)
}
CFRelease(array)
}

View File

@ -28,3 +28,15 @@ TEXT ·x509_CFNumberGetValue_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFNumberGetValue(SB) JMP x509_CFNumberGetValue(SB)
TEXT ·x509_CFEqual_trampoline(SB),NOSPLIT,$0-0 TEXT ·x509_CFEqual_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFEqual(SB) JMP x509_CFEqual(SB)
TEXT ·x509_CFArrayCreateMutable_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFArrayCreateMutable(SB)
TEXT ·x509_CFArrayAppendValue_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFArrayAppendValue(SB)
TEXT ·x509_CFDateCreate_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFDateCreate(SB)
TEXT ·x509_CFDataCreate_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFDataCreate(SB)
TEXT ·x509_CFErrorCopyDescription_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFErrorCopyDescription(SB)
TEXT ·x509_CFStringCreateExternalRepresentation_trampoline(SB),NOSPLIT,$0-0
JMP x509_CFStringCreateExternalRepresentation(SB)

View File

@ -8,6 +8,7 @@ package macOS
import ( import (
"errors" "errors"
"fmt"
"internal/abi" "internal/abi"
"strconv" "strconv"
"unsafe" "unsafe"
@ -29,6 +30,19 @@ const (
SecTrustSettingsResultUnspecified SecTrustSettingsResultUnspecified
) )
type SecTrustResultType int32
const (
SecTrustResultInvalid SecTrustResultType = iota
SecTrustResultProceed
SecTrustResultConfirm // deprecated
SecTrustResultDeny
SecTrustResultUnspecified
SecTrustResultRecoverableTrustFailure
SecTrustResultFatalTrustFailure
SecTrustResultOtherError
)
type SecTrustSettingsDomain int32 type SecTrustSettingsDomain int32
const ( const (
@ -115,3 +129,107 @@ func SecPolicyCopyProperties(policy CFRef) CFRef {
return CFRef(ret) return CFRef(ret)
} }
func x509_SecPolicyCopyProperties_trampoline() func x509_SecPolicyCopyProperties_trampoline()
//go:cgo_import_dynamic x509_SecTrustCreateWithCertificates SecTrustCreateWithCertificates "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecTrustCreateWithCertificates(certs CFRef, policies CFRef) (CFRef, error) {
var trustObj CFRef
ret := syscall(abi.FuncPCABI0(x509_SecTrustCreateWithCertificates_trampoline), uintptr(certs), uintptr(policies),
uintptr(unsafe.Pointer(&trustObj)), 0, 0, 0)
if int32(ret) != 0 {
return 0, OSStatus{"SecTrustCreateWithCertificates", int32(ret)}
}
return trustObj, nil
}
func x509_SecTrustCreateWithCertificates_trampoline()
//go:cgo_import_dynamic x509_SecCertificateCreateWithData SecCertificateCreateWithData "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecCertificateCreateWithData(b []byte) CFRef {
data := BytesToCFData(b)
ret := syscall(abi.FuncPCABI0(x509_SecCertificateCreateWithData_trampoline), kCFAllocatorDefault, uintptr(data), 0, 0, 0, 0)
CFRelease(data)
return CFRef(ret)
}
func x509_SecCertificateCreateWithData_trampoline()
//go:cgo_import_dynamic x509_SecPolicyCreateSSL SecPolicyCreateSSL "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecPolicyCreateSSL(name string) CFRef {
var hostname CFString
if name != "" {
hostname = StringToCFString(name)
defer CFRelease(CFRef(hostname))
}
ret := syscall(abi.FuncPCABI0(x509_SecPolicyCreateSSL_trampoline), 1 /* true */, uintptr(hostname), 0, 0, 0, 0)
return CFRef(ret)
}
func x509_SecPolicyCreateSSL_trampoline()
//go:cgo_import_dynamic x509_SecTrustSetVerifyDate SecTrustSetVerifyDate "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecTrustSetVerifyDate(trustObj CFRef, dateRef CFRef) error {
ret := syscall(abi.FuncPCABI0(x509_SecTrustSetVerifyDate_trampoline), uintptr(trustObj), uintptr(dateRef), 0, 0, 0, 0)
if int32(ret) != 0 {
return OSStatus{"SecTrustSetVerifyDate", int32(ret)}
}
return nil
}
func x509_SecTrustSetVerifyDate_trampoline()
//go:cgo_import_dynamic x509_SecTrustEvaluate SecTrustEvaluate "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecTrustEvaluate(trustObj CFRef) (CFRef, error) {
var result CFRef
ret := syscall(abi.FuncPCABI0(x509_SecTrustEvaluate_trampoline), uintptr(trustObj), uintptr(unsafe.Pointer(&result)), 0, 0, 0, 0)
if int32(ret) != 0 {
return 0, OSStatus{"SecTrustEvaluate", int32(ret)}
}
return CFRef(result), nil
}
func x509_SecTrustEvaluate_trampoline()
//go:cgo_import_dynamic x509_SecTrustGetResult SecTrustGetResult "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecTrustGetResult(trustObj CFRef, result CFRef) (CFRef, CFRef, error) {
var chain, info CFRef
ret := syscall(abi.FuncPCABI0(x509_SecTrustGetResult_trampoline), uintptr(trustObj), uintptr(unsafe.Pointer(&result)),
uintptr(unsafe.Pointer(&chain)), uintptr(unsafe.Pointer(&info)), 0, 0)
if int32(ret) != 0 {
return 0, 0, OSStatus{"SecTrustGetResult", int32(ret)}
}
return chain, info, nil
}
func x509_SecTrustGetResult_trampoline()
//go:cgo_import_dynamic x509_SecTrustEvaluateWithError SecTrustEvaluateWithError "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecTrustEvaluateWithError(trustObj CFRef) error {
var errRef CFRef
ret := syscall(abi.FuncPCABI0(x509_SecTrustEvaluateWithError_trampoline), uintptr(trustObj), uintptr(unsafe.Pointer(&errRef)), 0, 0, 0, 0)
if int32(ret) != 1 {
errStr := CFErrorCopyDescription(errRef)
err := fmt.Errorf("x509: %s", CFStringToString(errStr))
CFRelease(errRef)
CFRelease(errStr)
return err
}
return nil
}
func x509_SecTrustEvaluateWithError_trampoline()
//go:cgo_import_dynamic x509_SecTrustGetCertificateCount SecTrustGetCertificateCount "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecTrustGetCertificateCount(trustObj CFRef) int {
ret := syscall(abi.FuncPCABI0(x509_SecTrustGetCertificateCount_trampoline), uintptr(trustObj), 0, 0, 0, 0, 0)
return int(ret)
}
func x509_SecTrustGetCertificateCount_trampoline()
//go:cgo_import_dynamic x509_SecTrustGetCertificateAtIndex SecTrustGetCertificateAtIndex "/System/Library/Frameworks/Security.framework/Versions/A/Security"
func SecTrustGetCertificateAtIndex(trustObj CFRef, i int) CFRef {
ret := syscall(abi.FuncPCABI0(x509_SecTrustGetCertificateAtIndex_trampoline), uintptr(trustObj), uintptr(i), 0, 0, 0, 0)
return CFRef(ret)
}
func x509_SecTrustGetCertificateAtIndex_trampoline()

View File

@ -18,3 +18,21 @@ TEXT ·x509_SecTrustSettingsCopyTrustSettings_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustSettingsCopyTrustSettings(SB) JMP x509_SecTrustSettingsCopyTrustSettings(SB)
TEXT ·x509_SecPolicyCopyProperties_trampoline(SB),NOSPLIT,$0-0 TEXT ·x509_SecPolicyCopyProperties_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecPolicyCopyProperties(SB) JMP x509_SecPolicyCopyProperties(SB)
TEXT ·x509_SecTrustCreateWithCertificates_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustCreateWithCertificates(SB)
TEXT ·x509_SecCertificateCreateWithData_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecCertificateCreateWithData(SB)
TEXT ·x509_SecPolicyCreateSSL_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecPolicyCreateSSL(SB)
TEXT ·x509_SecTrustSetVerifyDate_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustSetVerifyDate(SB)
TEXT ·x509_SecTrustEvaluate_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustEvaluate(SB)
TEXT ·x509_SecTrustGetResult_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustGetResult(SB)
TEXT ·x509_SecTrustEvaluateWithError_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustEvaluateWithError(SB)
TEXT ·x509_SecTrustGetCertificateCount_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustGetCertificateCount(SB)
TEXT ·x509_SecTrustGetCertificateAtIndex_trampoline(SB),NOSPLIT,$0-0
JMP x509_SecTrustGetCertificateAtIndex(SB)

View File

@ -7,109 +7,93 @@
package x509 package x509
import ( import (
"bytes"
macOS "crypto/x509/internal/macos" macOS "crypto/x509/internal/macos"
"fmt" "errors"
"internal/godebug"
"os"
) )
var debugDarwinRoots = godebug.Get("x509roots") == "1"
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
return nil, nil certs := macOS.CFArrayCreateMutable()
} defer macOS.ReleaseCFArray(certs)
leaf := macOS.SecCertificateCreateWithData(c.Raw)
macOS.CFArrayAppendValue(certs, leaf)
if opts.Intermediates != nil {
for _, lc := range opts.Intermediates.lazyCerts {
c, err := lc.getCert()
if err != nil {
return nil, err
}
sc := macOS.SecCertificateCreateWithData(c.Raw)
macOS.CFArrayAppendValue(certs, sc)
}
}
func loadSystemRoots() (*CertPool, error) { policies := macOS.CFArrayCreateMutable()
var trustedRoots []*Certificate defer macOS.ReleaseCFArray(policies)
untrustedRoots := make(map[string]bool) sslPolicy := macOS.SecPolicyCreateSSL(opts.DNSName)
macOS.CFArrayAppendValue(policies, sslPolicy)
// macOS has three trust domains: one for CAs added by users to their trustObj, err := macOS.SecTrustCreateWithCertificates(certs, policies)
// "login" keychain, one for CAs added by Admins to the "System" keychain, if err != nil {
// and one for the CAs that ship with the OS. return nil, err
for _, domain := range []macOS.SecTrustSettingsDomain{ }
macOS.SecTrustSettingsDomainUser, defer macOS.CFRelease(trustObj)
macOS.SecTrustSettingsDomainAdmin,
macOS.SecTrustSettingsDomainSystem, if !opts.CurrentTime.IsZero() {
} { dateRef := macOS.TimeToCFDateRef(opts.CurrentTime)
certs, err := macOS.SecTrustSettingsCopyCertificates(domain) defer macOS.CFRelease(dateRef)
if err == macOS.ErrNoTrustSettings { if err := macOS.SecTrustSetVerifyDate(trustObj, dateRef); err != nil {
continue
} else if err != nil {
return nil, err return nil, err
} }
defer macOS.CFRelease(certs) }
for i := 0; i < macOS.CFArrayGetCount(certs); i++ { // TODO(roland): we may want to allow passing in SCTs via VerifyOptions and
c := macOS.CFArrayGetValueAtIndex(certs, i) // set them via SecTrustSetSignedCertificateTimestamps, since Apple will
cert, err := exportCertificate(c) // always enforce its SCT requirements, and there are still _some_ people
if err != nil { // using TLS or OCSP for that.
if debugDarwinRoots {
fmt.Fprintf(os.Stderr, "crypto/x509: domain %d, certificate #%d: %v\n", domain, i, err)
}
continue
}
var result macOS.SecTrustSettingsResult if err := macOS.SecTrustEvaluateWithError(trustObj); err != nil {
if domain == macOS.SecTrustSettingsDomainSystem { return nil, err
// 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 { chain := [][]*Certificate{{}}
// "Note the distinction between the results kSecTrustSettingsResultTrustRoot numCerts := macOS.SecTrustGetCertificateCount(trustObj)
// and kSecTrustSettingsResultTrustAsRoot: The former can only be applied to for i := 0; i < numCerts; i++ {
// root (self-signed) certificates; the latter can only be applied to certRef := macOS.SecTrustGetCertificateAtIndex(trustObj, i)
// non-root certificates." cert, err := exportCertificate(certRef)
case macOS.SecTrustSettingsResultTrustRoot: if err != nil {
if isRootCertificate(cert) { return nil, err
trustedRoots = append(trustedRoots, cert) }
} chain[0] = append(chain[0], cert)
case macOS.SecTrustSettingsResultTrustAsRoot: }
if !isRootCertificate(cert) { if len(chain[0]) == 0 {
trustedRoots = append(trustedRoots, cert) // This should _never_ happen, but to be safe
} return nil, errors.New("x509: macOS certificate verification internal error")
}
case macOS.SecTrustSettingsResultDeny: if opts.DNSName != "" {
// Add this certificate to untrustedRoots, which are subtracted // If we have a DNS name, apply our own name verification
// from trustedRoots, so that we don't have to evaluate policies if err := chain[0][0].VerifyHostname(opts.DNSName); err != nil {
// for every root in the system domain, but still apply user and return nil, err
// 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() keyUsages := opts.KeyUsages
for _, cert := range trustedRoots { if len(keyUsages) == 0 {
if !untrustedRoots[string(cert.Raw)] { keyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth}
pool.AddCert(cert) }
// If any key usage is acceptable then we're done.
for _, usage := range keyUsages {
if usage == ExtKeyUsageAny {
return chain, nil
} }
} }
return pool, nil
if !checkChainForKeyUsage(chain[0], keyUsages) {
return nil, CertificateInvalidError{c, IncompatibleUsage, ""}
}
return chain, nil
} }
// exportCertificate returns a *Certificate for a SecCertificateRef. // exportCertificate returns a *Certificate for a SecCertificateRef.
@ -124,116 +108,6 @@ func exportCertificate(cert macOS.CFRef) (*Certificate, error) {
return ParseCertificate(der) return ParseCertificate(der)
} }
// isRootCertificate reports whether Subject and Issuer match. func loadSystemRoots() (*CertPool, error) {
func isRootCertificate(cert *Certificate) bool { return nil, nil
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
// certificates 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
} }

View File

@ -2,38 +2,121 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package x509 package x509_test
import ( import (
"os" "crypto/tls"
"os/exec" "crypto/x509"
"internal/testenv"
"testing" "testing"
"time" "time"
) )
func TestSystemRoots(t *testing.T) { func TestPlatformVerifier(t *testing.T) {
t0 := time.Now() if !testenv.HasExternalNetwork() {
sysRoots, err := loadSystemRoots() // actual system roots t.Skip()
sysRootsDuration := time.Since(t0)
if err != nil {
t.Fatalf("failed to read system roots: %v", err)
} }
t.Logf("loadSystemRoots: %v", sysRootsDuration) getChain := func(host string) []*x509.Certificate {
t.Helper()
// There are 174 system roots on Catalina, and 163 on iOS right now, require c, err := tls.Dial("tcp", host+":443", &tls.Config{InsecureSkipVerify: true})
// at least 100 to make sure this is not completely broken. if err != nil {
if want, have := 100, sysRoots.len(); have < want { t.Fatalf("tls connection failed: %s", err)
t.Errorf("want at least %d system roots, have %d", want, have) }
return c.ConnectionState().PeerCertificates
} }
if t.Failed() { tests := []struct {
cmd := exec.Command("security", "dump-trust-settings") name string
cmd.Stdout, cmd.Stderr = os.Stderr, os.Stderr host string
cmd.Run() verifyName string
cmd = exec.Command("security", "dump-trust-settings", "-d") verifyTime time.Time
cmd.Stdout, cmd.Stderr = os.Stderr, os.Stderr verifyEKU []x509.ExtKeyUsage
cmd.Run() expectedErr string
}{
{
// whatever google.com serves should, hopefully, be trusted
name: "valid chain",
host: "google.com",
},
{
name: "expired leaf",
host: "expired.badssl.com",
expectedErr: "x509: “*.badssl.com” certificate is expired",
},
{
name: "wrong host for leaf",
host: "wrong.host.badssl.com",
verifyName: "wrong.host.badssl.com",
expectedErr: "x509: “*.badssl.com” certificate name does not match input",
},
{
name: "self-signed leaf",
host: "self-signed.badssl.com",
expectedErr: "x509: “*.badssl.com” certificate is not trusted",
},
{
name: "untrusted root",
host: "untrusted-root.badssl.com",
expectedErr: "x509: “BadSSL Untrusted Root Certificate Authority” certificate is not trusted",
},
{
name: "revoked leaf",
host: "revoked.badssl.com",
expectedErr: "x509: “revoked.badssl.com” certificate is revoked",
},
{
name: "leaf missing SCTs",
host: "no-sct.badssl.com",
expectedErr: "x509: “no-sct.badssl.com” certificate is not standards compliant",
},
{
name: "expired leaf (custom time)",
host: "google.com",
verifyTime: time.Time{}.Add(time.Hour),
expectedErr: "x509: “*.google.com” certificate is expired",
},
{
name: "valid chain (custom time)",
host: "google.com",
verifyTime: time.Now(),
},
{
name: "leaf doesn't have acceptable ExtKeyUsage",
host: "google.com",
expectedErr: "x509: certificate specifies an incompatible key usage",
verifyEKU: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
chain := getChain(tc.host)
var opts x509.VerifyOptions
if len(chain) > 1 {
opts.Intermediates = x509.NewCertPool()
for _, c := range chain[1:] {
opts.Intermediates.AddCert(c)
}
}
if tc.verifyName != "" {
opts.DNSName = tc.verifyName
}
if !tc.verifyTime.IsZero() {
opts.CurrentTime = tc.verifyTime
}
if len(tc.verifyEKU) > 0 {
opts.KeyUsages = tc.verifyEKU
}
_, err := chain[0].Verify(opts)
if err != nil && tc.expectedErr == "" {
t.Errorf("unexpected verification error: %s", err)
} else if err != nil && err.Error() != tc.expectedErr {
t.Errorf("unexpected verification error: got %q, want %q", err.Error(), tc.expectedErr)
} else if err == nil && tc.expectedErr != "" {
t.Errorf("unexpected verification success: want %q", tc.expectedErr)
}
})
} }
} }

View File

@ -741,8 +741,8 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
} }
} }
// Use Windows's own verification and chain building. // Use platform verifiers, where available
if opts.Roots == nil && runtime.GOOS == "windows" { if opts.Roots == nil && (runtime.GOOS == "windows" || runtime.GOOS == "darwin") {
return c.systemVerify(&opts) return c.systemVerify(&opts)
} }

View File

@ -1836,8 +1836,8 @@ func TestLongChain(t *testing.T) {
} }
func TestSystemRootsError(t *testing.T) { func TestSystemRootsError(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
t.Skip("Windows does not use (or support) systemRoots") t.Skip("Windows and darwin do not use (or support) systemRoots")
} }
defer func(oldSystemRoots *CertPool) { systemRoots = oldSystemRoots }(systemRootsPool()) defer func(oldSystemRoots *CertPool) { systemRoots = oldSystemRoots }(systemRootsPool())

View File

@ -1975,8 +1975,8 @@ func TestMultipleRDN(t *testing.T) {
} }
func TestSystemCertPool(t *testing.T) { func TestSystemCertPool(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
t.Skip("not implemented on Windows; Issue 16736, 18609") t.Skip("not implemented on Windows (Issue 16736, 18609) or darwin (Issue 46287)")
} }
a, err := SystemCertPool() a, err := SystemCertPool()
if err != nil { if err != nil {