SecSignVerifyTransform.c   [plain text]


/*
 *  SecSignVerifyTransform.c
 *  libsecurity_transform
 *
 *  Created by JOsborne on 3/11/10.
 *  Copyright 2010 Apple. All rights reserved.
 *
 */

#include "SecSignVerifyTransform.h"
#include "SecCustomTransform.h"
#include "Utilities.h"
#include <Security/Security.h>
#include "misc.h"


const static CFStringRef SignName = CFSTR("com.apple.security.Sign"), VerifyName = CFSTR("com.apple.security.Verify");
CFStringRef kSecKeyAttributeName = CFSTR("KEY"), kSecSignatureAttributeName = CFSTR("Signature"), kSecInputIsAttributeName = CFSTR("InputIs");
// Internally we force kSecInputIsAttributeName to one of these 3 things, you can use == rather then CFStringCompare once that happens
CFStringRef kSecInputIsPlainText = CFSTR("PlainText"), kSecInputIsDigest = CFSTR("Digest"), kSecInputIsRaw = CFSTR("Raw");

CFErrorRef do_sec_fail(OSStatus code, const char *func, const char *file, int line) {
	CFStringRef msg = CFStringCreateWithFormat(NULL, NULL, CFSTR("Internal error #%x at %s %s:%d"), code, func, file, line);
	CFErrorRef err = fancy_error(CFSTR("Internal CSSM error"), code, msg);
	CFRelease(msg);
	
	return err;
}
#define SEC_FAIL(err) if (err) { \
    SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, do_sec_fail(err, __func__, __FILE__, __LINE__)); \
    return (CFTypeRef)NULL; \
}
#define GET_SEC_FAIL(err) do_sec_fail(err, __func__, __FILE__, __LINE__)

CFErrorRef accumulate_data(CFMutableArrayRef *a, CFDataRef d) {
	if (!*a) {
		*a = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
		if (!*a) {
			return GetNoMemoryError();
		}
	}
	CFDataRef dc = CFDataCreateCopy(NULL, d);
	if (!dc) {
		return GetNoMemoryError();
	}
	CFIndex c = CFArrayGetCount(*a);
	CFArrayAppendValue(*a, dc);
	CFRelease(dc);
	if (CFArrayGetCount(*a) != c+1) {
		return GetNoMemoryError();
	}
	
	return NULL;
}

CFErrorRef fetch_and_clear_accumulated_data(CFMutableArrayRef *a, CFDataRef *data_out) {
	if (!*a) {
		*data_out = CFDataCreate(NULL, NULL, 0);
		return (*data_out) ? NULL : GetNoMemoryError();
	}

	CFIndex i, c = CFArrayGetCount(*a);
	CFIndex total = 0, prev_total = 0;
	
	for(i = 0; i < c; i++) {
		total += CFDataGetLength((CFDataRef)CFArrayGetValueAtIndex(*a, i));
		if (total < prev_total) {
			return GetNoMemoryError();
		}
		prev_total = total;
	}
	
	CFMutableDataRef out = CFDataCreateMutable(NULL, total);
	if (!out) {
		return GetNoMemoryError();
	}
	
	for(i = 0; i < c; i++) {
		CFDataRef d = (CFDataRef)CFArrayGetValueAtIndex(*a, i);
		CFDataAppendBytes(out, CFDataGetBytePtr(d), CFDataGetLength(d));
	}
	
	if (CFDataGetLength(out) != total) {
		CFRelease(out);
		return GetNoMemoryError();
	}
	
	CFArrayRef accumulator = *a;
	CFRelease(accumulator);
	*a = NULL;
	
	// This might be nice:
	//   *data_out = CFDataCreateCopy(NULL, out);
	//   CFRelease(out);
	// but that is slow (for large values) AND isn't really all that important anyway
	
	*data_out = out;
	
	return NULL;
}

struct digest_mapping {
	// These 3 values are "search values"
	CSSM_ALGORITHMS kclass;
	CFStringRef digest_name;
	int digest_length;
	
