diff --git a/Configurations/ConfigFramework.xcconfig b/Configurations/ConfigFramework.xcconfig index 1eeeaa924..c1bb20819 100644 --- a/Configurations/ConfigFramework.xcconfig +++ b/Configurations/ConfigFramework.xcconfig @@ -5,7 +5,6 @@ DYLIB_COMPATIBILITY_VERSION = 1.5 DYLIB_CURRENT_VERSION = 1.5 PRODUCT_NAME = Sparkle WRAPPER_EXTENSION = framework -OTHER_LDFLAGS = -lcrypto FRAMEWORK_VERSION = A INFOPLIST_FILE = Info.plist GCC_PREFIX_HEADER = Sparkle.pch diff --git a/SUDSAVerifier.m b/SUDSAVerifier.m index fb69f0504..eb4dd2ff3 100644 --- a/SUDSAVerifier.m +++ b/SUDSAVerifier.m @@ -6,168 +6,352 @@ // Copyright 2006 Andy Matuschak. All rights reserved. // -// DSA stuff adapted from code provided by Allan Odgaard. Thanks, Allan! - #import "SUDSAVerifier.h" #import -#import -#import -#import -#import -#import -#import - -// Alex: Avoid prototype warning -static long b64decode(unsigned char* str); -static EVP_PKEY* load_dsa_key(char *key); - - -static long b64decode(unsigned char* str) -{ - unsigned char *cur, *start; - int d, dlast, phase; - unsigned char c; - static int table[256] = { - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 00-0F */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 10-1F */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, /* 20-2F */ - 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, /* 30-3F */ - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 40-4F */ - 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, /* 50-5F */ - -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, /* 60-6F */ - 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, /* 70-7F */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 80-8F */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 90-9F */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A0-AF */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* B0-BF */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* C0-CF */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* D0-DF */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* E0-EF */ - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 /* F0-FF */ - }; - - dlast = phase = 0; - start = str; - for (cur = str; *cur != '\0'; ++cur ) - { - if(*cur == '\n' || *cur == '\r'){phase = dlast = 0; continue;} - d = table[(unsigned int)*cur]; - if(d != -1) - { - switch(phase) - { - case 0: - ++phase; - break; - case 1: - c = ((dlast << 2) | ((d & 0x30) >> 4)); - *str++ = c; - ++phase; - break; - case 2: - c = (((dlast & 0xf) << 4) | ((d & 0x3c) >> 2)); - *str++ = c; - ++phase; - break; - case 3: - c = (((dlast & 0x03 ) << 6) | d); - *str++ = c; - phase = 0; - break; - } - dlast = d; - } - } - *str = '\0'; - return str - start; -} - -static EVP_PKEY* load_dsa_key(char *key) -{ - EVP_PKEY* pkey = NULL; - size_t keylen = strlen(key); - if (keylen <= INT_MAX) - { - BIO *bio; - if((bio = BIO_new_mem_buf(key, (int)keylen))) - { - DSA* dsa_key = 0; - if(PEM_read_bio_DSA_PUBKEY(bio, &dsa_key, NULL, NULL)) - { - if((pkey = EVP_PKEY_new())) - { - if(EVP_PKEY_assign_DSA(pkey, dsa_key) != 1) - { - DSA_free(dsa_key); - EVP_PKEY_free(pkey); - pkey = NULL; - } - } - } - BIO_free(bio); - } - } - return pkey; -} +#import + +/* CDSA Specific */ +static CSSM_CSP_HANDLE cdsaInit( void ); +static void cdsaRelease( CSSM_CSP_HANDLE cspHandle ); +static CSSM_KEY_PTR cdsaCreateKey( CFDataRef rawKey ); +static void cdsaReleaseKey( CSSM_KEY_PTR key ); +static BOOL cdsaVerifyKey( CSSM_CSP_HANDLE cspHandle, const CSSM_KEY_PTR key ); +static BOOL cdsaVerifySignature( CSSM_CSP_HANDLE cspHandle, const CSSM_KEY_PTR key, const CFDataRef msg, const CFDataRef signature ); +static CFDataRef cdsaCreateSHA1Digest( CSSM_CSP_HANDLE cspHandle, const CFDataRef bytes ); + +/* Helper Functions */ +static NSData *b64decode( NSString *str ); +static NSData *rawKeyData( NSString *str ); @implementation SUDSAVerifier +#pragma mark - + (BOOL)validatePath:(NSString *)path withEncodedDSASignature:(NSString *)encodedSignature withPublicDSAKey:(NSString *)pkeyString { + if ( !encodedSignature || !pkeyString || !path ) return NO; BOOL result = NO; - if (!encodedSignature) { return NO; } - if (!pkeyString) { return NO; } + NSData *pathData = nil, *sigData = nil; + CFDataRef hashData = NULL; + CSSM_KEY_PTR pubKey = cdsaCreateKey((CFDataRef)rawKeyData(pkeyString)); // Create the DSA key + CSSM_CSP_HANDLE cspHandle = CSSM_INVALID_HANDLE; + + if ( !pubKey ) return NO; + if ( (cspHandle = cdsaInit()) == CSSM_INVALID_HANDLE ) goto validate_end; // Init CDSA + if ( !cdsaVerifyKey(cspHandle, pubKey) ) goto validate_end; // Verify the key is valid + if ( (pathData = [NSData dataWithContentsOfFile:path]) == nil ) goto validate_end; // File data + if ( (hashData = cdsaCreateSHA1Digest(cspHandle, (CFDataRef)pathData)) == NULL ) goto validate_end; // Hash + + // Remove any line feeds from end of signature + // (Not likely needed, but the verify _can_ fail if there is, so...) + if ( [encodedSignature characterAtIndex:[encodedSignature length] - 1] == '\n' ) { + NSMutableString *sig = [[encodedSignature mutableCopy] autorelease]; + while ( [sig characterAtIndex:[sig length] - 1] == '\n' ) + [sig deleteCharactersInRange:NSMakeRange([sig length] - 1, 1)]; + encodedSignature = sig; + } + if ( (sigData = b64decode(encodedSignature)) == nil ) goto validate_end; // Decode signature + + // Verify the signature on the file + result = cdsaVerifySignature( cspHandle, pubKey, hashData, (CFDataRef)sigData ); + +validate_end: + cdsaReleaseKey( pubKey ); + cdsaRelease( cspHandle ); + if ( hashData ) CFRelease( hashData ); + + return result; +} + +@end + +#pragma mark - +#pragma mark Misc Helper Functions +#pragma mark - + +static NSData *b64decode( NSString *str ) +{ + if ( !str ) return nil; + NSMutableData *retval = nil; + NSData *input = [str dataUsingEncoding:NSUTF8StringEncoding]; + UInt8 *ibuf = (UInt8 *)[input bytes], *buf = NULL, *a = NULL; + size_t len = [input length], i = 0, j = 0, size = ((len + 3) / 4) * 3; + static UInt8 table[256] = { + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* 00-0F */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* 10-1F */ + 99,99,99,99,99,99,99,99,99,99,99,62,99,99,99,63, /* 20-2F */ + 52,53,54,55,56,57,58,59,60,61,99,99,99,99,99,99, /* 30-3F */ + 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 40-4F */ + 15,16,17,18,19,20,21,22,23,24,25,99,99,99,99,99, /* 50-5F */ + 99,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, /* 60-6F */ + 41,42,43,44,45,46,47,48,49,50,51,99,99,99,99,99, /* 70-7F */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* 80-8F */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* 90-9F */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* A0-AF */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* B0-BF */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* C0-CF */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* D0-DF */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, /* E0-EF */ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 /* F0-FF */ + }; + + retval = [NSMutableData dataWithLength:size]; + buf = [retval mutableBytes]; + + if ( (a = calloc(4, sizeof(UInt8))) == NULL ) return nil; + + do { + size_t ai = 0; + a[0] = a[1] = a[2] = a[3] = 0; + do { + UInt8 d = table[ibuf[i++]]; + if ( d != 99 ) { + a[ai] = d; + ai++; + if ( ai == 4 ) break; + } + } while ( i < len ); + if ( ai >= 2 ) buf[j] = (a[0] << 2) | (a[1] >> 4); + if ( ai >= 3 ) buf[j+1] = (a[1] << 4) | (a[2] >> 2); + if ( ai >= 4 ) buf[j+2] = (a[2] << 6) | a[3]; + j += ai-1; + } while ( i < len ); + + free( a ); + if ( j < size ) [retval setLength:j]; + + return retval; +} + +static NSData *rawKeyData( NSString *key ) +{ + if ( (key == nil) || ([key length] == 0) ) return nil; + NSMutableString *t = [[key mutableCopy] autorelease]; + + // Remove the PEM guards (if present) + [t replaceOccurrencesOfString:@"-" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [t length])]; + [t replaceOccurrencesOfString:@"BEGIN PUBLIC KEY" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [t length])]; + [t replaceOccurrencesOfString:@"END PUBLIC KEY" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [t length])]; + + // Remove any line feeds from the beginning of the key + while ( [t characterAtIndex:0] == '\n' ) { + [t deleteCharactersInRange:NSMakeRange(0, 1)]; + } + + // Remove any line feeds at the end of the key + while ( [t characterAtIndex:[t length] - 1] == '\n' ) { + [t deleteCharactersInRange:NSMakeRange([t length] - 1, 1)]; + } // Remove whitespace around each line of the key. NSMutableArray *pkeyTrimmedLines = [NSMutableArray array]; - NSEnumerator *pkeyLinesEnumerator = [[pkeyString componentsSeparatedByString:@"\n"] objectEnumerator]; + NSEnumerator *pkeyLinesEnumerator = [[t componentsSeparatedByString:@"\n"] objectEnumerator]; + NSCharacterSet *whiteSet = [NSCharacterSet whitespaceCharacterSet]; NSString *pkeyLine; while ((pkeyLine = [pkeyLinesEnumerator nextObject]) != nil) { - [pkeyTrimmedLines addObject:[pkeyLine stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]]; + [pkeyTrimmedLines addObject:[pkeyLine stringByTrimmingCharactersInSet:whiteSet]]; } - pkeyString = [pkeyTrimmedLines componentsJoinedByString:@"\n"]; // Put them back together. - - NSMutableData* pkeyData = [[[pkeyString dataUsingEncoding:NSUTF8StringEncoding] mutableCopy] autorelease]; - void *pkeyBytes = [pkeyData mutableBytes]; - EVP_PKEY* pkey = NULL; - pkey = load_dsa_key(pkeyBytes); - if (!pkey) { return NO; } - - // Now, the signature is in base64; we have to decode it into a binary stream. - NSMutableData* signatureData = [[[encodedSignature dataUsingEncoding:NSUTF8StringEncoding] mutableCopy] autorelease]; - void *signature = [signatureData mutableBytes]; - long length = b64decode(signature); // Decode the signature in-place and get the new length of the signature string. - if ((length < 0) || (length > 0x7FFFFFFF)) { return NO; } // test before cast below - - // We've got the signature, now get the file data. - NSData *pathData = [NSData dataWithContentsOfFile:path]; - if (!pathData) { return NO; } - - // Hash the file with SHA-1. - unsigned char md[SHA_DIGEST_LENGTH]; - SHA1([pathData bytes], [pathData length], md); - - // Actually verify the signature on the file. - EVP_MD_CTX ctx; - EVP_MD_CTX_init(&ctx); - if(EVP_VerifyInit(&ctx, EVP_dss1()) == 1) // We're using DSA keys. - { - EVP_VerifyUpdate(&ctx, md, SHA_DIGEST_LENGTH); + key = [pkeyTrimmedLines componentsJoinedByString:@"\n"]; // Put them back together. + + // Base64 decode to return the raw key bits (DER format rather than PEM) + return b64decode( key ); +} + +#pragma mark - +#pragma mark CDSA +#pragma mark - + +/* Helper Functions */ +static CSSM_DATA_PTR su_createData( CFDataRef bytes ); +static void su_freeData( CSSM_DATA_PTR data, Boolean freeData ); +static Boolean su_copyBytesToData( CSSM_DATA_PTR data, CSSM_SIZE size, const uint8 *bytes ); + +/* Memory functions */ +static void *su_malloc( CSSM_SIZE size, void *ref ); +static void su_free( void *ptr, void *ref ); +static void *su_realloc( void *ptr, CSSM_SIZE size, void *ref ); +static void *su_calloc( uint32 num, CSSM_SIZE size, void *ref ); + +/* Constants & Typedefs */ +static CSSM_VERSION vers = { 2, 0 }; +static const CSSM_GUID su_guid = { 'S', 'p', 'a', { 'r', 'k', 'l', 'e', 0, 0, 0, 0 } }; +static CSSM_BOOL cssmInited = CSSM_FALSE; + +static CSSM_API_MEMORY_FUNCS SU_MemFuncs = { + su_malloc, + su_free, + su_realloc, + su_calloc, + NULL +}; + +static CSSM_CSP_HANDLE cdsaInit( void ) +{ + CSSM_CSP_HANDLE cspHandle = CSSM_INVALID_HANDLE; + CSSM_RETURN crtn; + + if ( !cssmInited ) { + CSSM_PVC_MODE pvcPolicy = CSSM_PVC_NONE; + crtn = CSSM_Init( &vers, CSSM_PRIVILEGE_SCOPE_NONE, &su_guid, CSSM_KEY_HIERARCHY_NONE, &pvcPolicy, NULL ); + if ( crtn ) return CSSM_INVALID_HANDLE; + cssmInited = CSSM_TRUE; + } + + crtn = CSSM_ModuleLoad( &gGuidAppleCSP, CSSM_KEY_HIERARCHY_NONE, NULL, NULL ); + if ( crtn ) return CSSM_INVALID_HANDLE; + + crtn = CSSM_ModuleAttach( &gGuidAppleCSP, &vers, &SU_MemFuncs, 0, CSSM_SERVICE_CSP, 0, CSSM_KEY_HIERARCHY_NONE, NULL, 0, NULL, &cspHandle ); + if ( crtn ) return CSSM_INVALID_HANDLE; + + return cspHandle; +} + +static void cdsaRelease( CSSM_CSP_HANDLE cspHandle ) +{ + if ( CSSM_ModuleDetach(cspHandle) != CSSM_OK ) return; + CSSM_ModuleUnload( &gGuidAppleCSP, NULL, NULL ); +} + +static CSSM_KEY_PTR cdsaCreateKey( CFDataRef rawKey ) +{ + CSSM_KEY_PTR retval = NULL; + + if ( !rawKey || (CFDataGetLength(rawKey) == 0) ) return NULL; + + if ( (retval = su_malloc(sizeof(CSSM_KEY), NULL)) == NULL ) return NULL; + + if ( !su_copyBytesToData(&(retval->KeyData), CFDataGetLength(rawKey), CFDataGetBytePtr(rawKey)) ) { + su_free( retval, NULL ); + return NULL; + } + + CSSM_KEYHEADER_PTR hdr = &(retval->KeyHeader); + + memset( hdr, 0, sizeof(CSSM_KEYHEADER) ); + + hdr->HeaderVersion = CSSM_KEYHEADER_VERSION; + hdr->CspId = su_guid; + hdr->BlobType = CSSM_KEYBLOB_RAW; + hdr->Format = CSSM_KEYBLOB_RAW_FORMAT_X509; + hdr->AlgorithmId = CSSM_ALGID_DSA; + hdr->KeyClass = CSSM_KEYCLASS_PUBLIC_KEY; + hdr->KeyAttr = CSSM_KEYATTR_EXTRACTABLE; + hdr->KeyUsage = CSSM_KEYUSE_ANY; + + return retval; +} + +static void cdsaReleaseKey( CSSM_KEY_PTR key ) +{ + if ( key ) { + if ( key->KeyData.Data ) su_free( key->KeyData.Data, NULL ); + su_free( key, NULL ); + } +} + +BOOL cdsaVerifyKey( CSSM_CSP_HANDLE cspHandle, CSSM_KEY_PTR key ) +{ + if ( key->KeyHeader.LogicalKeySizeInBits == 0 ) { + CSSM_RETURN crtn; + CSSM_KEY_SIZE keySize; - result = EVP_VerifyFinal(&ctx, signature, (unsigned int)length, pkey); + /* This will fail if the key isn't valid */ + crtn = CSSM_QueryKeySizeInBits( cspHandle, CSSM_INVALID_HANDLE, key, &keySize ); + if ( crtn ) return NO; + key->KeyHeader.LogicalKeySizeInBits = keySize.LogicalKeySizeInBits; } - EVP_MD_CTX_cleanup(&ctx); + return YES; +} + +static BOOL cdsaVerifySignature( CSSM_CSP_HANDLE cspHandle, const CSSM_KEY_PTR key, const CFDataRef msg, const CFDataRef signature ) +{ + CSSM_CC_HANDLE ccHandle = CSSM_INVALID_HANDLE; + CSSM_DATA_PTR plain = su_createData( msg ), cipher = su_createData( signature ); + BOOL retval = NO; + + if ( !plain || !cipher || (CSSM_CSP_CreateSignatureContext(cspHandle, CSSM_ALGID_SHA1WithDSA, NULL, key, &ccHandle) != CSSM_OK) ) + goto verify_end; - EVP_PKEY_free(pkey); + retval = ( CSSM_VerifyData(ccHandle, plain, 1, CSSM_ALGID_NONE, cipher) == CSSM_OK ); - // Prevent these from being collected earlier than we want (our only reference is an inner pointer). - [pkeyData self]; - [signatureData self]; - [pathData self]; +verify_end: + su_freeData( plain, true ); + su_freeData( cipher, true ); + if ( ccHandle ) CSSM_DeleteContext( ccHandle ); - return result == 1; + return retval; } -@end +static CFDataRef cdsaCreateSHA1Digest( CSSM_CSP_HANDLE cspHandle, const CFDataRef bytes ) +{ + CSSM_CC_HANDLE ccHandle = CSSM_INVALID_HANDLE; + CSSM_DATA_PTR data = su_createData( bytes ), dgst = su_createData( NULL ); + CFDataRef retval = NULL; + + if ( !data || !dgst || (CSSM_CSP_CreateDigestContext(cspHandle, CSSM_ALGID_SHA1, &ccHandle) != CSSM_OK) ) + goto digest_end; + + if ( CSSM_DigestData(ccHandle, data, 1, dgst) == CSSM_OK ) + retval = CFDataCreate( kCFAllocatorDefault, (const UInt8 *)dgst->Data, (CFIndex)dgst->Length ); + +digest_end: + su_freeData( data, true ); + su_freeData( dgst, true ); + if ( ccHandle ) CSSM_DeleteContext( ccHandle ); + + return retval; +} + +/* Memory Functions */ +static void *su_malloc( CSSM_SIZE size, void *ref ) +{ + return malloc( size ); +} + +static void su_free( void *ptr, void *ref ) +{ + free( ptr ); +} + +static void *su_realloc( void *ptr, CSSM_SIZE size, void *ref ) +{ + return realloc( ptr, size ); +} + +static void *su_calloc( uint32 num, CSSM_SIZE size, void *ref ) +{ + return calloc( num, size ); +} + +/* Helper Functions */ +static CSSM_DATA_PTR su_createData( CFDataRef bytes ) +{ + CSSM_DATA_PTR data = su_malloc( sizeof(CSSM_DATA), NULL ); + if ( !data ) return NULL; + data->Data = NULL; + data->Length = 0; + if ( bytes ) su_copyBytesToData( data, CFDataGetLength(bytes), CFDataGetBytePtr(bytes) ); + return data; +} + +static void su_freeData( CSSM_DATA_PTR data, Boolean freeData ) +{ + if ( data ) { + if ( freeData && data->Data ) su_free( data->Data, NULL ); + su_free( data, NULL ); + } +} + +static Boolean su_copyBytesToData( CSSM_DATA_PTR data, CSSM_SIZE size, const uint8 *bytes ) +{ + Boolean retval = false; + if ( size && bytes ) { + if ( (data->Data = su_malloc(size, NULL)) ) { + memcpy( data->Data, bytes, size ); + data->Length = size; + retval = true; + } + } + return retval; +}