1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/MYDecoder.m Tue Apr 14 18:34:52 2009 -0700
1.3 @@ -0,0 +1,389 @@
1.4 +//
1.5 +// MYDecoder.m
1.6 +// Cloudy
1.7 +//
1.8 +// Created by Jens Alfke on 1/16/08.
1.9 +// Copyright 2008 Jens Alfke. All rights reserved.
1.10 +//
1.11 +
1.12 +#import "MYDecoder.h"
1.13 +#import "MYCrypto_Private.h"
1.14 +#import "Test.h"
1.15 +#import "MYErrorUtils.h"
1.16 +
1.17 +
1.18 +@interface MYSigner ()
1.19 +- (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy;
1.20 +@end
1.21 +
1.22 +
1.23 +@implementation MYDecoder
1.24 +
1.25 +
1.26 +- (id) initWithData: (NSData*)data error: (NSError**)outError
1.27 +{
1.28 + self = [self init];
1.29 + if( self ) {
1.30 + [self addData: data];
1.31 + [self finish];
1.32 + *outError = self.error;
1.33 + if( *outError ) {
1.34 + [self release];
1.35 + return nil;
1.36 + }
1.37 + }
1.38 + return self;
1.39 +}
1.40 +
1.41 +- (id) init
1.42 +{
1.43 + self = [super init];
1.44 + if (self != nil) {
1.45 + OSStatus err = CMSDecoderCreate(&_decoder);
1.46 + if( err ) {
1.47 + [self release];
1.48 + self = nil;
1.49 + }
1.50 + }
1.51 + return self;
1.52 +}
1.53 +
1.54 +- (void) dealloc
1.55 +{
1.56 + if( _decoder ) CFRelease(_decoder);
1.57 + if (_policy) CFRelease(_policy);
1.58 + [super dealloc];
1.59 +}
1.60 +
1.61 +
1.62 +- (BOOL) addData: (NSData*)data
1.63 +{
1.64 + Assert(data);
1.65 + return !_error && checksave( CMSDecoderUpdateMessage(_decoder, data.bytes, data.length) );
1.66 +}
1.67 +
1.68 +
1.69 +- (BOOL) finish
1.70 +{
1.71 + return !_error && checksave( CMSDecoderFinalizeMessage(_decoder) );
1.72 +}
1.73 +
1.74 +- (NSError*) error
1.75 +{
1.76 + if( _error )
1.77 + return MYError(_error, NSOSStatusErrorDomain,
1.78 + @"%@", MYErrorName(NSOSStatusErrorDomain,_error));
1.79 + else
1.80 + return nil;
1.81 +}
1.82 +
1.83 +- (BOOL) useKeychain: (MYKeychain*)keychain
1.84 +{
1.85 + return !_error && checksave( CMSDecoderSetSearchKeychain(_decoder, keychain.keychainRef) );
1.86 +}
1.87 +
1.88 +
1.89 +@synthesize policy=_policy;
1.90 +
1.91 +
1.92 +- (NSData*) _dataFromFunction: (OSStatus (*)(CMSDecoderRef,CFDataRef*))function
1.93 +{
1.94 + CFDataRef data=NULL;
1.95 + if( checksave( (*function)(_decoder, &data) ) )
1.96 + return [(NSData*)CFMakeCollectable(data) autorelease];
1.97 + else
1.98 + return nil;
1.99 +}
1.100 +
1.101 +
1.102 +- (NSData*) detachedContent
1.103 +{
1.104 + return [self _dataFromFunction: &CMSDecoderCopyDetachedContent];
1.105 +}
1.106 +
1.107 +- (void) setDetachedContent: (NSData*)detachedContent
1.108 +{
1.109 + if( ! _error )
1.110 + checksave( CMSDecoderSetDetachedContent(_decoder, (CFDataRef)detachedContent) );
1.111 +}
1.112 +
1.113 +- (CSSM_OID) contentType
1.114 +{
1.115 + NSData *data = [self _dataFromFunction: &CMSDecoderCopyEncapsulatedContentType];
1.116 + return (CSSM_OID){data.length,(uint8*)data.bytes}; // safe since data is autoreleased
1.117 +}
1.118 +
1.119 +- (NSData*) content
1.120 +{
1.121 + return [self _dataFromFunction: &CMSDecoderCopyContent];
1.122 +}
1.123 +
1.124 +- (BOOL) isSigned
1.125 +{
1.126 + size_t n;
1.127 + return checksave( CMSDecoderGetNumSigners(_decoder, &n) ) && n > 0;
1.128 +}
1.129 +
1.130 +- (BOOL) isEncrypted
1.131 +{
1.132 + Boolean isEncrypted;
1.133 + return check(CMSDecoderIsContentEncrypted(_decoder,&isEncrypted), @"CMSDecoderIsContentEncrypted")
1.134 + && isEncrypted;
1.135 +}
1.136 +
1.137 +- (NSArray*) signers
1.138 +{
1.139 + size_t n;
1.140 + if( ! checksave( CMSDecoderGetNumSigners(_decoder, &n) ) )
1.141 + return nil;
1.142 + NSMutableArray *signers = [NSMutableArray arrayWithCapacity: n];
1.143 + for( size_t i=0; i<n; i++ ) {
1.144 + MYSigner *signer = [[MYSigner alloc] initWithDecoder: _decoder
1.145 + index: i
1.146 + policy: _policy];
1.147 + [signers addObject: signer];
1.148 + [signer release];
1.149 + }
1.150 + return signers;
1.151 +}
1.152 +
1.153 +
1.154 +- (NSArray*) certificates
1.155 +{
1.156 + CFArrayRef certRefs = NULL;
1.157 + if( ! checksave( CMSDecoderCopyAllCerts(_decoder, &certRefs) ) || ! certRefs )
1.158 + return nil;
1.159 + unsigned n = CFArrayGetCount(certRefs);
1.160 + NSMutableArray *certs = [NSMutableArray arrayWithCapacity: n];
1.161 + for( unsigned i=0; i<n; i++ ) {
1.162 + SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(certRefs, i);
1.163 + [certs addObject: [MYCertificate certificateWithCertificateRef: certRef]];
1.164 + }
1.165 + CFRelease(certRefs);
1.166 + return certs;
1.167 +}
1.168 +
1.169 +
1.170 +- (NSString*) dump
1.171 +{
1.172 + static const char * kStatusNames[kCMSSignerInvalidIndex+1] = {
1.173 + "kCMSSignerUnsigned", "kCMSSignerValid", "kCMSSignerNeedsDetachedContent",
1.174 + "kCMSSignerInvalidSignature","kCMSSignerInvalidCert","kCMSSignerInvalidIndex"};
1.175 +
1.176 + CSSM_OID contentType = self.contentType;
1.177 + NSMutableString *s = [NSMutableString stringWithFormat: @"%@<%p>:\n"
1.178 + "\tcontentType = %@ (\"%@\")\n"
1.179 + "\tcontent = %u bytes\n"
1.180 + "\tsigned=%i, encrypted=%i\n"
1.181 + "\tpolicy=%@\n"
1.182 + "\t%u certificates\n"
1.183 + "\tsigners =\n",
1.184 + self.class, self,
1.185 + OIDAsString(contentType), @"??"/*nameOfOID(&contentType)*/,
1.186 + self.content.length,
1.187 + self.isSigned,self.isEncrypted,
1.188 + MYPolicyGetName(_policy),
1.189 + self.certificates.count];
1.190 + for( MYSigner *signer in self.signers ) {
1.191 + CMSSignerStatus status = signer.status;
1.192 + const char *statusName = (status<=kCMSSignerInvalidIndex) ?kStatusNames[status] :"??";
1.193 + [s appendFormat:@"\t\t- status = %s\n"
1.194 + "\t\t verifyResult = %@\n"
1.195 + "\t\t trust = %@\n"
1.196 + "\t\t cert = %@\n",
1.197 + statusName,
1.198 + (signer.verifyResult ?MYErrorName(NSOSStatusErrorDomain,signer.verifyResult)
1.199 + :@"OK"),
1.200 + MYTrustDescribe(signer.trust),
1.201 + signer.certificate];
1.202 + }
1.203 + return s;
1.204 +}
1.205 +
1.206 +
1.207 +@end
1.208 +
1.209 +
1.210 +
1.211 +#pragma mark -
1.212 +@implementation MYSigner : NSObject
1.213 +
1.214 +#define kUncheckedStatus ((CMSSignerStatus)-1)
1.215 +
1.216 +- (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy
1.217 +{
1.218 + self = [super init];
1.219 + if( self ) {
1.220 + CFRetain(decoder);
1.221 + _decoder = decoder;
1.222 + _index = index;
1.223 + if(policy) _policy = CFRetain(policy);
1.224 + _status = kUncheckedStatus;
1.225 + }
1.226 + return self;
1.227 +}
1.228 +
1.229 +- (void) dealloc
1.230 +{
1.231 + if(_decoder) CFRelease(_decoder);
1.232 + if(_policy) CFRelease(_policy);
1.233 + if(_trust) CFRelease(_trust);
1.234 + [super dealloc];
1.235 +}
1.236 +
1.237 +- (void) _getInfo {
1.238 + if (_status == kUncheckedStatus) {
1.239 + if( !check(CMSDecoderCopySignerStatus(_decoder, _index, _policy, (_policy!=nil),
1.240 + &_status, &_trust, &_verifyResult),
1.241 + @"CMSDecoderCopySignerStatus"))
1.242 + _status = kMYSignerStatusCheckFailed;
1.243 + }
1.244 +}
1.245 +
1.246 +- (CMSSignerStatus) status
1.247 +{
1.248 + [self _getInfo];
1.249 + return _status;
1.250 +}
1.251 +
1.252 +- (OSStatus) verifyResult
1.253 +{
1.254 + [self _getInfo];
1.255 + return _verifyResult;
1.256 +}
1.257 +
1.258 +- (SecTrustRef) trust
1.259 +{
1.260 + [self _getInfo];
1.261 + return _trust;
1.262 +}
1.263 +
1.264 +
1.265 +- (NSString*) emailAddress
1.266 +{
1.267 + // Don't let caller see the addr if they haven't checked validity & the signature's invalid:
1.268 + if (_status==kUncheckedStatus && self.status != kCMSSignerValid)
1.269 + return nil;
1.270 +
1.271 + CFStringRef email=NULL;
1.272 + if( CMSDecoderCopySignerEmailAddress(_decoder, _index, &email) == noErr )
1.273 + return [(NSString*)CFMakeCollectable(email) autorelease];
1.274 + return nil;
1.275 +}
1.276 +
1.277 +- (MYCertificate *) certificate
1.278 +{
1.279 + // Don't let caller see the cert if they haven't checked validity & the signature's invalid:
1.280 + if (_status==kUncheckedStatus && self.status != kCMSSignerValid)
1.281 + return nil;
1.282 +
1.283 + SecCertificateRef certRef=NULL;
1.284 + OSStatus err = CMSDecoderCopySignerCert(_decoder, _index, &certRef);
1.285 + if( err == noErr )
1.286 + return [MYCertificate certificateWithCertificateRef: certRef];
1.287 + else {
1.288 + Warn(@"CMSDecoderCopySignerCert returned err %i",err);
1.289 + return nil;
1.290 + }
1.291 +}
1.292 +
1.293 +
1.294 +- (NSString*) description
1.295 +{
1.296 + NSMutableString *desc = [NSMutableString stringWithFormat: @"%@[st=%i", self.class,(int)self.status];
1.297 + int verify = self.verifyResult;
1.298 + if( verify )
1.299 + [desc appendFormat: @"; verify error %i",verify];
1.300 + else {
1.301 + MYCertificate *cert = self.certificate;
1.302 + if( cert )
1.303 + [desc appendFormat: @"; %@",cert.commonName];
1.304 + }
1.305 + [desc appendString: @"]"];
1.306 + return desc;
1.307 +}
1.308 +
1.309 +
1.310 +@end
1.311 +
1.312 +
1.313 +
1.314 +// Taken from Keychain.framework
1.315 +NSString* OIDAsString(const CSSM_OID oid) {
1.316 + if ((NULL == oid.Data) || (0 >= oid.Length)) {
1.317 + return nil;
1.318 + } else {
1.319 + NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)];
1.320 + unsigned int i;
1.321 +
1.322 + for (i = 0; i < oid.Length; ++i) {
1.323 + [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]];
1.324 + }
1.325 +
1.326 + return result;
1.327 + }
1.328 +}
1.329 +
1.330 +
1.331 +
1.332 +#pragma mark -
1.333 +#pragma mark TEST CASE:
1.334 +
1.335 +
1.336 +#if DEBUG
1.337 +
1.338 +#import "MYEncoder.h"
1.339 +#import "MYIdentity.h"
1.340 +
1.341 +static void TestRoundTrip( NSString *title, NSData *source, MYIdentity *signer, MYCertificate *recipient )
1.342 +{
1.343 + Log(@"Testing MYEncoder/Decoder %@...",title);
1.344 + NSError *error;
1.345 + NSData *encoded = [MYEncoder encodeData: source signer: signer recipient: recipient error: &error];
1.346 + CAssertEq(error,nil);
1.347 + CAssert([encoded length]);
1.348 + Log(@"MYEncoder encoded %u bytes into %u bytes", source.length,encoded.length);
1.349 + Log(@"Decoding...");
1.350 + MYDecoder *d = [[MYDecoder alloc] init];
1.351 + d.policy = [MYCertificate X509Policy];
1.352 + [d addData: encoded];
1.353 + [d finish];
1.354 +
1.355 + CAssertEq(d.error,nil);
1.356 + Log(@"%@", d.dump);
1.357 + CAssert(d.content);
1.358 + CAssert([d.content isEqual: source]);
1.359 + CAssertEq(d.detachedContent,nil);
1.360 + CAssertEq(d.isSigned,(signer!=nil));
1.361 + CAssertEq(d.isEncrypted,(recipient!=nil));
1.362 +
1.363 + if( signer ) {
1.364 + CAssert(d.certificates.count >= 1); // may include extra parent certs
1.365 + CAssertEq(d.signers.count,1U);
1.366 + MYSigner *outSigner = [d.signers objectAtIndex: 0];
1.367 + CAssertEq(outSigner.status,(CMSSignerStatus)kCMSSignerValid);
1.368 + CAssertEq(outSigner.verifyResult,noErr);
1.369 + CAssert([outSigner.certificate isEqualToCertificate: signer]);
1.370 + } else {
1.371 + CAssertEq(d.certificates.count, 0U);
1.372 + CAssertEq(d.signers.count,0U);
1.373 + }
1.374 +}
1.375 +
1.376 +
1.377 +TestCase(MYDecoder) {
1.378 + RequireTestCase(MYEncoder);
1.379 +
1.380 + MYIdentity *me = [MYIdentity preferredIdentityForName: @"MYCryptoTest"];
1.381 + CAssert(me,@"No default identity has been set up in the Keychain");
1.382 + Log(@"Using %@", me);
1.383 +
1.384 + NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
1.385 + CAssert(source);
1.386 +
1.387 + TestRoundTrip(@"signing", source, me, nil);
1.388 + TestRoundTrip(@"encryption", source, nil, me);
1.389 + TestRoundTrip(@"signing+encryption", source, me, me);
1.390 +}
1.391 +
1.392 +#endif DEBUG