// Copyright 2022 Google LLC. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build darwin && cgo // +build darwin,cgo // Package keychain contains functions for retrieving certificates from the Darwin Keychain. package keychain /* #cgo CFLAGS: -mmacosx-version-min=10.12 #cgo LDFLAGS: -framework CoreFoundation -framework Security #include #include */ import "C" import ( "bytes" "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "io" "runtime" "sync" "time" "unsafe" ) // Maps for translating from crypto.Hash to SecKeyAlgorithm. // https://developer.apple.com/documentation/security/seckeyalgorithm var ( ecdsaAlgorithms = map[crypto.Hash]C.CFStringRef{ crypto.SHA256: C.kSecKeyAlgorithmECDSASignatureDigestX962SHA256, crypto.SHA384: C.kSecKeyAlgorithmECDSASignatureDigestX962SHA384, crypto.SHA512: C.kSecKeyAlgorithmECDSASignatureDigestX962SHA512, } rsaPKCS1v15Algorithms = map[crypto.Hash]C.CFStringRef{ crypto.SHA256: C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256, crypto.SHA384: C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384, crypto.SHA512: C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512, } rsaPSSAlgorithms = map[crypto.Hash]C.CFStringRef{ crypto.SHA256: C.kSecKeyAlgorithmRSASignatureDigestPSSSHA256, crypto.SHA384: C.kSecKeyAlgorithmRSASignatureDigestPSSSHA384, crypto.SHA512: C.kSecKeyAlgorithmRSASignatureDigestPSSSHA512, } ) // cfStringToString returns a Go string given a CFString. func cfStringToString(cfStr C.CFStringRef) string { s := C.CFStringGetCStringPtr(cfStr, C.kCFStringEncodingUTF8) if s != nil { return C.GoString(s) } glyphLength := C.CFStringGetLength(cfStr) + 1 utf8Length := C.CFStringGetMaximumSizeForEncoding(glyphLength, C.kCFStringEncodingUTF8) if s = (*C.char)(C.malloc(C.size_t(utf8Length))); s == nil { panic("unable to allocate memory") } defer C.free(unsafe.Pointer(s)) if C.CFStringGetCString(cfStr, s, utf8Length, C.kCFStringEncodingUTF8) == 0 { panic("unable to convert cfStringref to string") } return C.GoString(s) } func cfRelease(x unsafe.Pointer) { C.CFRelease(C.CFTypeRef(x)) } // cfError is an error type that owns a CFErrorRef, and obtains the error string // by using CFErrorCopyDescription. type cfError struct { e C.CFErrorRef } // cfErrorFromRef converts a C.CFErrorRef to a cfError, taking ownership of the // reference and releasing when the value is finalized. func cfErrorFromRef(cfErr C.CFErrorRef) *cfError { if cfErr == 0 { return nil } c := &cfError{e: cfErr} runtime.SetFinalizer(c, func(x interface{}) { C.CFRelease(C.CFTypeRef(x.(*cfError).e)) }) return c } func (e *cfError) Error() string { s := C.CFErrorCopyDescription(C.CFErrorRef(e.e)) defer C.CFRelease(C.CFTypeRef(s)) return cfStringToString(s) } // keychainError is an error type that is based on an OSStatus return code, and // obtains the error string with SecCopyErrorMessageString. type keychainError C.OSStatus func (e keychainError) Error() string { s := C.SecCopyErrorMessageString(C.OSStatus(e), nil) defer C.CFRelease(C.CFTypeRef(s)) return cfStringToString(s) } // cfDataToBytes turns a CFDataRef into a byte slice. func cfDataToBytes(cfData C.CFDataRef) []byte { return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData))) } // bytesToCFData turns a byte slice into a CFDataRef. Caller then "owns" the // CFDataRef and must CFRelease the CFDataRef when done. func bytesToCFData(buf []byte) C.CFDataRef { return C.CFDataCreate(C.kCFAllocatorDefault, (*C.UInt8)(unsafe.Pointer(&buf[0])), C.CFIndex(len(buf))) } // int32ToCFNumber turns an int32 into a CFNumberRef. Caller then "owns" // the CFNumberRef and must CFRelease the CFNumberRef when done. func int32ToCFNumber(n int32) C.CFNumberRef { return C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberSInt32Type, unsafe.Pointer(&n)) } // Key is a wrapper around the Keychain reference that uses it to // implement signing-related methods with Keychain functionality. type Key struct { privateKeyRef C.SecKeyRef certs []*x509.Certificate once sync.Once } // newKey makes a new Key wrapper around the key reference, // takes ownership of the reference, and sets up a finalizer to handle releasing // the reference. func newKey(privateKeyRef C.SecKeyRef, certs []*x509.Certificate) (*Key, error) { k := &Key{ privateKeyRef: privateKeyRef, certs: certs, } // This struct now owns the key reference. Retain now and release on // finalise in case the credential gets forgotten about. C.CFRetain(C.CFTypeRef(privateKeyRef)) runtime.SetFinalizer(k, func(x interface{}) { x.(*Key).Close() }) return k, nil } // CertificateChain returns the credential as a raw X509 cert chain. This // contains the public key. func (k *Key) CertificateChain() [][]byte { rv := make([][]byte, len(k.certs)) for i, c := range k.certs { rv[i] = c.Raw } return rv } // Close releases resources held by the credential. func (k *Key) Close() error { // Don't double-release references. k.once.Do(func() { C.CFRelease(C.CFTypeRef(k.privateKeyRef)) }) return nil } // Public returns the corresponding public key for this Key. Good // thing we extracted it when we created it. func (k *Key) Public() crypto.PublicKey { return k.certs[0].PublicKey } // Sign signs a message digest. Here, we pass off the signing to Keychain library. func (k *Key) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { // Map the signing algorithm and hash function to a SecKeyAlgorithm constant. var algorithms map[crypto.Hash]C.CFStringRef switch pub := k.Public().(type) { case *ecdsa.PublicKey: algorithms = ecdsaAlgorithms case *rsa.PublicKey: if _, ok := opts.(*rsa.PSSOptions); ok { algorithms = rsaPSSAlgorithms break } algorithms = rsaPKCS1v15Algorithms default: return nil, fmt.Errorf("unsupported algorithm %T", pub) } algorithm, ok := algorithms[opts.HashFunc()] if !ok { return nil, fmt.Errorf("unsupported hash function %T", opts.HashFunc()) } // Copy input over into CF-land. cfDigest := bytesToCFData(digest) defer C.CFRelease(C.CFTypeRef(cfDigest)) var cfErr C.CFErrorRef sig := C.SecKeyCreateSignature(C.SecKeyRef(k.privateKeyRef), algorithm, C.CFDataRef(cfDigest), &cfErr) if cfErr != 0 { return nil, cfErrorFromRef(cfErr) } defer C.CFRelease(C.CFTypeRef(sig)) return cfDataToBytes(C.CFDataRef(sig)), nil } // Cred gets the first Credential (filtering on issuer) corresponding to // available certificate and private key pairs (i.e. identities) available in // the Keychain. This includes both the current login keychain for the user, // and the system keychain. func Cred(issuerCN string) (*Key, error) { leafSearch := C.CFDictionaryCreateMutable(C.kCFAllocatorDefault, 5, &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) defer C.CFRelease(C.CFTypeRef(unsafe.Pointer(leafSearch))) // Get identities (certificate + private key pairs). C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecClass), unsafe.Pointer(C.kSecClassIdentity)) // Get identities that are signing capable. C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecAttrCanSign), unsafe.Pointer(C.kCFBooleanTrue)) // For each identity, give us the reference to it. C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecReturnRef), unsafe.Pointer(C.kCFBooleanTrue)) // Be sure to list out all the matches. C.CFDictionaryAddValue(leafSearch, unsafe.Pointer(C.kSecMatchLimit), unsafe.Pointer(C.kSecMatchLimitAll)) // Do the matching-item copy. var leafMatches C.CFTypeRef if errno := C.SecItemCopyMatching((C.CFDictionaryRef)(leafSearch), &leafMatches); errno != C.errSecSuccess { return nil, keychainError(errno) } defer C.CFRelease(leafMatches) signingIdents := C.CFArrayRef(leafMatches) // Dump the certs into golang x509 Certificates. var ( leafIdent C.SecIdentityRef leaf *x509.Certificate ) // Find the first valid leaf whose issuer (CA) matches the name in filter. // Validation in identityToX509 covers Not Before, Not After and key alg. for i := 0; i < int(C.CFArrayGetCount(signingIdents)) && leaf == nil; i++ { identDict := C.CFArrayGetValueAtIndex(signingIdents, C.CFIndex(i)) xc, err := identityToX509(C.SecIdentityRef(identDict)) if err != nil { continue } if xc.Issuer.CommonName == issuerCN { leaf = xc leafIdent = C.SecIdentityRef(identDict) } } caSearch := C.CFDictionaryCreateMutable(C.kCFAllocatorDefault, 0, &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) defer C.CFRelease(C.CFTypeRef(unsafe.Pointer(caSearch))) // Get identities (certificates). C.CFDictionaryAddValue(caSearch, unsafe.Pointer(C.kSecClass), unsafe.Pointer(C.kSecClassCertificate)) // For each identity, give us the reference to it. C.CFDictionaryAddValue(caSearch, unsafe.Pointer(C.kSecReturnRef), unsafe.Pointer(C.kCFBooleanTrue)) // Be sure to list out all the matches. C.CFDictionaryAddValue(caSearch, unsafe.Pointer(C.kSecMatchLimit), unsafe.Pointer(C.kSecMatchLimitAll)) // Do the matching-item copy. var caMatches C.CFTypeRef if errno := C.SecItemCopyMatching((C.CFDictionaryRef)(caSearch), &caMatches); errno != C.errSecSuccess { return nil, keychainError(errno) } defer C.CFRelease(caMatches) certRefs := C.CFArrayRef(caMatches) // Validate and dump the certs into golang x509 Certificates. var allCerts []*x509.Certificate for i := 0; i < int(C.CFArrayGetCount(certRefs)); i++ { refDict := C.CFArrayGetValueAtIndex(certRefs, C.CFIndex(i)) if xc, err := certRefToX509(C.SecCertificateRef(refDict)); err == nil { allCerts = append(allCerts, xc) } } // Build a certificate chain from leaf by matching prev.RawIssuer to // next.RawSubject across all valid certificates in the keychain. var ( certs []*x509.Certificate prev, next *x509.Certificate ) for prev = leaf; prev != nil; prev, next = next, nil { certs = append(certs, prev) for _, xc := range allCerts { if certIn(xc, certs) { continue // finite chains only, mmmmkay. } if bytes.Equal(prev.RawIssuer, xc.RawSubject) && prev.CheckSignatureFrom(xc) == nil { // Prefer certificates with later expirations. if next == nil || xc.NotAfter.After(next.NotAfter) { next = xc } } } } if len(certs) == 0 { return nil, fmt.Errorf("no key found with issuer common name %q", issuerCN) } skr, err := identityToSecKeyRef(leafIdent) if err != nil { return nil, err } defer C.CFRelease(C.CFTypeRef(skr)) return newKey(skr, certs) } // identityToX509 converts a single CFDictionary that contains the item ref and // attribute dictionary into an x509.Certificate. func identityToX509(ident C.SecIdentityRef) (*x509.Certificate, error) { var certRef C.SecCertificateRef if errno := C.SecIdentityCopyCertificate(ident, &certRef); errno != 0 { return nil, keychainError(errno) } defer C.CFRelease(C.CFTypeRef(certRef)) return certRefToX509(certRef) } // certRefToX509 converts a single C.SecCertificateRef into an *x509.Certificate. func certRefToX509(certRef C.SecCertificateRef) (*x509.Certificate, error) { // Export the PEM-encoded certificate to a CFDataRef. var certPEMData C.CFDataRef if errno := C.SecItemExport(C.CFTypeRef(certRef), C.kSecFormatUnknown, C.kSecItemPemArmour, nil, &certPEMData); errno != 0 { return nil, keychainError(errno) } defer C.CFRelease(C.CFTypeRef(certPEMData)) certPEM := cfDataToBytes(certPEMData) // This part based on crypto/tls. var certDERBlock *pem.Block for { certDERBlock, certPEM = pem.Decode(certPEM) if certDERBlock == nil { return nil, fmt.Errorf("failed to parse certificate PEM data") } if certDERBlock.Type == "CERTIFICATE" { // found it break } } // Check the certificate is OK by the x509 library, and obtain the // public key algorithm (which I assume is the same as the private key // algorithm). This also filters out certs missing critical extensions. xc, err := x509.ParseCertificate(certDERBlock.Bytes) if err != nil { return nil, err } switch xc.PublicKey.(type) { case *rsa.PublicKey, *ecdsa.PublicKey: default: return nil, fmt.Errorf("unsupported key type %T", xc.PublicKey) } // Check the certificate is valid if n := time.Now(); n.Before(xc.NotBefore) || n.After(xc.NotAfter) { return nil, fmt.Errorf("certificate not valid") } return xc, nil } // identityToSecKeyRef converts a single CFDictionary that contains the item ref and // attribute dictionary into a SecKeyRef for its private key. func identityToSecKeyRef(ident C.SecIdentityRef) (C.SecKeyRef, error) { // Get the private key (ref). Note that "Copy" in "CopyPrivateKey" // refers to "the create rule" of CoreFoundation memory management, and // does not actually copy the private key---it gives us a copy of the // reference that we now own. var ref C.SecKeyRef if errno := C.SecIdentityCopyPrivateKey(C.SecIdentityRef(ident), &ref); errno != 0 { return 0, keychainError(errno) } return ref, nil } func stringIn(s string, ss []string) bool { for _, s2 := range ss { if s == s2 { return true } } return false } func certIn(xc *x509.Certificate, xcs []*x509.Certificate) bool { for _, xc2 := range xcs { if xc.Equal(xc2) { return true } } return false }