diff -r 000000000000 -r 4c0eafa7b233 MYDecoder.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYDecoder.m Sun Apr 12 22:02:20 2009 -0700 @@ -0,0 +1,389 @@ +// +// MYDecoder.m +// Cloudy +// +// Created by Jens Alfke on 1/16/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "MYDecoder.h" +#import "MYCrypto_Private.h" +#import "Test.h" +#import "MYErrorUtils.h" + + +@interface MYSigner () +- (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy; +@end + + +@implementation MYDecoder + + +- (id) initWithData: (NSData*)data error: (NSError**)outError +{ + self = [self init]; + if( self ) { + [self addData: data]; + [self finish]; + *outError = self.error; + if( *outError ) { + [self release]; + return nil; + } + } + return self; +} + +- (id) init +{ + self = [super init]; + if (self != nil) { + OSStatus err = CMSDecoderCreate(&_decoder); + if( err ) { + [self release]; + self = nil; + } + } + return self; +} + +- (void) dealloc +{ + if( _decoder ) CFRelease(_decoder); + if (_policy) CFRelease(_policy); + [super dealloc]; +} + + +- (BOOL) addData: (NSData*)data +{ + Assert(data); + return !_error && checksave( CMSDecoderUpdateMessage(_decoder, data.bytes, data.length) ); +} + + +- (BOOL) finish +{ + return !_error && checksave( CMSDecoderFinalizeMessage(_decoder) ); +} + +- (NSError*) error +{ + if( _error ) + return MYError(_error, NSOSStatusErrorDomain, + @"%@", MYErrorName(NSOSStatusErrorDomain,_error)); + else + return nil; +} + +- (BOOL) useKeychain: (MYKeychain*)keychain +{ + return !_error && checksave( CMSDecoderSetSearchKeychain(_decoder, keychain.keychainRef) ); +} + + +@synthesize policy=_policy; + + +- (NSData*) _dataFromFunction: (OSStatus (*)(CMSDecoderRef,CFDataRef*))function +{ + CFDataRef data=NULL; + if( checksave( (*function)(_decoder, &data) ) ) + return [(NSData*)CFMakeCollectable(data) autorelease]; + else + return nil; +} + + +- (NSData*) detachedContent +{ + return [self _dataFromFunction: &CMSDecoderCopyDetachedContent]; +} + +- (void) setDetachedContent: (NSData*)detachedContent +{ + if( ! _error ) + checksave( CMSDecoderSetDetachedContent(_decoder, (CFDataRef)detachedContent) ); +} + +- (CSSM_OID) contentType +{ + NSData *data = [self _dataFromFunction: &CMSDecoderCopyEncapsulatedContentType]; + return (CSSM_OID){data.length,(uint8*)data.bytes}; // safe since data is autoreleased +} + +- (NSData*) content +{ + return [self _dataFromFunction: &CMSDecoderCopyContent]; +} + +- (BOOL) isSigned +{ + size_t n; + return checksave( CMSDecoderGetNumSigners(_decoder, &n) ) && n > 0; +} + +- (BOOL) isEncrypted +{ + Boolean isEncrypted; + return check(CMSDecoderIsContentEncrypted(_decoder,&isEncrypted), @"CMSDecoderIsContentEncrypted") + && isEncrypted; +} + +- (NSArray*) signers +{ + size_t n; + if( ! checksave( CMSDecoderGetNumSigners(_decoder, &n) ) ) + return nil; + NSMutableArray *signers = [NSMutableArray arrayWithCapacity: n]; + for( size_t i=0; i:\n" + "\tcontentType = %@ (\"%@\")\n" + "\tcontent = %u bytes\n" + "\tsigned=%i, encrypted=%i\n" + "\tpolicy=%@\n" + "\t%u certificates\n" + "\tsigners =\n", + self.class, self, + OIDAsString(contentType), @"??"/*nameOfOID(&contentType)*/, + self.content.length, + self.isSigned,self.isEncrypted, + MYPolicyGetName(_policy), + self.certificates.count]; + for( MYSigner *signer in self.signers ) { + CMSSignerStatus status = signer.status; + const char *statusName = (status<=kCMSSignerInvalidIndex) ?kStatusNames[status] :"??"; + [s appendFormat:@"\t\t- status = %s\n" + "\t\t verifyResult = %@\n" + "\t\t trust = %@\n" + "\t\t cert = %@\n", + statusName, + (signer.verifyResult ?MYErrorName(NSOSStatusErrorDomain,signer.verifyResult) + :@"OK"), + MYTrustDescribe(signer.trust), + signer.certificate]; + } + return s; +} + + +@end + + + +#pragma mark - +@implementation MYSigner : NSObject + +#define kUncheckedStatus ((CMSSignerStatus)-1) + +- (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy +{ + self = [super init]; + if( self ) { + CFRetain(decoder); + _decoder = decoder; + _index = index; + if(policy) _policy = CFRetain(policy); + _status = kUncheckedStatus; + } + return self; +} + +- (void) dealloc +{ + if(_decoder) CFRelease(_decoder); + if(_policy) CFRelease(_policy); + if(_trust) CFRelease(_trust); + [super dealloc]; +} + +- (void) _getInfo { + if (_status == kUncheckedStatus) { + if( !check(CMSDecoderCopySignerStatus(_decoder, _index, _policy, (_policy!=nil), + &_status, &_trust, &_verifyResult), + @"CMSDecoderCopySignerStatus")) + _status = kMYSignerStatusCheckFailed; + } +} + +- (CMSSignerStatus) status +{ + [self _getInfo]; + return _status; +} + +- (OSStatus) verifyResult +{ + [self _getInfo]; + return _verifyResult; +} + +- (SecTrustRef) trust +{ + [self _getInfo]; + return _trust; +} + + +- (NSString*) emailAddress +{ + // Don't let caller see the addr if they haven't checked validity & the signature's invalid: + if (_status==kUncheckedStatus && self.status != kCMSSignerValid) + return nil; + + CFStringRef email=NULL; + if( CMSDecoderCopySignerEmailAddress(_decoder, _index, &email) == noErr ) + return [(NSString*)CFMakeCollectable(email) autorelease]; + return nil; +} + +- (MYCertificate *) certificate +{ + // Don't let caller see the cert if they haven't checked validity & the signature's invalid: + if (_status==kUncheckedStatus && self.status != kCMSSignerValid) + return nil; + + SecCertificateRef certRef=NULL; + OSStatus err = CMSDecoderCopySignerCert(_decoder, _index, &certRef); + if( err == noErr ) + return [MYCertificate certificateWithCertificateRef: certRef]; + else { + Warn(@"CMSDecoderCopySignerCert returned err %i",err); + return nil; + } +} + + +- (NSString*) description +{ + NSMutableString *desc = [NSMutableString stringWithFormat: @"%@[st=%i", self.class,(int)self.status]; + int verify = self.verifyResult; + if( verify ) + [desc appendFormat: @"; verify error %i",verify]; + else { + MYCertificate *cert = self.certificate; + if( cert ) + [desc appendFormat: @"; %@",cert.commonName]; + } + [desc appendString: @"]"]; + return desc; +} + + +@end + + + +// Taken from Keychain.framework +NSString* OIDAsString(const CSSM_OID oid) { + if ((NULL == oid.Data) || (0 >= oid.Length)) { + return nil; + } else { + NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)]; + unsigned int i; + + for (i = 0; i < oid.Length; ++i) { + [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]]; + } + + return result; + } +} + + + +#pragma mark - +#pragma mark TEST CASE: + + +#if DEBUG + +#import "MYEncoder.h" +#import "MYIdentity.h" + +static void TestRoundTrip( NSString *title, NSData *source, MYIdentity *signer, MYCertificate *recipient ) +{ + Log(@"Testing MYEncoder/Decoder %@...",title); + NSError *error; + NSData *encoded = [MYEncoder encodeData: source signer: signer recipient: recipient error: &error]; + CAssertEq(error,nil); + CAssert([encoded length]); + Log(@"MYEncoder encoded %u bytes into %u bytes", source.length,encoded.length); + Log(@"Decoding..."); + MYDecoder *d = [[MYDecoder alloc] init]; + d.policy = [MYCertificate X509Policy]; + [d addData: encoded]; + [d finish]; + + CAssertEq(d.error,nil); + Log(@"%@", d.dump); + CAssert(d.content); + CAssert([d.content isEqual: source]); + CAssertEq(d.detachedContent,nil); + CAssertEq(d.isSigned,(signer!=nil)); + CAssertEq(d.isEncrypted,(recipient!=nil)); + + if( signer ) { + CAssert(d.certificates.count >= 1); // may include extra parent certs + CAssertEq(d.signers.count,1U); + MYSigner *outSigner = [d.signers objectAtIndex: 0]; + CAssertEq(outSigner.status,(CMSSignerStatus)kCMSSignerValid); + CAssertEq(outSigner.verifyResult,noErr); + CAssert([outSigner.certificate isEqualToCertificate: signer]); + } else { + CAssertEq(d.certificates.count, 0U); + CAssertEq(d.signers.count,0U); + } +} + + +TestCase(MYDecoder) { + RequireTestCase(MYEncoder); + + MYIdentity *me = [MYIdentity preferredIdentityForName: @"MYCryptoTest"]; + CAssert(me,@"No default identity has been set up in the Keychain"); + Log(@"Using %@", me); + + NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"]; + CAssert(source); + + TestRoundTrip(@"signing", source, me, nil); + TestRoundTrip(@"encryption", source, nil, me); + TestRoundTrip(@"signing+encryption", source, me, me); +} + +#endif DEBUG