SecKey.c   [plain text]


/*
 * Copyright (c) 2006-2011 Apple Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/* 
 * SecKey.c - CoreFoundation based key object
 */


#include <Security/SecKeyInternal.h>
#include <Security/SecItem.h>
#include <Security/SecItemPriv.h>
#include <Security/SecFramework.h>

#include "SecRSAKeyPriv.h"
#include "SecECKey.h"
#include "SecBasePriv.h"

#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFString.h>
#include <MacErrors.h>
#include <pthread.h>
#include <string.h>
#include <AssertMacros.h>
#include <security_utilities/debugging.h>
#include <CommonCrypto/CommonDigest.h>
#include <Security/SecAsn1Coder.h>
#include <Security/oidsalg.h>
#include <Security/SecInternal.h>
#include <Security/SecRandom.h>
#include <corecrypto/ccrng_system.h>
#include <asl.h>

static pthread_once_t kSecKeyRegisterClass = PTHREAD_ONCE_INIT;
static CFTypeID kSecKeyTypeID = _kCFRuntimeNotATypeID;

/* Forward declartions of static functions. */
static CFStringRef SecKeyDescribe(CFTypeRef cf);
static void SecKeyDestroy(CFTypeRef cf);

/* Static functions. */
#define MAX_DIGEST_LEN (CC_SHA512_DIGEST_LENGTH)

/* Currently length of SHA512 oid + 1 */
#define MAX_OID_LEN (10)

#define DER_MAX_DIGEST_INFO_LEN  (10 + MAX_DIGEST_LEN + MAX_OID_LEN)

/* Encode the digestInfo header into digestInfo and return the offset from
   digestInfo at which to put the actual digest.  Returns 0 if digestInfo
   won't fit within digestInfoLength bytes.

    0x30, topLen,
        0x30, algIdLen,
            0x06, oid.Len, oid.Data,
            0x05, 0x00
        0x04, digestLen
            digestData
*/
static size_t DEREncodeDigestInfoPrefix(const SecAsn1Oid *oid,
    size_t digestLength, uint8_t *digestInfo, size_t digestInfoLength) {
    size_t algIdLen = oid->Length + 4;
    size_t topLen = algIdLen + digestLength + 4;
	size_t totalLen = topLen + 2;

    if (totalLen > digestInfoLength) {
        return 0;
    }

    size_t ix = 0;
    digestInfo[ix++] = (SEC_ASN1_SEQUENCE | SEC_ASN1_CONSTRUCTED);
    digestInfo[ix++] = topLen;
    digestInfo[ix++] = (SEC_ASN1_SEQUENCE | SEC_ASN1_CONSTRUCTED);
    digestInfo[ix++] = algIdLen;
    digestInfo[ix++] = SEC_ASN1_OBJECT_ID;
    digestInfo[ix++] = oid->Length;
    memcpy(&digestInfo[ix], oid->Data, oid->Length);
    ix += oid->Length;
    digestInfo[ix++] = SEC_ASN1_NULL;
    digestInfo[ix++] = 0;
    digestInfo[ix++] = SEC_ASN1_OCTET_STRING;
    digestInfo[ix++] = digestLength;

    return ix;
}

static struct ccrng_system_state ccrng_system_state_seckey;

static void register_algs(void) {
    ccrng_seckey = (struct ccrng_state *)&ccrng_system_state_seckey;
    ccrng_system_init(&ccrng_system_state_seckey);
}


/*
 */
