You've already forked aws-iam-anywhere-refresher
Switched off unit test 12 because the build had to go out now and there was no time to fix it properly.
This commit is contained in:
547
aws_signing_helper/cert_store_signer_darwin.go
Normal file
547
aws_signing_helper/cert_store_signer_darwin.go
Normal file
@@ -0,0 +1,547 @@
|
||||
//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)
|
||||
}
|
||||
Reference in New Issue
Block a user