MYDecoder.m
author Jens Alfke <jens@mooseyard.com>
Wed Jul 01 14:19:13 2009 -0700 (2009-07-01)
changeset 26 d9c2a06d4e4e
parent 12 e4c971be4079
permissions -rw-r--r--
Whew, lots and lots of changes accumulated over the past few weeks. Mostly fixes for bugs I discovered while retrofitting Cloudy to use MYCrypto.
     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 
   312 #pragma mark -
   313 #pragma mark TEST CASE:
   314 
   315 
   316 #if DEBUG
   317 
   318 #import "MYEncoder.h"
   319 #import "MYIdentity.h"
   320 
   321 static void TestRoundTrip( NSString *title, NSData *source, MYIdentity *signer, MYCertificate *recipient )
   322 {
   323     Log(@"Testing MYEncoder/Decoder %@...",title);
   324     NSError *error;
   325     NSData *encoded = [MYEncoder encodeData: source signer: signer recipient: recipient error: &error];
   326     CAssertEq(error,nil);
   327     CAssert([encoded length]);
   328     Log(@"MYEncoder encoded %u bytes into %u bytes", source.length,encoded.length);
   329     Log(@"Decoding...");
   330     MYDecoder *d = [[MYDecoder alloc] init];
   331     d.policy = [MYCertificate X509Policy];
   332     [d addData: encoded];
   333     [d finish];
   334 
   335     CAssertEq(d.error,nil);
   336     Log(@"%@", d.dump);
   337     CAssert(d.content);
   338     CAssert([d.content isEqual: source]);
   339     CAssertEq(d.detachedContent,nil);
   340     CAssertEq(d.isSigned,(signer!=nil));
   341     CAssertEq(d.isEncrypted,(recipient!=nil));
   342 
   343     if( signer ) {
   344         CAssert(d.certificates.count >= 1);     // may include extra parent certs
   345         CAssertEq(d.signers.count,1U);
   346         MYSigner *outSigner = [d.signers objectAtIndex: 0];
   347         CAssertEq(outSigner.status,(CMSSignerStatus)kCMSSignerValid);
   348         CAssertEq(outSigner.verifyResult,noErr);
   349         CAssert([outSigner.certificate isEqualToCertificate: signer]);
   350     } else {
   351         CAssertEq(d.certificates.count, 0U);
   352         CAssertEq(d.signers.count,0U);
   353     }
   354 }
   355 
   356 
   357 TestCase(MYDecoder) {
   358     RequireTestCase(MYEncoder);
   359     
   360     MYIdentity *me = [MYIdentity preferredIdentityForName: @"MYCryptoTest"];
   361     CAssert(me,@"No default identity has been set up in the Keychain");
   362     Log(@"Using %@", me);
   363     
   364     NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
   365     CAssert(source);
   366     
   367     TestRoundTrip(@"signing",            source, me,  nil);
   368     TestRoundTrip(@"encryption",         source, nil, me);
   369     TestRoundTrip(@"signing+encryption", source, me,  me);
   370 }
   371 
   372 #endif DEBUG
   373 
   374 
   375 
   376 /*
   377  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   378  
   379  Redistribution and use in source and binary forms, with or without modification, are permitted
   380  provided that the following conditions are met:
   381  
   382  * Redistributions of source code must retain the above copyright notice, this list of conditions
   383  and the following disclaimer.
   384  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   385  and the following disclaimer in the documentation and/or other materials provided with the
   386  distribution.
   387  
   388  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   389  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   390  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   391  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   392  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   393   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   394  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   395  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   396  */