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