MYDecoder.m
author snej@snej.local
Sun Apr 12 22:16:14 2009 -0700 (2009-04-12)
changeset 9 aa5eb3fd6ebf
child 12 e4c971be4079
permissions -rw-r--r--
Doc touch-up
     1 //
     2 //  MYDecoder.m
     3 //  Cloudy
     4 //
     5 //  Created by Jens Alfke on 1/16/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYDecoder.h"
    10 #import "MYCrypto_Private.h"
    11 #import "Test.h"
    12 #import "MYErrorUtils.h"
    13 
    14 
    15 @interface MYSigner ()
    16 - (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy;
    17 @end
    18 
    19 
    20 @implementation MYDecoder
    21 
    22 
    23 - (id) initWithData: (NSData*)data error: (NSError**)outError
    24 {
    25     self = [self init];
    26     if( self ) {
    27         [self addData: data];
    28         [self finish];
    29         *outError = self.error;
    30         if( *outError ) {
    31             [self release];
    32             return nil;
    33         }
    34     }
    35     return self;
    36 }
    37 
    38 - (id) init
    39 {
    40     self = [super init];
    41     if (self != nil) {
    42         OSStatus err = CMSDecoderCreate(&_decoder);
    43         if( err ) {
    44             [self release];
    45             self = nil;
    46         }
    47     }
    48     return self;
    49 }
    50 
    51 - (void) dealloc
    52 {
    53     if( _decoder ) CFRelease(_decoder);
    54     if (_policy) CFRelease(_policy);
    55     [super dealloc];
    56 }
    57 
    58 
    59 - (BOOL) addData: (NSData*)data
    60 {
    61     Assert(data);
    62     return !_error && checksave( CMSDecoderUpdateMessage(_decoder, data.bytes, data.length) );
    63 }
    64 
    65 
    66 - (BOOL) finish
    67 {
    68     return !_error && checksave( CMSDecoderFinalizeMessage(_decoder) );
    69 }
    70 
    71 - (NSError*) error
    72 {
    73     if( _error )
    74         return MYError(_error, NSOSStatusErrorDomain, 
    75                        @"%@", MYErrorName(NSOSStatusErrorDomain,_error));
    76     else
    77         return nil;
    78 }
    79 
    80 - (BOOL) useKeychain: (MYKeychain*)keychain
    81 {
    82     return !_error && checksave( CMSDecoderSetSearchKeychain(_decoder, keychain.keychainRef) );
    83 }
    84 
    85 
    86 @synthesize policy=_policy;
    87 
    88 
    89 - (NSData*) _dataFromFunction: (OSStatus (*)(CMSDecoderRef,CFDataRef*))function
    90 {
    91     CFDataRef data=NULL;
    92     if( checksave( (*function)(_decoder, &data) ) )
    93        return [(NSData*)CFMakeCollectable(data) autorelease];
    94     else
    95         return nil;
    96 }
    97 
    98 
    99 - (NSData*) detachedContent
   100 {
   101     return [self _dataFromFunction: &CMSDecoderCopyDetachedContent];
   102 }
   103 
   104 - (void) setDetachedContent: (NSData*)detachedContent
   105 {
   106     if( ! _error )
   107         checksave( CMSDecoderSetDetachedContent(_decoder, (CFDataRef)detachedContent) );
   108 }
   109 
   110 - (CSSM_OID) contentType
   111 {
   112     NSData *data = [self _dataFromFunction: &CMSDecoderCopyEncapsulatedContentType];
   113     return (CSSM_OID){data.length,(uint8*)data.bytes};      // safe since data is autoreleased
   114 }
   115 
   116 - (NSData*) content
   117 {
   118     return [self _dataFromFunction: &CMSDecoderCopyContent];
   119 }
   120 
   121 - (BOOL) isSigned
   122 {
   123     size_t n;
   124     return checksave( CMSDecoderGetNumSigners(_decoder, &n) ) && n > 0;
   125 }
   126 
   127 - (BOOL) isEncrypted
   128 {
   129     Boolean isEncrypted;
   130     return check(CMSDecoderIsContentEncrypted(_decoder,&isEncrypted), @"CMSDecoderIsContentEncrypted")
   131         && isEncrypted;
   132 }
   133 
   134 - (NSArray*) signers
   135 {
   136     size_t n;
   137     if( ! checksave( CMSDecoderGetNumSigners(_decoder, &n) ) )
   138         return nil;
   139     NSMutableArray *signers = [NSMutableArray arrayWithCapacity: n];
   140     for( size_t i=0; i<n; i++ ) {
   141         MYSigner *signer = [[MYSigner alloc] initWithDecoder: _decoder
   142                                                        index: i
   143                                                       policy: _policy];
   144         [signers addObject: signer];
   145         [signer release];
   146     }
   147     return signers;
   148 }
   149 
   150 
   151 - (NSArray*) certificates
   152 {
   153     CFArrayRef certRefs = NULL;
   154     if( ! checksave( CMSDecoderCopyAllCerts(_decoder, &certRefs) ) || ! certRefs )
   155         return nil;
   156     unsigned n = CFArrayGetCount(certRefs);
   157     NSMutableArray *certs = [NSMutableArray arrayWithCapacity: n];
   158     for( unsigned i=0; i<n; i++ ) {
   159         SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(certRefs, i);
   160         [certs addObject: [MYCertificate certificateWithCertificateRef: certRef]];
   161     }
   162     CFRelease(certRefs);
   163     return certs;
   164 }
   165 
   166 
   167 - (NSString*) dump
   168 {
   169     static const char * kStatusNames[kCMSSignerInvalidIndex+1] = {
   170         "kCMSSignerUnsigned", "kCMSSignerValid", "kCMSSignerNeedsDetachedContent",	
   171         "kCMSSignerInvalidSignature","kCMSSignerInvalidCert","kCMSSignerInvalidIndex"};			
   172         
   173     CSSM_OID contentType = self.contentType;
   174     NSMutableString *s = [NSMutableString stringWithFormat:  @"%@<%p>:\n"
   175                                                               "\tcontentType = %@ (\"%@\")\n"
   176                                                               "\tcontent = %u bytes\n"
   177                                                               "\tsigned=%i, encrypted=%i\n"
   178                                                               "\tpolicy=%@\n"
   179                                                               "\t%u certificates\n"
   180                                                               "\tsigners =\n",
   181                               self.class, self,
   182                               OIDAsString(contentType), @"??"/*nameOfOID(&contentType)*/,
   183                               self.content.length,
   184                               self.isSigned,self.isEncrypted,
   185                               MYPolicyGetName(_policy),
   186                               self.certificates.count];
   187     for( MYSigner *signer in self.signers ) {
   188         CMSSignerStatus status = signer.status;
   189         const char *statusName = (status<=kCMSSignerInvalidIndex) ?kStatusNames[status] :"??";
   190         [s appendFormat:@"\t\t- status = %s\n"
   191                          "\t\t  verifyResult = %@\n"
   192                          "\t\t  trust = %@\n"
   193                          "\t\t  cert = %@\n",
   194                  statusName,
   195                  (signer.verifyResult ?MYErrorName(NSOSStatusErrorDomain,signer.verifyResult)
   196                                       :@"OK"),
   197                  MYTrustDescribe(signer.trust),
   198                  signer.certificate];
   199     }
   200     return s;
   201 }
   202 
   203 
   204 @end
   205 
   206 
   207 
   208 #pragma mark -
   209 @implementation MYSigner : NSObject
   210 
   211 #define kUncheckedStatus ((CMSSignerStatus)-1)
   212 
   213 - (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy
   214 {
   215     self = [super init];
   216     if( self ) {
   217         CFRetain(decoder);
   218         _decoder = decoder;
   219         _index = index;
   220         if(policy) _policy = CFRetain(policy);
   221         _status = kUncheckedStatus;
   222     }
   223     return self;
   224 }
   225 
   226 - (void) dealloc
   227 {
   228     if(_decoder) CFRelease(_decoder);
   229     if(_policy) CFRelease(_policy);
   230     if(_trust) CFRelease(_trust);
   231     [super dealloc];
   232 }
   233 
   234 - (void) _getInfo {
   235     if (_status == kUncheckedStatus) {
   236         if( !check(CMSDecoderCopySignerStatus(_decoder, _index, _policy, (_policy!=nil),
   237                                               &_status, &_trust, &_verifyResult), 
   238                    @"CMSDecoderCopySignerStatus"))
   239             _status = kMYSignerStatusCheckFailed;
   240     }
   241 }
   242 
   243 - (CMSSignerStatus) status
   244 {
   245     [self _getInfo];
   246     return _status;
   247 }
   248 
   249 - (OSStatus) verifyResult
   250 {
   251     [self _getInfo];
   252     return _verifyResult;
   253 }
   254 
   255 - (SecTrustRef) trust
   256 {
   257     [self _getInfo];
   258     return _trust;
   259 }
   260 
   261 
   262 - (NSString*) emailAddress
   263 {
   264     // Don't let caller see the addr if they haven't checked validity & the signature's invalid:
   265     if (_status==kUncheckedStatus && self.status != kCMSSignerValid)
   266         return nil;
   267     
   268     CFStringRef email=NULL;
   269     if( CMSDecoderCopySignerEmailAddress(_decoder, _index, &email) == noErr )
   270         return [(NSString*)CFMakeCollectable(email) autorelease];
   271     return nil;
   272 }
   273 
   274 - (MYCertificate *) certificate
   275 {
   276     // Don't let caller see the cert if they haven't checked validity & the signature's invalid:
   277     if (_status==kUncheckedStatus && self.status != kCMSSignerValid)
   278         return nil;
   279     
   280     SecCertificateRef certRef=NULL;
   281     OSStatus err = CMSDecoderCopySignerCert(_decoder, _index, &certRef);
   282     if( err == noErr )
   283         return [MYCertificate certificateWithCertificateRef: certRef];
   284     else {
   285         Warn(@"CMSDecoderCopySignerCert returned err %i",err);
   286         return nil;
   287     }
   288 }
   289 
   290 
   291 - (NSString*) description
   292 {
   293     NSMutableString *desc = [NSMutableString stringWithFormat: @"%@[st=%i", self.class,(int)self.status];
   294     int verify = self.verifyResult;
   295     if( verify )
   296         [desc appendFormat: @"; verify error %i",verify];
   297     else {
   298         MYCertificate *cert = self.certificate;
   299         if( cert )
   300             [desc appendFormat: @"; %@",cert.commonName];
   301     }
   302     [desc appendString: @"]"];
   303     return desc;
   304 }
   305 
   306 
   307 @end
   308 
   309 
   310 
   311 // Taken from Keychain.framework
   312 NSString* OIDAsString(const CSSM_OID oid) {
   313     if ((NULL == oid.Data) || (0 >= oid.Length)) {
   314         return nil;
   315     } else {
   316         NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)];
   317         unsigned int i;
   318         
   319         for (i = 0; i < oid.Length; ++i) {
   320             [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]];
   321         }
   322         
   323         return result;
   324     }
   325 }
   326 
   327 
   328 
   329 #pragma mark -
   330 #pragma mark TEST CASE:
   331 
   332 
   333 #if DEBUG
   334 
   335 #import "MYEncoder.h"
   336 #import "MYIdentity.h"
   337 
   338 static void TestRoundTrip( NSString *title, NSData *source, MYIdentity *signer, MYCertificate *recipient )
   339 {
   340     Log(@"Testing MYEncoder/Decoder %@...",title);
   341     NSError *error;
   342     NSData *encoded = [MYEncoder encodeData: source signer: signer recipient: recipient error: &error];
   343     CAssertEq(error,nil);
   344     CAssert([encoded length]);
   345     Log(@"MYEncoder encoded %u bytes into %u bytes", source.length,encoded.length);
   346     Log(@"Decoding...");
   347     MYDecoder *d = [[MYDecoder alloc] init];
   348     d.policy = [MYCertificate X509Policy];
   349     [d addData: encoded];
   350     [d finish];
   351 
   352     CAssertEq(d.error,nil);
   353     Log(@"%@", d.dump);
   354     CAssert(d.content);
   355     CAssert([d.content isEqual: source]);
   356     CAssertEq(d.detachedContent,nil);
   357     CAssertEq(d.isSigned,(signer!=nil));
   358     CAssertEq(d.isEncrypted,(recipient!=nil));
   359 
   360     if( signer ) {
   361         CAssert(d.certificates.count >= 1);     // may include extra parent certs
   362         CAssertEq(d.signers.count,1U);
   363         MYSigner *outSigner = [d.signers objectAtIndex: 0];
   364         CAssertEq(outSigner.status,(CMSSignerStatus)kCMSSignerValid);
   365         CAssertEq(outSigner.verifyResult,noErr);
   366         CAssert([outSigner.certificate isEqualToCertificate: signer]);
   367     } else {
   368         CAssertEq(d.certificates.count, 0U);
   369         CAssertEq(d.signers.count,0U);
   370     }
   371 }
   372 
   373 
   374 TestCase(MYDecoder) {
   375     RequireTestCase(MYEncoder);
   376     
   377     MYIdentity *me = [MYIdentity preferredIdentityForName: @"MYCryptoTest"];
   378     CAssert(me,@"No default identity has been set up in the Keychain");
   379     Log(@"Using %@", me);
   380     
   381     NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
   382     CAssert(source);
   383     
   384     TestRoundTrip(@"signing",            source, me,  nil);
   385     TestRoundTrip(@"encryption",         source, nil, me);
   386     TestRoundTrip(@"signing+encryption", source, me,  me);
   387 }
   388 
   389 #endif DEBUG