static CFDictionaryRef SecKeyGenerateAttributeDictionaryFor(SecKeyRef key,
                                                     CFTypeRef keyType,
                                                     CFDataRef privateBlob)
{
	CFAllocatorRef allocator = CFGetAllocator(key);
	DICT_DECLARE(25);
	CFDataRef pubKeyDigest = NULL, pubKeyBlob = NULL;
	CFDictionaryRef dict = NULL;

    size_t sizeValue = SecKeyGetSize(key, kSecKeyKeySizeInBits);
    CFNumberRef sizeInBits = CFNumberCreate(allocator, kCFNumberLongType, &sizeValue);

	/* encode the public key. */
    require_noerr(SecKeyCopyPublicBytes(key, &pubKeyBlob), errOut);
    require(pubKeyBlob, errOut);

	/* Calculate the digest of the public key. */
	require(pubKeyDigest = SecSHA1DigestCreate(allocator,
                                               CFDataGetBytePtr(pubKeyBlob), CFDataGetLength(pubKeyBlob)),
			errOut);

	DICT_ADDPAIR(kSecClass, kSecClassKey);
	DICT_ADDPAIR(kSecAttrKeyClass, privateBlob ? kSecAttrKeyClassPrivate : kSecAttrKeyClassPublic);
	DICT_ADDPAIR(kSecAttrApplicationLabel, pubKeyDigest);
	DICT_ADDPAIR(kSecAttrIsPermanent, kCFBooleanTrue);
	DICT_ADDPAIR(kSecAttrIsPrivate, kCFBooleanTrue);
	DICT_ADDPAIR(kSecAttrIsModifiable, kCFBooleanTrue);
	DICT_ADDPAIR(kSecAttrKeyType, keyType);
	DICT_ADDPAIR(kSecAttrKeySizeInBits, sizeInBits);
	DICT_ADDPAIR(kSecAttrEffectiveKeySize, sizeInBits);
	DICT_ADDPAIR(kSecAttrIsSensitive, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrWasAlwaysSensitive, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrIsExtractable, kCFBooleanTrue);
	DICT_ADDPAIR(kSecAttrWasNeverExtractable, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrCanEncrypt, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrCanDecrypt, kCFBooleanTrue);
	DICT_ADDPAIR(kSecAttrCanDerive, kCFBooleanTrue);
	DICT_ADDPAIR(kSecAttrCanSign, kCFBooleanTrue);
	DICT_ADDPAIR(kSecAttrCanVerify, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrCanSignRecover, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrCanVerifyRecover, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrCanWrap, kCFBooleanFalse);
	DICT_ADDPAIR(kSecAttrCanUnwrap, kCFBooleanTrue);
	DICT_ADDPAIR(kSecValueData, privateBlob ? privateBlob : pubKeyBlob);
    dict = DICT_CREATE(allocator);

errOut:
	// @@@ Zero out key material.
	CFReleaseSafe(pubKeyDigest);
	CFReleaseSafe(pubKeyBlob);
	CFReleaseSafe(sizeInBits);

	return dict;
}

CFDictionaryRef SecKeyGeneratePrivateAttributeDictionary(SecKeyRef key,
                                                         CFTypeRef keyType,
                                                         CFDataRef privateBlob)
{
    return SecKeyGenerateAttributeDictionaryFor(key, keyType, privateBlob);
}

CFDictionaryRef SecKeyGeneratePublicAttributeDictionary(SecKeyRef key, CFTypeRef keyType)
{
    return SecKeyGenerateAttributeDictionaryFor(key, keyType, NULL);
}

/*
 */

static CFStringRef SecKeyDescribe(CFTypeRef cf) {
    SecKeyRef key = (SecKeyRef)cf;
    return CFStringCreateWithFormat(kCFAllocatorDefault,NULL,CFSTR("<SecKeyRef: %p>"), key);
}

static void SecKeyDestroy(CFTypeRef cf) {
    SecKeyRef key = (SecKeyRef)cf;
    if (key->key_class->destroy)
        key->key_class->destroy(key);
}

static Boolean SecKeyEqual(CFTypeRef cf1, CFTypeRef cf2) {
    SecKeyRef key1 = (SecKeyRef)cf1;
    SecKeyRef key2 = (SecKeyRef)cf2;
    if (key1 == key2)
        return true;
    if (!key2 || key1->key_class != key2->key_class)
        return false;
    if (key1->key_class->extraBytes)
        return !memcmp(key1->key, key2->key, key1->key_class->extraBytes);

    /* TODO: Won't work when we get reference keys. */
    CFDictionaryRef d1, d2;
    d1 = SecKeyCopyAttributeDictionary(key1);
    d2 = SecKeyCopyAttributeDictionary(key2);
    Boolean result = CFEqual(d1, d2);
    CFReleaseSafe(d1);
    CFReleaseSafe(d2);
    return result;
}

static void SecKeyRegisterClass(void) {
	static const CFRuntimeClass kSecKeyClass = {
		0,													/* version */
		"SecKey",                                           /* class name */
		NULL,												/* init */
		NULL,												/* copy */
		SecKeyDestroy,                                      /* dealloc */
		SecKeyEqual,										/* equal */
		NULL,												/* hash */
		NULL,												/* copyFormattingDesc */
		SecKeyDescribe                                      /* copyDebugDesc */
	};

    kSecKeyTypeID = _CFRuntimeRegisterClass(&kSecKeyClass);
    register_algs();
}

/* Public API functions. */
CFTypeID SecKeyGetTypeID(void) {
    pthread_once(&kSecKeyRegisterClass, SecKeyRegisterClass);
    return kSecKeyTypeID;
}

static bool getBoolForKey(CFDictionaryRef dict, CFStringRef key, bool default_value) {
	CFTypeRef value = CFDictionaryGetValue(dict, key);
	if (value) {
		if (CFGetTypeID(value) == CFBooleanGetTypeID()) {
			return CFBooleanGetValue(value);
		} else {
			secwarning("Value %@ for key %@ is not bool", value, key);
		}
	}

	return default_value;
}

