5 // Created by Jens Alfke on 1/16/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
10 #import "MYCrypto_Private.h"
12 #import "MYErrorUtils.h"
15 @interface MYSigner ()
16 - (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy;
20 @implementation MYDecoder
23 - (id) initWithData: (NSData*)data error: (NSError**)outError
29 *outError = self.error;
42 OSStatus err = CMSDecoderCreate(&_decoder);
53 if( _decoder ) CFRelease(_decoder);
54 if (_policy) CFRelease(_policy);
59 - (BOOL) addData: (NSData*)data
62 return !_error && checksave( CMSDecoderUpdateMessage(_decoder, data.bytes, data.length) );
68 return !_error && checksave( CMSDecoderFinalizeMessage(_decoder) );
74 return MYError(_error, NSOSStatusErrorDomain,
75 @"%@", MYErrorName(NSOSStatusErrorDomain,_error));
80 - (BOOL) useKeychain: (MYKeychain*)keychain
82 return !_error && checksave( CMSDecoderSetSearchKeychain(_decoder, keychain.keychainRef) );
86 @synthesize policy=_policy;
89 - (NSData*) _dataFromFunction: (OSStatus (*)(CMSDecoderRef,CFDataRef*))function
92 if( checksave( (*function)(_decoder, &data) ) )
93 return [(NSData*)CFMakeCollectable(data) autorelease];
99 - (NSData*) detachedContent
101 return [self _dataFromFunction: &CMSDecoderCopyDetachedContent];
104 - (void) setDetachedContent: (NSData*)detachedContent
107 checksave( CMSDecoderSetDetachedContent(_decoder, (CFDataRef)detachedContent) );
110 - (CSSM_OID) contentType
112 NSData *data = [self _dataFromFunction: &CMSDecoderCopyEncapsulatedContentType];
113 return (CSSM_OID){data.length,(uint8*)data.bytes}; // safe since data is autoreleased
118 return [self _dataFromFunction: &CMSDecoderCopyContent];
124 return checksave( CMSDecoderGetNumSigners(_decoder, &n) ) && n > 0;
130 return check(CMSDecoderIsContentEncrypted(_decoder,&isEncrypted), @"CMSDecoderIsContentEncrypted")
137 if( ! checksave( CMSDecoderGetNumSigners(_decoder, &n) ) )
139 NSMutableArray *signers = [NSMutableArray arrayWithCapacity: n];
140 for( size_t i=0; i<n; i++ ) {
141 MYSigner *signer = [[MYSigner alloc] initWithDecoder: _decoder
144 [signers addObject: signer];
151 - (NSArray*) certificates
153 CFArrayRef certRefs = NULL;
154 if( ! checksave( CMSDecoderCopyAllCerts(_decoder, &certRefs) ) || ! certRefs )
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]];
169 static const char * kStatusNames[kCMSSignerInvalidIndex+1] = {
170 "kCMSSignerUnsigned", "kCMSSignerValid", "kCMSSignerNeedsDetachedContent",
171 "kCMSSignerInvalidSignature","kCMSSignerInvalidCert","kCMSSignerInvalidIndex"};
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"
179 "\t%u certificates\n"
182 OIDAsString(contentType), @"??"/*nameOfOID(&contentType)*/,
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"
195 (signer.verifyResult ?MYErrorName(NSOSStatusErrorDomain,signer.verifyResult)
197 MYTrustDescribe(signer.trust),
209 @implementation MYSigner : NSObject
211 #define kUncheckedStatus ((CMSSignerStatus)-1)
213 - (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy
220 if(policy) _policy = CFRetain(policy);
221 _status = kUncheckedStatus;
228 if(_decoder) CFRelease(_decoder);
229 if(_policy) CFRelease(_policy);
230 if(_trust) CFRelease(_trust);
235 if (_status == kUncheckedStatus) {
236 if( !check(CMSDecoderCopySignerStatus(_decoder, _index, _policy, (_policy!=nil),
237 &_status, &_trust, &_verifyResult),
238 @"CMSDecoderCopySignerStatus"))
239 _status = kMYSignerStatusCheckFailed;
243 - (CMSSignerStatus) status
249 - (OSStatus) verifyResult
252 return _verifyResult;
255 - (SecTrustRef) trust
262 - (NSString*) emailAddress
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)
268 CFStringRef email=NULL;
269 if( CMSDecoderCopySignerEmailAddress(_decoder, _index, &email) == noErr )
270 return [(NSString*)CFMakeCollectable(email) autorelease];
274 - (MYCertificate *) certificate
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)
280 SecCertificateRef certRef=NULL;
281 OSStatus err = CMSDecoderCopySignerCert(_decoder, _index, &certRef);
283 return [MYCertificate certificateWithCertificateRef: certRef];
285 Warn(@"CMSDecoderCopySignerCert returned err %i",err);
291 - (NSString*) description
293 NSMutableString *desc = [NSMutableString stringWithFormat: @"%@[st=%i", self.class,(int)self.status];
294 int verify = self.verifyResult;
296 [desc appendFormat: @"; verify error %i",verify];
298 MYCertificate *cert = self.certificate;
300 [desc appendFormat: @"; %@",cert.commonName];
302 [desc appendString: @"]"];
311 // Taken from Keychain.framework
312 NSString* OIDAsString(const CSSM_OID oid) {
313 if ((NULL == oid.Data) || (0 >= oid.Length)) {
316 NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)];
319 for (i = 0; i < oid.Length; ++i) {
320 [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]];
330 #pragma mark TEST CASE:
335 #import "MYEncoder.h"
336 #import "MYIdentity.h"
338 static void TestRoundTrip( NSString *title, NSData *source, MYIdentity *signer, MYCertificate *recipient )
340 Log(@"Testing MYEncoder/Decoder %@...",title);
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);
347 MYDecoder *d = [[MYDecoder alloc] init];
348 d.policy = [MYCertificate X509Policy];
349 [d addData: encoded];
352 CAssertEq(d.error,nil);
355 CAssert([d.content isEqual: source]);
356 CAssertEq(d.detachedContent,nil);
357 CAssertEq(d.isSigned,(signer!=nil));
358 CAssertEq(d.isEncrypted,(recipient!=nil));
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]);
368 CAssertEq(d.certificates.count, 0U);
369 CAssertEq(d.signers.count,0U);
374 TestCase(MYDecoder) {
375 RequireTestCase(MYEncoder);
377 MYIdentity *me = [MYIdentity preferredIdentityForName: @"MYCryptoTest"];
378 CAssert(me,@"No default identity has been set up in the Keychain");
379 Log(@"Using %@", me);
381 NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
384 TestRoundTrip(@"signing", source, me, nil);
385 TestRoundTrip(@"encryption", source, nil, me);
386 TestRoundTrip(@"signing+encryption", source, me, me);