	// "data values"
	CSSM_ALGORITHMS plain_text_algo, digest_algo;
};

Boolean digest_mapping_equal(struct digest_mapping *a, struct digest_mapping *b) {
	if (a == b) {
		return TRUE;
	}
	
	if (a->kclass == b->kclass && a->digest_length == b->digest_length && !CFStringCompare(a->digest_name, b->digest_name, 0)) {
		return TRUE;
	}
	
	return FALSE;
}

CFHashCode digest_mapping_hash(struct digest_mapping *dm) {
	return CFHash(dm->digest_name) + dm->kclass + dm->digest_length;
}

CSSM_ALGORITHMS alg_for_signature_context(CFStringRef input_is, const struct digest_mapping *dm) {
	if (!CFStringCompare(kSecInputIsPlainText, input_is, 0)) {
		return dm->plain_text_algo;
	} else if (!CFStringCompare(kSecInputIsDigest, input_is, 0) || !CFStringCompare(kSecInputIsRaw, input_is, 0)) {
		return dm->kclass;
	} else {
		return CSSM_ALGID_NONE;
	}
}

CFErrorRef pick_sign_alg(CFStringRef digest, int digest_length, const CSSM_KEY *ckey, struct digest_mapping **picked) {
	static dispatch_once_t once = 0;
	static CFMutableSetRef algos = NULL;

	dispatch_once(&once, ^{
		struct digest_mapping digest_mappings_stack[] = {
			{CSSM_ALGID_RSA, kSecDigestSHA1, 0,   CSSM_ALGID_SHA1WithRSA, CSSM_ALGID_SHA1},
			{CSSM_ALGID_RSA, kSecDigestSHA1, 160,   CSSM_ALGID_SHA1WithRSA, CSSM_ALGID_SHA1},
			
			{CSSM_ALGID_RSA, kSecDigestMD2, 0,   CSSM_ALGID_MD2WithRSA, CSSM_ALGID_MD2},
			{CSSM_ALGID_RSA, kSecDigestMD2, 128,   CSSM_ALGID_MD2WithRSA, CSSM_ALGID_MD2},
			
			{CSSM_ALGID_RSA, kSecDigestMD5, 0,   CSSM_ALGID_MD5WithRSA, CSSM_ALGID_MD5},
			{CSSM_ALGID_RSA, kSecDigestMD5, 128,   CSSM_ALGID_MD5WithRSA, CSSM_ALGID_MD5},
			
			{CSSM_ALGID_RSA, kSecDigestSHA2, 0,   CSSM_ALGID_SHA512WithRSA, CSSM_ALGID_SHA512},
			{CSSM_ALGID_RSA, kSecDigestSHA2, 512,   CSSM_ALGID_SHA512WithRSA, CSSM_ALGID_SHA512},
			{CSSM_ALGID_RSA, kSecDigestSHA2, 384,   CSSM_ALGID_SHA384WithRSA, CSSM_ALGID_SHA384},
			{CSSM_ALGID_RSA, kSecDigestSHA2, 256,   CSSM_ALGID_SHA256WithRSA, CSSM_ALGID_SHA256},
			{CSSM_ALGID_RSA, kSecDigestSHA2, 224,   CSSM_ALGID_SHA224WithRSA, CSSM_ALGID_SHA224},
			
			
			{CSSM_ALGID_ECDSA, kSecDigestSHA1, 0,   CSSM_ALGID_SHA1WithECDSA, CSSM_ALGID_SHA1},
			{CSSM_ALGID_ECDSA, kSecDigestSHA1, 160,   CSSM_ALGID_SHA1WithECDSA, CSSM_ALGID_SHA1},
			
			{CSSM_ALGID_ECDSA, kSecDigestSHA2, 0,   CSSM_ALGID_SHA512WithECDSA, CSSM_ALGID_SHA512},
			{CSSM_ALGID_ECDSA, kSecDigestSHA2, 512,   CSSM_ALGID_SHA512WithECDSA, CSSM_ALGID_SHA512},
			{CSSM_ALGID_ECDSA, kSecDigestSHA2, 384,   CSSM_ALGID_SHA384WithECDSA, CSSM_ALGID_SHA384},
			{CSSM_ALGID_ECDSA, kSecDigestSHA2, 256,   CSSM_ALGID_SHA256WithECDSA, CSSM_ALGID_SHA256},
			{CSSM_ALGID_ECDSA, kSecDigestSHA2, 224,   CSSM_ALGID_SHA224WithECDSA, CSSM_ALGID_SHA224},
			
			{CSSM_ALGID_DSA, kSecDigestSHA1, 0,   CSSM_ALGID_SHA1WithDSA, CSSM_ALGID_SHA1},
			{CSSM_ALGID_DSA, kSecDigestSHA1, 160,   CSSM_ALGID_SHA1WithDSA, CSSM_ALGID_SHA1},
		};
		
		CFIndex mapping_count = sizeof(digest_mappings_stack)/sizeof(digest_mappings_stack[0]);
		void *digest_mappings = malloc(sizeof(digest_mappings_stack));
		memcpy(digest_mappings, digest_mappings_stack, sizeof(digest_mappings_stack));

		CFSetCallBacks dmcb = { .version = 0, .retain = NULL, .release = NULL, .copyDescription = NULL, .equal = (CFSetEqualCallBack)digest_mapping_equal, .hash = (CFSetHashCallBack)digest_mapping_hash };
		
		algos = CFSetCreateMutable(NULL, mapping_count, &dmcb);
		int i;
		for(i = 0; i < mapping_count; i++) {
			CFSetAddValue(algos, i + (struct digest_mapping *)digest_mappings);
		}
	});
	
	struct digest_mapping search;
	search.kclass = ckey->KeyHeader.AlgorithmId;
	search.digest_name = digest;
	search.digest_length = digest_length;
	
	struct digest_mapping *dmapping = (void*)CFSetGetValue(algos, &search);
	
	if (dmapping) {
		*picked = dmapping;
		return NULL;
	}
	
	// It is argueable better to gennerate these messages by looking at digest_mappings, but with only 3 keytypes and 4 digests (only one of which has signifigant length variations) a case statment is likely the best way.
	switch (ckey->KeyHeader.AlgorithmId) {
		case CSSM_ALGID_RSA:
			return fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidAlgorithm, CFSTR("Invalid digest algorithm for RSA signature, choose one of: SHA1, SHA2 (512bits, 348bits, 256bits, or 224 bits), MD2, or MD5"));
			break;
			
		case CSSM_ALGID_ECDSA:
			return fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidAlgorithm, CFSTR("Invalid digest algorithm for ECDSA signature, choose one of: SHA1, or SHA2 (512bits, 348bits, 256bits, or 224 bits)"));
			break;
			
		case CSSM_ALGID_DSA:
			return fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidAlgorithm, CFSTR("Invalid digest algorithm for DSA signature, only SHA1 is supported"));
			break;
			
		default:
			return fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidAlgorithm, CFSTR("Expected key to be RSA, DSA or ECDSA key"));
	}
}