static OSStatus add_ref(CFTypeRef item, CFMutableDictionaryRef dict) {
	CFDictionarySetValue(dict, kSecValueRef, item);
	return SecItemAdd(dict, NULL);
}

static void merge_params_applier(const void *key, const void *value,
                                 void *context) {
	CFMutableDictionaryRef result = (CFMutableDictionaryRef)context;
	CFDictionaryAddValue(result, key, value);
}

/* Create a mutable dictionary that is based on the subdictionary for key
 with any attributes from the top level dict merged in. */
static CFMutableDictionaryRef merge_params(CFDictionaryRef dict,
                                           CFStringRef key) {
	CFDictionaryRef subdict = CFDictionaryGetValue(dict, key);
	CFMutableDictionaryRef result;

	if (subdict) {
		result = CFDictionaryCreateMutableCopy(NULL, 0, subdict);
		/* Add everything in dict not already in result to result. */
		CFDictionaryApplyFunction(dict, merge_params_applier, result);
	} else {
		result = CFDictionaryCreateMutableCopy(NULL, 0, dict);
	}

	/* Remove values that only belong in the top level dict. */
	CFDictionaryRemoveValue(result, kSecPublicKeyAttrs);
	CFDictionaryRemoveValue(result, kSecPrivateKeyAttrs);
	CFDictionaryRemoveValue(result, kSecAttrKeyType);
	CFDictionaryRemoveValue(result, kSecAttrKeySizeInBits);

	return result;
}

/* Generate a private/public keypair. */
OSStatus SecKeyGeneratePair(CFDictionaryRef parameters,
                            SecKeyRef *publicKey, SecKeyRef *privateKey) {
    OSStatus result = errSecUnsupportedAlgorithm;
    SecKeyRef privKey = NULL;
	SecKeyRef pubKey = NULL;
    CFMutableDictionaryRef pubParams = merge_params(parameters, kSecPublicKeyAttrs),
                           privParams = merge_params(parameters, kSecPrivateKeyAttrs);
	CFStringRef ktype = CFDictionaryGetValue(parameters, kSecAttrKeyType);

    require(ktype, errOut);

    if (CFEqual(ktype, kSecAttrKeyTypeEC)) {
        result = SecECKeyGeneratePair(parameters, &pubKey, &privKey);
    } else if (CFEqual(ktype, kSecAttrKeyTypeRSA)) {
        result = SecRSAKeyGeneratePair(parameters, &pubKey, &privKey);
    }

    require_noerr(result, errOut);

    /* Store the keys in the keychain if they are marked as permanent. */
    if (getBoolForKey(pubParams, kSecAttrIsPermanent, false)) {
        require_noerr(result = add_ref(pubKey, pubParams), errOut);
    }
    if (getBoolForKey(privParams, kSecAttrIsPermanent, false)) {
        require_noerr(result = add_ref(privKey, privParams), errOut);
    }

    if (publicKey) {
        *publicKey = pubKey;
        pubKey = NULL;
    }
    if (privateKey) {
        *privateKey = privKey;
        privKey = NULL;
    }

errOut:
	CFReleaseSafe(pubParams);
	CFReleaseSafe(privParams);
    CFReleaseSafe(pubKey);
    CFReleaseSafe(privKey);

    return result;
}

SecKeyRef SecKeyCreatePublicFromDER(CFAllocatorRef allocator,
    const SecAsn1Oid *oid, const SecAsn1Item *params,
    const SecAsn1Item *keyData) {
    SecKeyRef publicKey = NULL;
	if (SecAsn1OidCompare(oid, &CSSMOID_RSA)) {
        /* pkcs1 1 */
		publicKey = SecKeyCreateRSAPublicKey(kCFAllocatorDefault,
			keyData->Data, keyData->Length, kSecKeyEncodingPkcs1);
	} else if (SecAsn1OidCompare(oid, &CSSMOID_ecPublicKey)) {
        SecDERKey derKey = {
            .oid = oid->Data,
            .oidLength = oid->Length,
            .key = keyData->Data,
            .keyLength = keyData->Length,
        };
        if (params) {
            derKey.parameters = params->Data;
            derKey.parametersLength = params->Length;
        }
		publicKey = SecKeyCreateECPublicKey(kCFAllocatorDefault,
			(const uint8_t *)&derKey, sizeof(derKey), kSecDERKeyEncoding);
    } else {
		secwarning("Unsupported algorithm oid");
	}

    return publicKey;
}

