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 |
// Taken from Keychain.framework
|
snej@8
|
312 |
NSString* OIDAsString(const CSSM_OID oid) {
|
snej@8
|
313 |
if ((NULL == oid.Data) || (0 >= oid.Length)) {
|
snej@8
|
314 |
return nil;
|
snej@8
|
315 |
} else {
|
snej@8
|
316 |
NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)];
|
snej@8
|
317 |
unsigned int i;
|
snej@8
|
318 |
|
snej@8
|
319 |
for (i = 0; i < oid.Length; ++i) {
|
snej@8
|
320 |
[result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]];
|
snej@8
|
321 |
}
|
snej@8
|
322 |
|
snej@8
|
323 |
return result;
|
snej@8
|
324 |
}
|
snej@8
|
325 |
}
|
snej@8
|
326 |
|
snej@8
|
327 |
|
snej@8
|
328 |
|
snej@8
|
329 |
#pragma mark -
|
snej@8
|
330 |
#pragma mark TEST CASE:
|
snej@8
|
331 |
|
snej@8
|
332 |
|
snej@8
|
333 |
#if DEBUG
|
snej@8
|
334 |
|
snej@8
|
335 |
#import "MYEncoder.h"
|
snej@8
|
336 |
#import "MYIdentity.h"
|
snej@8
|
337 |
|
snej@8
|
338 |
static void TestRoundTrip( NSString *title, NSData *source, MYIdentity *signer, MYCertificate *recipient )
|
snej@8
|
339 |
{
|
snej@8
|
340 |
Log(@"Testing MYEncoder/Decoder %@...",title);
|
snej@8
|
341 |
NSError *error;
|
snej@8
|
342 |
NSData *encoded = [MYEncoder encodeData: source signer: signer recipient: recipient error: &error];
|
snej@8
|
343 |
CAssertEq(error,nil);
|
snej@8
|
344 |
CAssert([encoded length]);
|
snej@8
|
345 |
Log(@"MYEncoder encoded %u bytes into %u bytes", source.length,encoded.length);
|
snej@8
|
346 |
Log(@"Decoding...");
|
snej@8
|
347 |
MYDecoder *d = [[MYDecoder alloc] init];
|
snej@8
|
348 |
d.policy = [MYCertificate X509Policy];
|
snej@8
|
349 |
[d addData: encoded];
|
snej@8
|
350 |
[d finish];
|
snej@8
|
351 |
|
snej@8
|
352 |
CAssertEq(d.error,nil);
|
snej@8
|
353 |
Log(@"%@", d.dump);
|
snej@8
|
354 |
CAssert(d.content);
|
snej@8
|
355 |
CAssert([d.content isEqual: source]);
|
snej@8
|
356 |
CAssertEq(d.detachedContent,nil);
|
snej@8
|
357 |
CAssertEq(d.isSigned,(signer!=nil));
|
snej@8
|
358 |
CAssertEq(d.isEncrypted,(recipient!=nil));
|
snej@8
|
359 |
|
snej@8
|
360 |
if( signer ) {
|
snej@8
|
361 |
CAssert(d.certificates.count >= 1); // may include extra parent certs
|
snej@8
|
362 |
CAssertEq(d.signers.count,1U);
|
snej@8
|
363 |
MYSigner *outSigner = [d.signers objectAtIndex: 0];
|
snej@8
|
364 |
CAssertEq(outSigner.status,(CMSSignerStatus)kCMSSignerValid);
|
snej@8
|
365 |
CAssertEq(outSigner.verifyResult,noErr);
|
snej@8
|
366 |
CAssert([outSigner.certificate isEqualToCertificate: signer]);
|
snej@8
|
367 |
} else {
|
snej@8
|
368 |
CAssertEq(d.certificates.count, 0U);
|
snej@8
|
369 |
CAssertEq(d.signers.count,0U);
|
snej@8
|
370 |
}
|
snej@8
|
371 |
}
|
snej@8
|
372 |
|
snej@8
|
373 |
|
snej@8
|
374 |
TestCase(MYDecoder) {
|
snej@8
|
375 |
RequireTestCase(MYEncoder);
|
snej@8
|
376 |
|
snej@8
|
377 |
MYIdentity *me = [MYIdentity preferredIdentityForName: @"MYCryptoTest"];
|
snej@8
|
378 |
CAssert(me,@"No default identity has been set up in the Keychain");
|
snej@8
|
379 |
Log(@"Using %@", me);
|
snej@8
|
380 |
|
snej@8
|
381 |
NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
|
snej@8
|
382 |
CAssert(source);
|
snej@8
|
383 |
|
snej@8
|
384 |
TestRoundTrip(@"signing", source, me, nil);
|
snej@8
|
385 |
TestRoundTrip(@"encryption", source, nil, me);
|
snej@8
|
386 |
TestRoundTrip(@"signing+encryption", source, me, me);
|
snej@8
|
387 |
}
|
snej@8
|
388 |
|
snej@8
|
389 |
#endif DEBUG
|