static SecTransformInstanceBlock SignTransform(CFStringRef name, 
							SecTransformRef newTransform, 
							SecTransformImplementationRef ref)
{
	SecTransformInstanceBlock instanceBlock = ^
	{
		CFErrorRef result = NULL;
		SecTransformCustomSetAttribute(ref, kSecKeyAttributeName, kSecTransformMetaAttributeRequired, kCFBooleanTrue);
		SecTransformCustomSetAttribute(ref, kSecInputIsAttributeName, kSecTransformMetaAttributeRequired, kCFBooleanTrue);

		__block CSSM_CC_HANDLE cch;
		__block SecKeyRef key = NULL;
		__block SecTransformDataBlock first_process_data = NULL;
		__block CFStringRef digest = NULL;
		__block int digest_length = 0;
		__block CFStringRef input_is = NULL;
		__block CFMutableArrayRef data_accumulator = NULL;
		__block struct digest_mapping *sign_alg;
		
		SecTransformDataBlock plain_text_process_data = 
		^(CFTypeRef value) 
		{
			CFDataRef d = value;
			OSStatus rc;
			
			if (d) {
				CSSM_DATA c_d;
				c_d.Data = (void*)CFDataGetBytePtr(d);
				c_d.Length = CFDataGetLength(d);
				
				rc = CSSM_SignDataUpdate(cch, &c_d, 1);
				SEC_FAIL(rc);
			} else {
				CSSM_DATA sig;
				const int max_sig_size = 32*1024;
				unsigned char *sig_data = malloc(max_sig_size);	
				sig.Data = sig_data;
				sig.Length = max_sig_size;
				
				rc = CSSM_SignDataFinal(cch, &sig);
				SEC_FAIL(rc);
				assert(sig.Length <= 32*1024);
				CSSM_DeleteContext(cch);
				// Could use NoCopy and hold onto the allocation, and that will be a good idea when we can have it not so oversized
				CFDataRef result = CFDataCreate(NULL, sig.Data, sig.Length);
				SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, result);
				CFRelease(result);
				free(sig_data);
				
				key = NULL;
				
				CFRelease(digest);
				digest = NULL;
				
				digest_length = 0;
				
				SecTransformSetDataAction(ref, kSecTransformActionProcessData,  first_process_data);
				
				return (CFTypeRef)NULL;
			}
			
			return SecTransformNoData();
		};
		
		SecTransformDataBlock cooked_process_data = 
		^(CFTypeRef value) 
		{
			CFDataRef d = value;
			if (d) {
				accumulate_data(&data_accumulator, d);
			} else {
				CSSM_DATA sig;
				const int max_sig_size = 32*1024;
				unsigned char *sig_data = malloc(max_sig_size);	
				sig.Data = sig_data;
				sig.Length = max_sig_size;
				
				CFDataRef alldata;
				CFErrorRef err = fetch_and_clear_accumulated_data(&data_accumulator, &alldata);
				if (err) {
					return (CFTypeRef)err;
				}
				CSSM_DATA c_d;
				c_d.Data = (void*)CFDataGetBytePtr(alldata);
				c_d.Length = CFDataGetLength(alldata);
				
				OSStatus rc = CSSM_SignData(cch, &c_d, 1, (input_is == kSecInputIsDigest) ? sign_alg->digest_algo : CSSM_ALGID_NONE, &sig);
				SEC_FAIL(rc);
				CFRelease(alldata);
				
				assert(sig.Length <= 32*1024);
				CSSM_DeleteContext(cch);
				// Could use NoCopy and hold onto the allocation, and that will be a good idea when we can have it not so oversized
				CFDataRef result = CFDataCreate(NULL, sig.Data, sig.Length);
				SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, result);
				CFRelease(result);
				free(sig_data);
				
				key = NULL;
				
				CFRelease(digest);
				digest = NULL;
				
				digest_length = 0;
				
				SecTransformSetDataAction(ref, kSecTransformActionProcessData, first_process_data);
				
				return (CFTypeRef)NULL;
			}
			
			return SecTransformNoData();
		};
				
		first_process_data = Block_copy(^(CFTypeRef value) 
		{
			OSStatus rc;
			if (key && digest && input_is) 
			{
				const CSSM_KEY *cssm_key;
				rc = SecKeyGetCSSMKey(key, &cssm_key);
				SEC_FAIL(rc);
				
				CFErrorRef bad_alg = pick_sign_alg(digest, digest_length, cssm_key, &sign_alg);
				if (bad_alg) 
				{
					return (CFTypeRef)bad_alg;
				}

				CSSM_CSP_HANDLE csp;
				rc = SecKeyGetCSPHandle(key, &csp);
				SEC_FAIL(rc);
				
				const CSSM_ACCESS_CREDENTIALS *access_cred;
				rc = SecKeyGetCredentials(key, CSSM_ACL_AUTHORIZATION_SIGN, kSecCredentialTypeDefault, &access_cred);
				SEC_FAIL(rc);
				
				CSSM_CSP_CreateSignatureContext(csp, alg_for_signature_context(input_is, sign_alg), access_cred, cssm_key, &cch);
				SEC_FAIL(rc);
				
				rc = CSSM_SignDataInit(cch);
				SEC_FAIL(rc);
								
				SecTransformDataBlock pd = (input_is == kSecInputIsPlainText) ? plain_text_process_data : cooked_process_data;

				SecTransformSetDataAction(ref, kSecTransformActionProcessData, pd);
				return pd(value);
			} 
			else 
			{
				SecTransformPushbackAttribute(ref, kSecTransformInputAttributeName, value);
				return SecTransformNoData();
			}
		});
		
		SecTransformSetDataAction(ref, kSecTransformActionProcessData, first_process_data);
		
		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecDigestTypeAttribute, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				digest = CFRetain(value);
				return value;
			});

		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecKeyAttributeName, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				if (value == NULL) {
                    return value;
                }
                
                const CSSM_KEY *cssm_key;
				key = (SecKeyRef)value;
			
				OSStatus rc = SecKeyGetCSSMKey(key, &cssm_key);
				SEC_FAIL(rc);
			
				if (!cssm_key->KeyHeader.KeyUsage & CSSM_KEYUSE_SIGN)
				{
					key = NULL;
                    
                    CFTypeRef error = CreateSecTransformErrorRef(kSecTransformErrorInvalidInput, "Key %@ can not be used to sign", key);
					SecTransformCustomSetAttribute(ref, kSecTransformAbortAttributeName, kSecTransformMetaAttributeValue, error);
                    return (CFTypeRef)NULL;
				}
				return value;
			});
		
		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecDigestLengthAttribute, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				CFNumberGetValue(value, kCFNumberIntType, &digest_length);
				return value;
			});
		
		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecInputIsAttributeName,
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				if (!CFStringCompare(value, kSecInputIsPlainText, 0)) {
					input_is = kSecInputIsPlainText;
				} else if (!CFStringCompare(value, kSecInputIsDigest, 0)) {
					input_is = kSecInputIsDigest;
				} else if (!CFStringCompare(value, kSecInputIsRaw, 0)) {
					input_is = kSecInputIsRaw;
				} else {
					input_is = NULL;
					return (CFTypeRef)fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidType, CFSTR("InputIs should be one of: PlainText, Digest, or Raw"));
				}
				return (CFTypeRef)input_is;
			});			
		
		SecTransformSetTransformAction(ref, kSecTransformActionFinalize, 
			^{
				Block_release(first_process_data);
				return (CFTypeRef)NULL;
			});
		
		return result;
	};
		
	return Block_copy(instanceBlock);	
}
							