SecKeyRef SecKeyCreate(CFAllocatorRef allocator,
    const SecKeyDescriptor *key_class, const uint8_t *keyData,
	CFIndex keyDataLength, SecKeyEncoding encoding) {
	check(key_class);

    size_t size = sizeof(struct __SecKey) + key_class->extraBytes;
    SecKeyRef result = (SecKeyRef)_CFRuntimeCreateInstance(allocator,
        SecKeyGetTypeID(), size - sizeof(CFRuntimeBase), NULL);
	if (result) {
		memset((char*)result + sizeof(result->_base), 0, size - sizeof(result->_base));
        result->key_class = key_class;
        if (key_class->extraBytes) {
            /* Make result->key point to the extraBytes we allocated. */
            result->key = ((char*)result) + sizeof(*result);
        }
        if (key_class->init) {
			OSStatus status;
			status = key_class->init(result, keyData, keyDataLength, encoding);
			if (status) {
				secwarning("init %s key: %d", key_class->name, status);
				CFRelease(result);
				result = NULL;
			}
		}
    }
    return result;
}

enum {
    kSecKeyDigestInfoSign,
    kSecKeyDigestInfoVerify
};

static OSStatus SecKeyDigestInfoSignVerify(
    SecKeyRef           key,            /* Private key */
	SecPadding          padding,		/* kSecPaddingPKCS1@@@ */
	const uint8_t       *dataToSign,	/* signature over this data */
	size_t              dataToSignLen,	/* length of dataToSign */
	uint8_t             *sig,			/* signature, RETURNED */
	size_t              *sigLen,        /* IN/OUT */
    int mode) {
    size_t digestInfoLength = DER_MAX_DIGEST_INFO_LEN;
    uint8_t digestInfo[digestInfoLength];
    const SecAsn1Oid *digestOid;
    size_t digestLen;

    switch (padding) {
#if 0
    case kSecPaddingPKCS1MD2:
        digestLen = CC_MD2_DIGEST_LENGTH;
        digestOid = &CSSMOID_MD2;
        break;
    case kSecPaddingPKCS1MD4:
        digestLen = CC_MD4_DIGEST_LENGTH;
        digestOid = &CSSMOID_MD4;
        break;
    case kSecPaddingPKCS1MD5:
        digestLen = CC_MD5_DIGEST_LENGTH;
        digestOid = &CSSMOID_MD5;
        break;
#endif
    case kSecPaddingPKCS1SHA1:
        digestLen = CC_SHA1_DIGEST_LENGTH;
        digestOid = &CSSMOID_SHA1;
        break;
#if 0
    case kSecPaddingPKCS1SHA224:
        digestLen = CC_SHA224_DIGEST_LENGTH;
        digestOid = &CSSMOID_SHA224;
        break;
    case kSecPaddingPKCS1SHA256:
        digestLen = CCSHA256_OUTPUT_SIZE;
        digestOid = &CSSMOID_SHA256;
        break;
    case kSecPaddingPKCS1SHA384:
        digestLen = CC_SHA384_DIGEST_LENGTH;
        digestOid = &CSSMOID_SHA384;
        break;
    case kSecPaddingPKCS1SHA512:
        digestLen = CC_SHA512_DIGEST_LENGTH;
        digestOid = &CSSMOID_SHA512;
        break;
#endif
    default:
        return errSecUnsupportedPadding;
    }

    if (dataToSignLen != digestLen)
        return errSecParam;

    size_t offset = DEREncodeDigestInfoPrefix(digestOid, digestLen,
        digestInfo, digestInfoLength);
    if (!offset)
        return errSecBufferTooSmall;

    /* Append the digest to the digestInfo prefix and adjust the length. */
    memcpy(&digestInfo[offset], dataToSign, digestLen);
    digestInfoLength = offset + digestLen;

    if (mode == kSecKeyDigestInfoSign) {
        return key->key_class->rawSign(key, kSecPaddingPKCS1,
            digestInfo, digestInfoLength, sig, sigLen);
    } else {
        return key->key_class->rawVerify(key, kSecPaddingPKCS1,
            digestInfo, digestInfoLength, sig, *sigLen);
    }

    return noErr;
}

OSStatus SecKeyRawSign(
    SecKeyRef           key,            /* Private key */
	SecPadding          padding,		/* kSecPaddingNone or kSecPaddingPKCS1 */
	const uint8_t       *dataToSign,	/* signature over this data */
	size_t              dataToSignLen,	/* length of dataToSign */
	uint8_t             *sig,			/* signature, RETURNED */
	size_t              *sigLen) {		/* IN/OUT */
    if (!key->key_class->rawSign)
        return errSecUnsupportedOperation;

    if (padding < kSecPaddingPKCS1MD2) {
        return key->key_class->rawSign(key, padding, dataToSign, dataToSignLen,
            sig, sigLen);
    } else {
        return SecKeyDigestInfoSignVerify(key, padding, dataToSign, dataToSignLen,
        sig, sigLen, kSecKeyDigestInfoSign);
    }
}

