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