SecTransformRef SecSignTransformCreate(SecKeyRef key, CFErrorRef* error)
{	
	static dispatch_once_t once;
	__block Boolean ok = TRUE;
			
	dispatch_block_t aBlock = ^
	{
		ok = SecTransformRegister(SignName, &SignTransform, error);
	};
	
	dispatch_once(&once, aBlock);

	if (!ok) 
	{
		return NULL;
	}	
	
	SecTransformRef tr = SecTransformCreate(SignName, error);
	if (!tr) {
		return tr;
	}
	SecTransformSetAttribute(tr, kSecKeyAttributeName, key, error);
	SecTransformSetAttribute(tr, kSecDigestTypeAttribute, kSecDigestSHA1, NULL);
	SecTransformSetAttribute(tr, kSecInputIsAttributeName, kSecInputIsPlainText, NULL);
	
	return tr;
}

static SecTransformInstanceBlock VerifyTransform(CFStringRef name, 
							SecTransformRef newTransform, 
							SecTransformImplementationRef ref)
{
	SecTransformInstanceBlock instanceBlock = ^
	{
		CFErrorRef result = NULL;
		SecTransformCustomSetAttribute(ref, kSecKeyAttributeName, kSecTransformMetaAttributeRequired, kCFBooleanTrue);
		SecTransformCustomSetAttribute(ref, kSecSignatureAttributeName, kSecTransformMetaAttributeRequired, kCFBooleanTrue);
		SecTransformCustomSetAttribute(ref, kSecInputIsAttributeName, kSecTransformMetaAttributeRequired, kCFBooleanTrue);
		
		__block CSSM_CC_HANDLE cch;
		__block const CSSM_KEY *cssm_key;
		__block CSSM_CSP_HANDLE csp;
		__block const CSSM_ACCESS_CREDENTIALS *access_cred;
		__block CFDataRef signature = NULL;
		__block unsigned char had_last_input = 0;
		__block CFStringRef digest = NULL;
		__block int digest_length = 0;
		__block SecTransformDataBlock first_process_data;
		__block SecKeyRef key = NULL;
		__block CFStringRef input_is = NULL;
		__block CFMutableArrayRef data_accumulator = NULL;
		__block struct digest_mapping *verify_alg = NULL;

		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecInputIsAttributeName, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				if (!CFStringCompare(value, kSecInputIsPlainText, 0)) {
					input_is = kSecInputIsPlainText;
				} else if (!CFStringCompare(value, kSecInputIsDigest, 0)) {
					input_is = kSecInputIsDigest;
				} else if (!CFStringCompare(value, kSecInputIsRaw, 0)) {
					input_is = kSecInputIsRaw;
				} else {
					input_is = NULL;
					return (CFTypeRef)fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidType, CFSTR("InputIs should be one of: PlainText, Digest, or Raw"));
				}
				return (CFTypeRef)input_is;
			});			
		
		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecKeyAttributeName, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				OSStatus rc;
                
                if (value == NULL) {
                    return value;
                }
			
				rc = SecKeyGetCSSMKey((SecKeyRef)value, &cssm_key);
				SEC_FAIL(rc);
			
				if (!cssm_key->KeyHeader.KeyUsage & CSSM_KEYUSE_VERIFY)
				{
					// This key cannot verify!
					return (CFTypeRef)CreateSecTransformErrorRef(kSecTransformErrorInvalidInput, "Key %@ can not be used to verify", key);
				}
		
				// we don't need to retain this because the owning transform is doing that for us
				key = (SecKeyRef) value;
				return value;
			});
		
		// We call this when we get the last input and when we get the signature.   If both are true when it is 
		// called we are really done, and it gennerates the output
		void (^done)(void) = 
		^{
			if (signature && had_last_input) 
			{
				CSSM_DATA sig;
				OSStatus rc;
				sig.Data = (void*)CFDataGetBytePtr(signature);
				sig.Length = CFDataGetLength(signature);
				CFRelease(signature);
				signature = NULL;
				
				if (input_is == kSecInputIsPlainText) {
					rc = CSSM_VerifyDataFinal(cch, &sig);
				} else {
					CFDataRef alldata;
					CFErrorRef err = fetch_and_clear_accumulated_data(&data_accumulator, &alldata);
					if (err) {
						SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, (CFTypeRef)err);
						return;
					}
					
					CSSM_DATA c_d;
					c_d.Data = (void*)CFDataGetBytePtr(alldata);
					c_d.Length = CFDataGetLength(alldata);
					
					rc = CSSM_VerifyData(cch, &c_d, 1, (input_is == kSecInputIsDigest) ? verify_alg->digest_algo : CSSM_ALGID_NONE, &sig);
				}
				CSSM_DeleteContext(cch);
				if (rc == 0 || rc == CSSMERR_CSP_VERIFY_FAILED) {
					SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, rc ? kCFBooleanFalse : kCFBooleanTrue);
					SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, NULL);
				} else {
					SecTransformCustomSetAttribute(ref, kSecTransformOutputAttributeName, kSecTransformMetaAttributeValue, GET_SEC_FAIL(rc));
				}
				had_last_input = FALSE;
				SecTransformSetDataAction(ref, kSecTransformActionProcessData, first_process_data);	
			}
		};
		
		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecSignatureAttributeName, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				if (value) {
					signature = CFRetain(value);
				}
			
				done();
			
				return (CFTypeRef)value;
			});
		
		SecTransformDataBlock process_data = 
			^(CFTypeRef value) 
			{
				OSStatus rc;
				CFDataRef d = value;
			
				if (d) {
					if (input_is == kSecInputIsPlainText) {
						CSSM_DATA c_d;
						c_d.Data = (void*)CFDataGetBytePtr(d);
						c_d.Length = CFDataGetLength(d);
					
						rc = CSSM_VerifyDataUpdate(cch, &c_d, 1);
						SEC_FAIL(rc);
					} else {
						accumulate_data(&data_accumulator, d);
					}
				} else {
					had_last_input = 1;
					done();
				}
			
				return SecTransformNoData();
			};
		
		first_process_data = 
			^(CFTypeRef value) 
			{
				if (key && digest && input_is) {
					// XXX: For RSA keys, signal an error if the digest size>keysize
				
					OSStatus rc = SecKeyGetCSPHandle(key, &csp);
					SEC_FAIL(rc);
				
					rc = SecKeyGetCredentials(key, CSSM_ACL_AUTHORIZATION_ANY, kSecCredentialTypeDefault, &access_cred);
					SEC_FAIL(rc);
				
					CFErrorRef bad_alg = pick_sign_alg(digest, digest_length, cssm_key, &verify_alg);
					if (bad_alg) {
						return (CFTypeRef)bad_alg;
					}
				
					CSSM_CSP_CreateSignatureContext(csp, alg_for_signature_context(input_is, verify_alg), NULL, cssm_key, &cch);
					SEC_FAIL(rc);
				
					rc = CSSM_VerifyDataInit(cch);
					SEC_FAIL(rc);

					SecTransformSetDataAction(ref, kSecTransformActionProcessData, process_data);
					return process_data(value);
				} else {
					SecTransformPushbackAttribute(ref, kSecTransformInputAttributeName, value);
					return SecTransformNoData();
				}
			};
		first_process_data = Block_copy(first_process_data);
		
		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecDigestTypeAttribute, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				digest = CFRetain(value);
				return value;
			});
		
		SecTransformSetTransformAction(ref, kSecTransformActionFinalize, 
			^{
				Block_release(first_process_data);
				return (CFTypeRef)NULL;
			});
		
		SecTransformSetAttributeAction(ref, kSecTransformActionAttributeNotification, kSecDigestLengthAttribute, 
			^(SecTransformAttributeRef ah, CFTypeRef value) 
			{
				CFNumberGetValue(value, kCFNumberIntType, &digest_length);
				return value;
			});
		
		SecTransformSetDataAction(ref, kSecTransformActionProcessData, first_process_data);	
		
		return result;
	};
	
	return Block_copy(instanceBlock);
}

SecTransformRef SecVerifyTransformCreate(SecKeyRef key, CFDataRef signature, CFErrorRef* error)
{
	static dispatch_once_t once;
	__block Boolean ok = TRUE;
			
	dispatch_block_t aBlock = ^
	{
		ok = SecTransformRegister(VerifyName, &VerifyTransform, error);
	};
	
	dispatch_once(&once, aBlock);

	if (!ok) 
	{
		return NULL;
	}
	
	
	SecTransformRef tr = SecTransformCreate(VerifyName, error);
	if (!tr) {
		return tr;
	}
	
	SecTransformSetAttribute(tr, kSecKeyAttributeName, key, error);
	if (signature) 
	{
		SecTransformSetAttribute(tr, kSecSignatureAttributeName, signature, error);
	}
	SecTransformSetAttribute(tr, kSecDigestTypeAttribute, kSecDigestSHA1, NULL);
	SecTransformSetAttribute(tr, kSecDigestTypeAttribute, kSecDigestSHA1, NULL);
	SecTransformSetAttribute(tr, kSecInputIsAttributeName, kSecInputIsPlainText, NULL);

	return tr;
}