OSStatus SecKeyRawVerify(
    SecKeyRef           key,            /* Public key */
	SecPadding          padding,		/* kSecPaddingNone or kSecPaddingPKCS1 */
	const uint8_t       *signedData,	/* signature over this data */
	size_t              signedDataLen,	/* length of dataToSign */
	const uint8_t       *sig,			/* signature */
	size_t              sigLen) {		/* length of signature */
    if (!key->key_class->rawVerify)
        return errSecUnsupportedOperation;

    if (padding < kSecPaddingPKCS1MD2) {
        return key->key_class->rawVerify(key, padding, signedData, signedDataLen,
            sig, sigLen);
    } else {
        /* Casting away the constness of sig is safe since
           SecKeyDigestInfoSignVerify only modifies sig if
           mode == kSecKeyDigestInfoSign. */
        return SecKeyDigestInfoSignVerify(key, padding,
            signedData, signedDataLen, (uint8_t *)sig, &sigLen,
            kSecKeyDigestInfoVerify);
    }
}

OSStatus SecKeyEncrypt(
    SecKeyRef           key,                /* Public key */
	SecPadding          padding,			/* kSecPaddingNone, kSecPaddingPKCS1, kSecPaddingOAEP */
	const uint8_t		*plainText,
	size_t              plainTextLen,		/* length of plainText */
	uint8_t             *cipherText,	
	size_t              *cipherTextLen) {	/* IN/OUT */
    if (key->key_class->encrypt)
        return key->key_class->encrypt(key, padding, plainText, plainTextLen,
            cipherText, cipherTextLen);
    return errSecUnsupportedOperation;
}

OSStatus SecKeyDecrypt(
    SecKeyRef           key,                /* Private key */
	SecPadding          padding,			/* kSecPaddingNone, kSecPaddingPKCS1, kSecPaddingOAEP */
	const uint8_t       *cipherText,
	size_t              cipherTextLen,		/* length of cipherText */
	uint8_t             *plainText,	
	size_t              *plainTextLen) {	/* IN/OUT */
    if (key->key_class->decrypt)
        return key->key_class->decrypt(key, padding, cipherText, cipherTextLen,
            plainText, plainTextLen);
    return errSecUnsupportedOperation;
}

size_t SecKeyGetBlockSize(SecKeyRef key) {
    if (key->key_class->blockSize)
        return key->key_class->blockSize(key);
    return 0;
}

/* Private API functions. */

CFDictionaryRef SecKeyCopyAttributeDictionary(SecKeyRef key) {
    if (key->key_class->copyDictionary)
        return key->key_class->copyDictionary(key);
    return NULL;
}

SecKeyRef SecKeyCreateFromAttributeDictionary(CFDictionaryRef refAttributes) {
	/* TODO: Support having an allocator in refAttributes. */
 	CFAllocatorRef allocator = NULL;
	CFDataRef data = CFDictionaryGetValue(refAttributes, kSecValueData);
	CFTypeRef ktype = CFDictionaryGetValue(refAttributes, kSecAttrKeyType);
	SInt32 algorithm;
	SecKeyRef ref;

	/* First figure out the key type (algorithm). */
	if (CFGetTypeID(ktype) == CFNumberGetTypeID()) {
		CFNumberGetValue(ktype, kCFNumberSInt32Type, &algorithm);
	} else {
		secwarning("Unsupported key type: %@", ktype);
		return NULL;
	}

	/* TODO: The code below won't scale well, consider moving to something
	   table driven. */
	SInt32 class;
	CFTypeRef kclass = CFDictionaryGetValue(refAttributes, kSecAttrKeyClass);
	if (CFGetTypeID(kclass) == CFNumberGetTypeID()) {
		CFNumberGetValue(kclass, kCFNumberSInt32Type, &class);
	} else {
		secwarning("Unsupported key class: %@", kclass);
		return NULL;
	}

    switch (class) {
        case 0: // kSecAttrKeyClassPublic
            switch (algorithm) {
                case 42: // kSecAlgorithmRSA
                    ref = SecKeyCreateRSAPublicKey(allocator,
                                                   CFDataGetBytePtr(data), CFDataGetLength(data),
                                                   kSecKeyEncodingBytes);
                    break;
                case 43: // kSecAlgorithmECDSA
                    ref = SecKeyCreateECPublicKey(allocator,
                                                  CFDataGetBytePtr(data), CFDataGetLength(data),
                                                  kSecKeyEncodingBytes);
                    break;
                default:
                    secwarning("Unsupported public key type: %@", ktype);
                    ref = NULL;
                    break;
            };
            break;
        case 1: // kSecAttrKeyClassPrivate
            switch (algorithm) {
                case 42: // kSecAlgorithmRSA
                    ref = SecKeyCreateRSAPrivateKey(allocator,
                                                    CFDataGetBytePtr(data), CFDataGetLength(data),
                                                    kSecKeyEncodingBytes);
                    break;
                case 43: // kSecAlgorithmECDSA
                    ref = SecKeyCreateECPrivateKey(allocator,
                                                   CFDataGetBytePtr(data), CFDataGetLength(data),
                                                   kSecKeyEncodingBytes);
                    break;
                default:
                    secwarning("Unsupported private key type: %@", ktype);
                    ref = NULL;
                    break;
            };
            break;
        case 2: // kSecAttrKeyClassSymmetric
            secwarning("Unsupported symmetric key type: %@", ktype);
            ref = NULL;
            break;
        default:
            secwarning("Unsupported key class: %@", kclass);
            ref = NULL;
    }

	return ref;
}

