
Some checks failed
🏗️✨ Test Build Workflow / 🖥️ 🔨 Build (push) Failing after 14m15s
Reviewed-on: Siteworxpro/aws-iam-anywhere-refresher#1 Co-authored-by: Ron Rise <ron@siteworxpro.com> Co-committed-by: Ron Rise <ron@siteworxpro.com>
548 lines
14 KiB
Go
548 lines
14 KiB
Go
//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 = aws4X509EcdsaSha256
|
|
case *rsa.PublicKey:
|
|
signingAlgorithm = aws4X509RsaSha256
|
|
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)
|
|
}
|