aws_signing_helper/cert_store_signer_darwin.go (416 lines of code) (raw):
//go:build darwin
package aws_signing_helper
// This code is based on the smimesign repository at
// https://github.com/github/smimesign
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
*/
import "C"
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"errors"
"fmt"
"io"
"log"
"sort"
"unsafe"
)
type DarwinCertStoreSigner struct {
identRef C.SecIdentityRef
keyRef C.SecKeyRef
certRef C.SecCertificateRef
cert *x509.Certificate
certChain []*x509.Certificate
}
// osStatus wraps a C.OSStatus
type osStatus C.OSStatus
const (
errSecItemNotFound = osStatus(C.errSecItemNotFound)
)
// Gets the matching identity and certificate for this CertIdentifier
// If there is more than one, only a list of the matching certificates is returned
func GetMatchingCertsAndIdentity(certIdentifier CertIdentifier) ([]C.SecIdentityRef, []C.SecCertificateRef, []CertificateContainer, error) {
queryMap := map[C.CFTypeRef]C.CFTypeRef{
C.CFTypeRef(C.kSecClass): C.CFTypeRef(C.kSecClassIdentity),
C.CFTypeRef(C.kSecReturnRef): C.CFTypeRef(C.kCFBooleanTrue),
C.CFTypeRef(C.kSecMatchLimit): C.CFTypeRef(C.kSecMatchLimitAll),
}
query := mapToCFDictionary(queryMap)
if query == 0 {
return nil, nil, nil, errors.New("error creating CFDictionary")
}
defer C.CFRelease(C.CFTypeRef(query))
var absResult C.CFTypeRef
if err := osStatusError(C.SecItemCopyMatching(query, &absResult)); err != nil {
if err == errSecItemNotFound {
return nil, nil, nil, errors.New("unable to find matching identity in cert store")
}
return nil, nil, nil, err
}
defer C.CFRelease(C.CFTypeRef(absResult))
aryResult := C.CFArrayRef(absResult)
// identRefs aren't owned by us initially
numIdentRefs := C.CFArrayGetCount(aryResult)
identRefs := make([]C.CFTypeRef, numIdentRefs)
C.CFArrayGetValues(aryResult, C.CFRange{0, numIdentRefs}, (*unsafe.Pointer)(unsafe.Pointer(&identRefs[0])))
var certContainers []CertificateContainer
var certRefs []C.SecCertificateRef
var outputIdentRefs []C.SecIdentityRef
var isMatch bool
certContainerIndex := 0
for _, curIdentRef := range identRefs {
curCertRef, err := getCertRef(C.SecIdentityRef(curIdentRef))
if err != nil {
return nil, nil, nil, errors.New("unable to get cert ref")
}
curCert, err := exportCertRef(curCertRef)
if err != nil {
if Debug {
log.Printf("unable to parse certificate with error (%s) - skipping\n", err)
}
goto nextIteration
}
// Find whether there is a matching certificate
isMatch = certMatches(certIdentifier, *curCert)
if isMatch {
certContainers = append(certContainers, CertificateContainer{certContainerIndex, curCert, ""})
certContainerIndex += 1
// Assign to certRef and identRef at most once in the loop
// Both values are only useful if there is exactly one match in the certificate store
// When creating a signer, there has to be exactly one matching certificate
certRefs = append(certRefs, curCertRef)
// Note that only the SecIdentityRef needs to be retained since it was neither created nor copied
C.CFRetain(C.CFTypeRef(curIdentRef))
outputIdentRefs = append(outputIdentRefs, C.SecIdentityRef(curIdentRef))
}
nextIteration:
}
if Debug {
log.Printf("found %d matching identities\n", len(certContainers))
}
// It's the caller's responsibility to release each SecIdentityRef after use.
return outputIdentRefs, certRefs, certContainers, nil
}
// Gets the certificates that match the CertIdentifier
func GetMatchingCerts(certIdentifier CertIdentifier) ([]CertificateContainer, error) {
identRefs, certRefs, certContainers, err := GetMatchingCertsAndIdentity(certIdentifier)
for i, identRef := range identRefs {
C.CFRelease(C.CFTypeRef(identRef))
identRefs[i] = 0
}
for i, certRef := range certRefs {
C.CFRelease(C.CFTypeRef(certRef))
certRefs[i] = 0
}
return certContainers, err
}
// Creates a DarwinCertStoreSigner based on the identifying certificate
func GetCertStoreSigner(certIdentifier CertIdentifier, useLatestExpiringCert bool) (signer Signer, signingAlgorithm string, err error) {
var (
selectedCertContainer CertificateContainer
cert *x509.Certificate
identRef C.SecIdentityRef
certRef C.SecCertificateRef
keyRef C.SecKeyRef
)
identRefs, certRefs, certContainers, err := GetMatchingCertsAndIdentity(certIdentifier)
if err != nil {
goto fail
}
if len(certContainers) == 0 {
err = errors.New("no matching identities")
goto fail
}
if useLatestExpiringCert {
sort.Sort(CertificateContainerList(certContainers))
// Release the `SecIdentityRef`s and `SecCertificateRef`s that won't be used
for i, certContainer := range certContainers {
if i != len(certContainers)-1 {
C.CFRelease(C.CFTypeRef(identRefs[certContainer.Index]))
C.CFRelease(C.CFTypeRef(certRefs[certContainer.Index]))
identRefs[certContainer.Index] = 0
certRefs[certContainer.Index] = 0
}
}
} else {
if len(certContainers) > 1 {
err = errors.New("multiple matching identities")
goto fail
}
}
selectedCertContainer = certContainers[len(certContainers)-1]
if Debug {
log.Print(fmt.Sprintf("selected certificate: %s", DefaultCertContainerToString(selectedCertContainer)))
}
cert = selectedCertContainer.Cert
certRef = certRefs[selectedCertContainer.Index]
identRef = identRefs[selectedCertContainer.Index]
// Find the signing algorithm
switch cert.PublicKey.(type) {
case *ecdsa.PublicKey:
signingAlgorithm = aws4_x509_ecdsa_sha256
case *rsa.PublicKey:
signingAlgorithm = aws4_x509_rsa_sha256
default:
err = errors.New("unsupported algorithm")
goto fail
}
keyRef, err = getKeyRef(identRef)
if err != nil {
err = errors.New("unable to get key reference")
goto fail
}
return &DarwinCertStoreSigner{identRef, keyRef, certRef, cert, nil}, signingAlgorithm, nil
fail:
for i, identRef := range identRefs {
if identRef != 0 {
C.CFRelease(C.CFTypeRef(identRef))
identRefs[i] = 0
}
}
for i, certRef := range certRefs {
if certRef != 0 {
C.CFRelease(C.CFTypeRef(certRef))
certRefs[i] = 0
}
}
return nil, "", err
}
// Gets the certificate associated with this DarwinCertStoreSigner
func (signer *DarwinCertStoreSigner) Certificate() (*x509.Certificate, error) {
if signer.cert != nil {
return signer.cert, nil
}
certRef, err := signer.getCertRef()
if err != nil {
return nil, err
}
cert, err := exportCertRef(certRef)
if err != nil {
return nil, err
}
signer.cert = cert
return signer.cert, nil
}
// Gets the certificate chain associated with this DarwinCertStoreSigner
func (signer *DarwinCertStoreSigner) CertificateChain() ([]*x509.Certificate, error) {
if signer.certChain != nil {
return signer.certChain, nil
}
certRef, err := signer.getCertRef()
if err != nil {
return nil, err
}
policy := C.SecPolicyCreateSSL(0, 0)
var trustRef C.SecTrustRef
if err := osStatusError(C.SecTrustCreateWithCertificates(C.CFTypeRef(certRef), C.CFTypeRef(policy), &trustRef)); err != nil {
return nil, err
}
defer C.CFRelease(C.CFTypeRef(trustRef))
// var status C.SecTrustResultType
var cfErrRef C.CFErrorRef
if C.SecTrustEvaluateWithError(trustRef, &cfErrRef) {
return nil, cfErrorError(cfErrRef)
}
var (
nChain = C.SecTrustGetCertificateCount(trustRef)
certChain = make([]*x509.Certificate, 0, int(nChain))
)
certChainArr := C.SecTrustCopyCertificateChain(trustRef)
defer C.CFRelease(C.CFTypeRef(certChainArr))
for i := C.CFIndex(0); i < nChain; i++ {
chainCertRef := C.SecCertificateRef(C.CFArrayGetValueAtIndex(certChainArr, i))
if chainCertRef == 0 {
return nil, errors.New("nil certificate in chain")
}
chainCert, err := exportCertRef(chainCertRef)
if err != nil {
return nil, err
}
certChain = append(certChain, chainCert)
}
certChain = certChain[1:]
signer.certChain = certChain
return signer.certChain, nil
}
// Public implements the crypto.Signer interface and returns the public key associated with the signer
func (signer *DarwinCertStoreSigner) Public() crypto.PublicKey {
cert, err := signer.Certificate()
if err != nil {
return nil
}
return cert.PublicKey
}
// Closes the DarwinCertStoreSigner
func (signer *DarwinCertStoreSigner) Close() {
if signer.identRef != 0 {
C.CFRelease(C.CFTypeRef(signer.identRef))
signer.identRef = 0
}
if signer.keyRef != 0 {
C.CFRelease(C.CFTypeRef(signer.keyRef))
signer.keyRef = 0
}
if signer.certRef != 0 {
C.CFRelease(C.CFTypeRef(signer.certRef))
signer.certRef = 0
}
}
// Sign implements the crypto.Signer interface and signs the digest
func (signer *DarwinCertStoreSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
var hash []byte
switch opts.HashFunc() {
case crypto.SHA256:
sum := sha256.Sum256(digest)
hash = sum[:]
case crypto.SHA384:
sum := sha512.Sum384(digest)
hash = sum[:]
case crypto.SHA512:
sum := sha512.Sum512(digest)
hash = sum[:]
default:
return nil, ErrUnsupportedHash
}
keyRef, err := signer.getKeyRef()
if err != nil {
return nil, err
}
chash, err := bytesToCFData(hash)
if err != nil {
return nil, err
}
defer C.CFRelease(C.CFTypeRef(chash))
cert, err := signer.Certificate()
if err != nil {
return nil, err
}
algo, err := getAlgo(cert, opts.HashFunc())
if err != nil {
return nil, err
}
// sign the digest
var cfErrRef C.CFErrorRef
cSig := C.SecKeyCreateSignature(keyRef, algo, chash, &cfErrRef)
if err := cfErrorError(cfErrRef); err != nil {
C.CFRelease(C.CFTypeRef(cfErrRef))
return nil, err
}
if cSig == 0 {
return nil, errors.New("nil signature from SecKeyCreateSignature")
}
defer C.CFRelease(C.CFTypeRef(cSig))
sig := cfDataToBytes(cSig)
return sig, nil
}
// getAlgo decides which algorithm to use with this key type for the given hash.
func getAlgo(cert *x509.Certificate, hash crypto.Hash) (algo C.SecKeyAlgorithm, err error) {
switch cert.PublicKey.(type) {
case *ecdsa.PublicKey:
switch hash {
case crypto.SHA1:
algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA1
case crypto.SHA256:
algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA256
case crypto.SHA384:
algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA384
case crypto.SHA512:
algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA512
default:
err = ErrUnsupportedHash
}
case *rsa.PublicKey:
switch hash {
case crypto.SHA1:
algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1
case crypto.SHA256:
algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256
case crypto.SHA384:
algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384
case crypto.SHA512:
algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512
default:
err = ErrUnsupportedHash
}
default:
err = errors.New("unsupported key type")
}
return algo, err
}
// exportCertRef gets a *x509.Certificate for the given SecCertificateRef.
func exportCertRef(certRef C.SecCertificateRef) (*x509.Certificate, error) {
derRef := C.SecCertificateCopyData(certRef)
if derRef == 0 {
return nil, errors.New("error getting certificate from identity")
}
defer C.CFRelease(C.CFTypeRef(derRef))
der := cfDataToBytes(derRef)
crt, err := x509.ParseCertificate(der)
if err != nil {
return nil, err
}
return crt, nil
}
// getKeyRef gets the SecKeyRef for this identity's private key.
func getKeyRef(ref C.SecIdentityRef) (C.SecKeyRef, error) {
var keyRef C.SecKeyRef
if err := osStatusError(C.SecIdentityCopyPrivateKey(ref, &keyRef)); err != nil {
return 0, err
}
return keyRef, nil
}
// getKeyRef gets the SecKeyRef for this identity's private key.
func (signer DarwinCertStoreSigner) getKeyRef() (C.SecKeyRef, error) {
if signer.keyRef != 0 {
return signer.keyRef, nil
}
keyRef, err := getKeyRef(signer.identRef)
signer.keyRef = keyRef
return signer.keyRef, err
}
// getCertRef gets the SecCertificateRef for this identity's certificate.
func getCertRef(ref C.SecIdentityRef) (C.SecCertificateRef, error) {
var certRef C.SecCertificateRef
if err := osStatusError(C.SecIdentityCopyCertificate(ref, &certRef)); err != nil {
return 0, err
}
return certRef, nil
}
// getCertRef gets the identity's certificate reference
func (signer *DarwinCertStoreSigner) getCertRef() (C.SecCertificateRef, error) {
if signer.certRef != 0 {
return signer.certRef, nil
}
certRef, err := getCertRef(signer.identRef)
signer.certRef = certRef
return signer.certRef, err
}
// stringToCFData converts a Go string to a CFDataRef
func stringToCFData(str string) (C.CFDataRef, error) {
return bytesToCFData([]byte(str))
}
// cfDataToBytes converts a CFDataRef to a Go byte slice
func cfDataToBytes(cfdata C.CFDataRef) []byte {
nBytes := C.CFDataGetLength(cfdata)
bytesPtr := C.CFDataGetBytePtr(cfdata)
return C.GoBytes(unsafe.Pointer(bytesPtr), C.int(nBytes))
}
// bytesToCFData converts a Go byte slice to a CFDataRef
func bytesToCFData(gobytes []byte) (C.CFDataRef, error) {
var (
cptr = (*C.UInt8)(nil)
clen = C.CFIndex(len(gobytes))
)
if len(gobytes) > 0 {
cptr = (*C.UInt8)(&gobytes[0])
}
cdata := C.CFDataCreate(0, cptr, clen)
if cdata == 0 {
return 0, errors.New("error creating cdata")
}
return cdata, nil
}
// cfErrorError returns an error for a CFErrorRef unless it is nil
func cfErrorError(cerr C.CFErrorRef) error {
if cerr == 0 {
return nil
}
code := int(C.CFErrorGetCode(cerr))
if cdescription := C.CFErrorCopyDescription(cerr); cdescription != 0 {
defer C.CFRelease(C.CFTypeRef(cdescription))
if cstr := C.CFStringGetCStringPtr(cdescription, C.kCFStringEncodingUTF8); cstr != nil {
str := C.GoString(cstr)
return fmt.Errorf("CFError %d (%s)", code, str)
}
}
return fmt.Errorf("CFError %d", code)
}
// mapToCFDictionary converts a Go map[C.CFTypeRef]C.CFTypeRef to a CFDictionaryRef
func mapToCFDictionary(gomap map[C.CFTypeRef]C.CFTypeRef) C.CFDictionaryRef {
var (
n = len(gomap)
keys = make([]unsafe.Pointer, 0, n)
values = make([]unsafe.Pointer, 0, n)
)
for k, v := range gomap {
keys = append(keys, unsafe.Pointer(k))
values = append(values, unsafe.Pointer(v))
}
return C.CFDictionaryCreate(0, &keys[0], &values[0], C.CFIndex(n), nil, nil)
}
// osStatusError returns an error for an OSStatus unless it is errSecSuccess
func osStatusError(s C.OSStatus) error {
if s == C.errSecSuccess {
return nil
}
return osStatus(s)
}
// Error implements the error interface and returns a stringified error for this osStatus
func (s osStatus) Error() string {
return fmt.Sprintf("OSStatus %d", s)
}