358 lines
11 KiB
Objective-C
358 lines
11 KiB
Objective-C
//
|
|
// SUDSAVerifier.m
|
|
// Sparkle
|
|
//
|
|
// Created by Andy Matuschak on 3/16/06.
|
|
// Copyright 2006 Andy Matuschak. All rights reserved.
|
|
//
|
|
|
|
#import "SUDSAVerifier.h"
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#import <Security/cssm.h>
|
|
|
|
/* 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;
|
|
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 = [[t componentsSeparatedByString:@"\n"] objectEnumerator];
|
|
NSCharacterSet *whiteSet = [NSCharacterSet whitespaceCharacterSet];
|
|
NSString *pkeyLine;
|
|
while ((pkeyLine = [pkeyLinesEnumerator nextObject]) != nil)
|
|
{
|
|
[pkeyTrimmedLines addObject:[pkeyLine stringByTrimmingCharactersInSet:whiteSet]];
|
|
}
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
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;
|
|
|
|
retval = ( CSSM_VerifyData(ccHandle, plain, 1, CSSM_ALGID_NONE, cipher) == CSSM_OK );
|
|
|
|
verify_end:
|
|
su_freeData( plain, true );
|
|
su_freeData( cipher, true );
|
|
if ( ccHandle ) CSSM_DeleteContext( ccHandle );
|
|
|
|
return retval;
|
|
}
|
|
|
|
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;
|
|
}
|