/* TODO: This function should ensure that this keys algorithm matches the
   signature algorithm. */
static OSStatus SecKeyGetDigestInfo(SecKeyRef this, const SecAsn1AlgId *algId,
    const uint8_t *data, size_t dataLen, bool digestData,
    uint8_t *digestInfo, size_t *digestInfoLen /* IN/OUT */) {
    unsigned char *(*digestFcn)(const void *, CC_LONG, unsigned char *);
    CFIndex keyAlgID = kSecNullAlgorithmID;
    const SecAsn1Oid *digestOid;
    size_t digestLen;
    size_t offset = 0;

    /* Since these oids all have the same prefix, use switch. */
    if ((algId->algorithm.Length == CSSMOID_RSA.Length) &&
        !memcmp(algId->algorithm.Data, CSSMOID_RSA.Data,
            algId->algorithm.Length - 1)) {
        keyAlgID = kSecRSAAlgorithmID;
        switch (algId->algorithm.Data[algId->algorithm.Length - 1]) {
#if 0
        case 2: /* oidMD2WithRSA */
            digestFcn = CC_MD2;
            digestLen = CC_MD2_DIGEST_LENGTH;
            digestOid = &CSSMOID_MD2;
            break;
        case 3: /* oidMD4WithRSA */
            digestFcn = CC_MD4;
            digestLen = CC_MD4_DIGEST_LENGTH;
            digestOid = &CSSMOID_MD4;
            break;
        case 4: /* oidMD5WithRSA */
            digestFcn = CC_MD5;
            digestLen = CC_MD5_DIGEST_LENGTH;
            digestOid = &CSSMOID_MD5;
            break;
#endif /* 0 */
        case 5: /* oidSHA1WithRSA */
            digestFcn = CC_SHA1;
            digestLen = CC_SHA1_DIGEST_LENGTH;
            digestOid = &CSSMOID_SHA1;
            break;
        case 11: /* oidSHA256WithRSA */
            digestFcn = CC_SHA256;
            digestLen = CC_SHA256_DIGEST_LENGTH;
            digestOid = &CSSMOID_SHA256;
            break;
        case 12: /* oidSHA384WithRSA */
            /* pkcs1 12 */
            digestFcn = CC_SHA384;
            digestLen = CC_SHA384_DIGEST_LENGTH;
            digestOid = &CSSMOID_SHA384;
            break;
        case 13: /* oidSHA512WithRSA */
            digestFcn = CC_SHA512;
            digestLen = CC_SHA512_DIGEST_LENGTH;
            digestOid = &CSSMOID_SHA512;
            break;
        case 14: /* oidSHA224WithRSA */
            digestFcn = CC_SHA224;
            digestLen = CC_SHA224_DIGEST_LENGTH;
            digestOid = &CSSMOID_SHA224;
            break;
        default:
            secdebug("key", "unsupported rsa signature algorithm");
            return errSecUnsupportedAlgorithm;
        }
    } else if ((algId->algorithm.Length == CSSMOID_ECDSA_WithSHA224.Length) &&
        !memcmp(algId->algorithm.Data, CSSMOID_ECDSA_WithSHA224.Data,
            algId->algorithm.Length - 1)) {
        keyAlgID = kSecECDSAAlgorithmID;
        switch (algId->algorithm.Data[algId->algorithm.Length - 1]) {
        case 1: /* oidSHA224WithECDSA */
            digestFcn = CC_SHA224;
            digestLen = CC_SHA224_DIGEST_LENGTH;
            break;
        case 2: /* oidSHA256WithECDSA */
            digestFcn = CC_SHA256;
            digestLen = CC_SHA256_DIGEST_LENGTH;
            break;
        case 3: /* oidSHA384WithECDSA */
            /* pkcs1 12 */
            digestFcn = CC_SHA384;
            digestLen = CC_SHA384_DIGEST_LENGTH;
            break;
        case 4: /* oidSHA512WithECDSA */
            digestFcn = CC_SHA512;
            digestLen = CC_SHA512_DIGEST_LENGTH;
            break;
        default:
            secdebug("key", "unsupported ecdsa signature algorithm");
            return errSecUnsupportedAlgorithm;
        }
    } else if (SecAsn1OidCompare(&algId->algorithm, &CSSMOID_ECDSA_WithSHA1)) {
        keyAlgID = kSecECDSAAlgorithmID;
        digestFcn = CC_SHA1;
        digestLen = CC_SHA1_DIGEST_LENGTH;
    } else if (SecAsn1OidCompare(&algId->algorithm, &CSSMOID_SHA1)) {
        digestFcn = CC_SHA1;
        digestLen = CC_SHA1_DIGEST_LENGTH;
        digestOid = &CSSMOID_SHA1;
    } else if ((algId->algorithm.Length == CSSMOID_SHA224.Length) &&
        !memcmp(algId->algorithm.Data, CSSMOID_SHA224.Data, algId->algorithm.Length - 1))
    {
        switch (algId->algorithm.Data[algId->algorithm.Length - 1]) {
            case 4: /* OID_SHA224 */
                digestFcn = CC_SHA224;
                digestLen = CC_SHA224_DIGEST_LENGTH;
                digestOid = &CSSMOID_SHA224;
                break;
            case 1: /* OID_SHA256 */
                digestFcn = CC_SHA256;
                digestLen = CC_SHA256_DIGEST_LENGTH;
                digestOid = &CSSMOID_SHA256;
                break;
            case 2: /* OID_SHA384 */
                /* pkcs1 12 */
                digestFcn = CC_SHA384;
                digestLen = CC_SHA384_DIGEST_LENGTH;
                digestOid = &CSSMOID_SHA384;
                break;
            case 3: /* OID_SHA512 */
                digestFcn = CC_SHA512;
                digestLen = CC_SHA512_DIGEST_LENGTH;
                digestOid = &CSSMOID_SHA512;
                break;
            default:
                secdebug("key", "unsupported sha-2 signature algorithm");
                return errSecUnsupportedAlgorithm;
        }
    } else if (SecAsn1OidCompare(&algId->algorithm, &CSSMOID_MD5)) {
        digestFcn = CC_MD5;
        digestLen = CC_MD5_DIGEST_LENGTH;
        digestOid = &CSSMOID_MD5;
    } else {
        secdebug("key", "unsupported digesting algorithm");
        return errSecUnsupportedAlgorithm;
    }

    /* check key is appropriate for signature (superfluous for digest only oid) */
    if (keyAlgID == kSecNullAlgorithmID)
        keyAlgID = SecKeyGetAlgorithmID(this);
    else if (keyAlgID != SecKeyGetAlgorithmID(this))
        return errSecUnsupportedAlgorithm;

    switch(keyAlgID) {
        case kSecRSAAlgorithmID:
            offset = DEREncodeDigestInfoPrefix(digestOid, digestLen,
                                               digestInfo, *digestInfoLen);
            if (!offset)
                return errSecBufferTooSmall;
            break;
        case kSecDSAAlgorithmID:
            if (digestOid != &CSSMOID_SHA1)
                return errSecUnsupportedAlgorithm;
            break;
        case kSecECDSAAlgorithmID:
            break;
        default:
            secdebug("key", "unsupported signature algorithm");
            return errSecUnsupportedAlgorithm;
    }

    if (digestData) {
        if(dataLen>UINT32_MAX) /* Check for overflow with CC_LONG cast */
            return paramErr;
        digestFcn(data, (CC_LONG)dataLen, &digestInfo[offset]);
        *digestInfoLen = offset + digestLen;
    } else {
        if (dataLen != digestLen)
            return paramErr;
        memcpy(&digestInfo[offset], data, dataLen);
        *digestInfoLen = offset + dataLen;
    }

    return noErr;
}

