src/crypto/x509/internal/macos/corefoundation.go | 7 +++++++ src/crypto/x509/internal/macos/corefoundation.s | 2 ++ src/crypto/x509/internal/macos/security.go | 19 ++++++++++++++----- src/crypto/x509/root_darwin.go | 14 ++++++++++++-- src/crypto/x509/root_darwin_test.go | 10 +++++----- diff --git a/src/crypto/x509/internal/macos/corefoundation.go b/src/crypto/x509/internal/macos/corefoundation.go index 75c212910b67cfa6e31c36a74c861b03ac8ca4c9..2b20b52dc6cb724d149c3c9757be544a8701baf6 100644 --- a/src/crypto/x509/internal/macos/corefoundation.go +++ b/src/crypto/x509/internal/macos/corefoundation.go @@ -184,6 +184,13 @@ return CFRef(ret) } func x509_CFErrorCopyDescription_trampoline() +//go:cgo_import_dynamic x509_CFErrorGetCode CFErrorGetCode "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation" + +func CFErrorGetCode(errRef CFRef) int { + return int(syscall(abi.FuncPCABI0(x509_CFErrorGetCode_trampoline), uintptr(errRef), 0, 0, 0, 0, 0)) +} +func x509_CFErrorGetCode_trampoline() + //go:cgo_import_dynamic x509_CFStringCreateExternalRepresentation CFStringCreateExternalRepresentation "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation" func CFStringCreateExternalRepresentation(strRef CFRef) CFRef { diff --git a/src/crypto/x509/internal/macos/corefoundation.s b/src/crypto/x509/internal/macos/corefoundation.s index d69f72f795cd949504851c32a9b4be7599076f59..49cd084467b2c396e22cb2a06c983c6966dc8d7c 100644 --- a/src/crypto/x509/internal/macos/corefoundation.s +++ b/src/crypto/x509/internal/macos/corefoundation.s @@ -37,5 +37,7 @@ 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_CFErrorGetCode_trampoline(SB),NOSPLIT,$0-0 + JMP x509_CFErrorGetCode(SB) TEXT ·x509_CFStringCreateExternalRepresentation_trampoline(SB),NOSPLIT,$0-0 JMP x509_CFStringCreateExternalRepresentation(SB) diff --git a/src/crypto/x509/internal/macos/security.go b/src/crypto/x509/internal/macos/security.go index ef64bda49fd258bf42807b3792d913076e6781a4..0b7958eaa2891b84039a80df1e12ef75fc304228 100644 --- a/src/crypto/x509/internal/macos/security.go +++ b/src/crypto/x509/internal/macos/security.go @@ -8,7 +8,6 @@ package macOS import ( "errors" - "fmt" "internal/abi" "strconv" "unsafe" @@ -49,6 +48,15 @@ const ( SecTrustSettingsDomainUser SecTrustSettingsDomain = iota SecTrustSettingsDomainAdmin SecTrustSettingsDomainSystem +) + +const ( + // various macOS error codes that can be returned from + // SecTrustEvaluateWithError that we can map to Go cert + // verification error types. + ErrSecCertificateExpired = -67818 + ErrSecHostNameMismatch = -67602 + ErrSecNotTrusted = -67843 ) type OSStatus struct { @@ -190,17 +198,18 @@ func x509_SecTrustGetResult_trampoline() //go:cgo_import_dynamic x509_SecTrustEvaluateWithError SecTrustEvaluateWithError "/System/Library/Frameworks/Security.framework/Versions/A/Security" -func SecTrustEvaluateWithError(trustObj CFRef) error { +func SecTrustEvaluateWithError(trustObj CFRef) (int, 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)) + err := errors.New(CFStringToString(errStr)) + errCode := CFErrorGetCode(errRef) CFRelease(errRef) CFRelease(errStr) - return err + return errCode, err } - return nil + return 0, nil } func x509_SecTrustEvaluateWithError_trampoline() diff --git a/src/crypto/x509/root_darwin.go b/src/crypto/x509/root_darwin.go index ad365f577e79634644672a03a47294931776e5d7..c35885ace8358985204235759f0b93b892dd14fa 100644 --- a/src/crypto/x509/root_darwin.go +++ b/src/crypto/x509/root_darwin.go @@ -7,6 +7,7 @@ import ( macOS "crypto/x509/internal/macos" "errors" + "fmt" ) func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { @@ -54,8 +55,17 @@ // set them via SecTrustSetSignedCertificateTimestamps, since Apple will // always enforce its SCT requirements, and there are still _some_ people // using TLS or OCSP for that. - if err := macOS.SecTrustEvaluateWithError(trustObj); err != nil { - return nil, err + if ret, err := macOS.SecTrustEvaluateWithError(trustObj); err != nil { + switch ret { + case macOS.ErrSecCertificateExpired: + return nil, CertificateInvalidError{c, Expired, err.Error()} + case macOS.ErrSecHostNameMismatch: + return nil, HostnameError{c, opts.DNSName} + case macOS.ErrSecNotTrusted: + return nil, UnknownAuthorityError{Cert: c} + default: + return nil, fmt.Errorf("x509: %s", err) + } } chain := [][]*Certificate{{}} diff --git a/src/crypto/x509/root_darwin_test.go b/src/crypto/x509/root_darwin_test.go index 90a464f624960a75bd39d390d7e1d492a499a511..299cecf5563f682a2f18f1d51b440c0329d7b41c 100644 --- a/src/crypto/x509/root_darwin_test.go +++ b/src/crypto/x509/root_darwin_test.go @@ -42,23 +42,23 @@ }, { name: "expired leaf", host: "expired.badssl.com", - expectedErr: "x509: “*.badssl.com” certificate is expired", + expectedErr: "x509: certificate has expired or is not yet valid: “*.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", + expectedErr: "x509: certificate is valid for *.badssl.com, badssl.com, not wrong.host.badssl.com", }, { name: "self-signed leaf", host: "self-signed.badssl.com", - expectedErr: "x509: “*.badssl.com” certificate is not trusted", + expectedErr: "x509: certificate signed by unknown authority", }, { name: "untrusted root", host: "untrusted-root.badssl.com", - expectedErr: "x509: “BadSSL Untrusted Root Certificate Authority” certificate is not trusted", + expectedErr: "x509: certificate signed by unknown authority", }, { name: "revoked leaf", @@ -74,7 +74,7 @@ { name: "expired leaf (custom time)", host: "google.com", verifyTime: time.Time{}.Add(time.Hour), - expectedErr: "x509: “*.google.com” certificate is expired", + expectedErr: "x509: certificate has expired or is not yet valid: “*.google.com” certificate is expired", }, { name: "valid chain (custom time)",