OSStatus SecKeyDigestAndVerify(
    SecKeyRef           this,            /* Private key */
	const SecAsn1AlgId  *algId,         /* algorithm oid/params */
	const uint8_t       *dataToDigest,	/* signature over this data */
	size_t              dataToDigestLen,/* length of dataToDigest */
	const uint8_t       *sig,			/* signature to verify */
	size_t              sigLen) {		/* length of sig */
    size_t digestInfoLength = DER_MAX_DIGEST_INFO_LEN;
    uint8_t digestInfo[digestInfoLength];
    OSStatus status;

    status = SecKeyGetDigestInfo(this, algId, dataToDigest, dataToDigestLen, true,
        digestInfo, &digestInfoLength);
    if (status)
        return status;
    return SecKeyRawVerify(this, kSecPaddingPKCS1,
        digestInfo, digestInfoLength, sig, sigLen);
}

OSStatus SecKeyDigestAndSign(
    SecKeyRef           this,            /* Private key */
	const SecAsn1AlgId  *algId,         /* algorithm oid/params */
	const uint8_t       *dataToDigest,	/* signature over this data */
	size_t              dataToDigestLen,/* length of dataToDigest */
	uint8_t             *sig,			/* signature, RETURNED */
	size_t              *sigLen) {		/* IN/OUT */
    size_t digestInfoLength = DER_MAX_DIGEST_INFO_LEN;
    uint8_t digestInfo[digestInfoLength];
    OSStatus status;

    status = SecKeyGetDigestInfo(this, algId, dataToDigest, dataToDigestLen, true /* digest data */,
        digestInfo, &digestInfoLength);
    if (status)
        return status;
    return SecKeyRawSign(this, kSecPaddingPKCS1,
        digestInfo, digestInfoLength, sig, sigLen);
}

OSStatus SecKeyVerifyDigest(
                               SecKeyRef           this,            /* Private key */
                               const SecAsn1AlgId  *algId,         /* algorithm oid/params */
                               const uint8_t       *digestData,	/* signature over this digest */
                               size_t              digestDataLen,/* length of dataToDigest */
                               const uint8_t       *sig,			/* signature to verify */
                               size_t              sigLen) {		/* length of sig */
    size_t digestInfoLength = DER_MAX_DIGEST_INFO_LEN;
    uint8_t digestInfo[digestInfoLength];
    OSStatus status;

    status = SecKeyGetDigestInfo(this, algId, digestData, digestDataLen, false /* data is digest */,
                                 digestInfo, &digestInfoLength);
    if (status)
        return status;
    return SecKeyRawVerify(this, kSecPaddingPKCS1,
                           digestInfo, digestInfoLength, sig, sigLen);
}

OSStatus SecKeySignDigest(
                             SecKeyRef           this,            /* Private key */
                             const SecAsn1AlgId  *algId,         /* algorithm oid/params */
                             const uint8_t       *digestData,	/* signature over this digest */
                             size_t              digestDataLen,/* length of digestData */
                             uint8_t             *sig,			/* signature, RETURNED */
                             size_t              *sigLen) {		/* IN/OUT */
    size_t digestInfoLength = DER_MAX_DIGEST_INFO_LEN;
    uint8_t digestInfo[digestInfoLength];
    OSStatus status;

    status = SecKeyGetDigestInfo(this, algId, digestData, digestDataLen, false,
                                 digestInfo, &digestInfoLength);
    if (status)
        return status;
    return SecKeyRawSign(this, kSecPaddingPKCS1,
                         digestInfo, digestInfoLength, sig, sigLen);
}

CFIndex SecKeyGetAlgorithmID(SecKeyRef key) {
    /* This method was added to version 1 keys. */
    if (key->key_class->version > 0 && key->key_class->getAlgorithmID)
        return key->key_class->getAlgorithmID(key);
    /* All version 0 key were RSA. */
    return kSecRSAAlgorithmID;
}


OSStatus SecKeyCopyPublicBytes(SecKeyRef key, CFDataRef* serializedPublic) {
    if (key->key_class->version > 1 && key->key_class->copyPublic)
        return key->key_class->copyPublic(key, serializedPublic);
    return errSecUnimplemented;
}

SecKeyRef SecKeyCreateFromPublicBytes(CFAllocatorRef allocator, CFIndex algorithmID, const uint8_t *keyData, CFIndex keyDataLength)
{
    switch (algorithmID)
    {
        case kSecRSAAlgorithmID:
            return SecKeyCreateRSAPublicKey(allocator,
                                            keyData, keyDataLength,
                                            kSecKeyEncodingBytes);
        case kSecECDSAAlgorithmID:
            return SecKeyCreateECPublicKey(allocator,
                                           keyData, keyDataLength,
                                           kSecKeyEncodingBytes);
        default:
            return NULL;
    }
}

SecKeyRef SecKeyCreateFromPublicData(CFAllocatorRef allocator, CFIndex algorithmID, CFDataRef serialized)
{
    return SecKeyCreateFromPublicBytes(allocator, algorithmID, CFDataGetBytePtr(serialized), CFDataGetLength(serialized));
}

// This is a bit icky hack to avoid changing the vtable for
// SecKey.
size_t SecKeyGetSize(SecKeyRef key, SecKeySize whichSize)
{
    size_t result = SecKeyGetBlockSize(key);

    if (kSecECDSAAlgorithmID == SecKeyGetAlgorithmID(key)) {
        switch (whichSize) {
            case kSecKeyEncryptedDataSize:
                result = 0;
                break;
            case kSecKeySignatureSize:
                result = 2 * result + 6;
                if (result >= 66)
                    result += 2;
                break;
            case kSecKeyKeySizeInBits:
                if (result >= 66)
                    return 521;
        }
    }
            
    if (whichSize == kSecKeyKeySizeInBits)
            result *= 8;
        
    return result;

}