A snapshot taken during the long, agonizing crawl toward getting everything running on iPhone.
5 // Created by Jens Alfke on 6/2/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
10 // <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
11 // <http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt> "X.509 Style Guide"
12 // <http://en.wikipedia.org/wiki/X.509> Wikipedia article on X.509
15 #import "MYCertificateInfo.h"
17 #import "MYASN1Object.h"
19 #import "MYBERParser.h"
20 #import "MYDEREncoder.h"
21 #import "MYErrorUtils.h"
24 #define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)
27 static id $atIf(NSArray *array, NSUInteger index) {
28 return index < array.count ?[array objectAtIndex: index] :nil;
32 @interface MYCertificateName ()
33 - (id) _initWithComponents: (NSArray*)components;
36 @interface MYCertificateInfo ()
37 @property (retain) NSArray *_root;
42 @implementation MYCertificateInfo
45 static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
46 *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
51 kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
53 kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
55 kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
57 kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
59 kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
61 kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
63 kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
69 - (id) initWithRoot: (NSArray*)root
73 _root = [root retain];
78 + (NSString*) validate: (id)root {
79 NSArray *top = $castIf(NSArray,root);
81 return @"Too few top-level components";
82 NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]);
84 return @"Too few identity components";
85 MYASN1Object *version = $castIf(MYASN1Object, [info objectAtIndex: 0]);
86 if (!version || version.tag != 0)
87 return @"Missing or invalid version";
88 NSArray *versionComps = $castIf(NSArray, version.components);
89 if (!versionComps || versionComps.count != 1)
90 return @"Invalid version";
91 NSNumber *versionNum = $castIf(NSNumber, [versionComps objectAtIndex: 0]);
92 if (!versionNum || versionNum.intValue < 0 || versionNum.intValue > 2)
93 return @"Unrecognized version number";
98 - (id) initWithCertificateData: (NSData*)data error: (NSError**)outError;
100 if (outError) *outError = nil;
101 id root = MYBERParse(data,outError);
102 NSString *errorMsg = [[self class] validate: root];
104 if (outError && !*outError)
105 *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg);
110 return [self initWithRoot: root];
119 - (BOOL) isEqual: (id)object {
120 return [object isKindOfClass: [MYCertificateInfo class]]
121 && [_root isEqual: ((MYCertificateInfo*)object)->_root];
124 - (NSArray*) _info {return $castIf(NSArray,$atIf(_root,0));}
126 - (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
131 - (NSDate*) validFrom {return $castIf(NSDate, $atIf(self._validDates, 0));}
132 - (NSDate*) validTo {return $castIf(NSDate, $atIf(self._validDates, 1));}
134 - (MYCertificateName*) subject {
135 return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease];
138 - (MYCertificateName*) issuer {
139 return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease];
142 - (BOOL) isSigned {return [_root count] >= 3;}
145 id issuer = $atIf(self._info,3);
146 return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
150 - (MYPublicKey*) subjectPublicKey {
151 NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6));
152 MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0));
153 if (!$equal(keyAlgorithmID, kRSAAlgorithmID))
155 MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1));
156 if (!keyData) return nil;
157 return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease];
166 @implementation MYCertificateRequest
168 - (id) initWithPublicKey: (MYPublicKey*)publicKey {
170 id empty = [NSNull null];
171 id version = [[MYASN1Object alloc] initWithTag: 0 ofClass: 2 components: $array($object(0))];
172 NSArray *root = $array( $marray(version,
174 $array(kRSAAlgorithmID),
176 $marray(empty, empty),
178 $array( $array(kRSAAlgorithmID, empty),
179 [MYBitString bitStringWithData: publicKey.keyData] ) ) );
180 self = [super initWithRoot: root];
183 _publicKey = publicKey.retain;
190 [_publicKey release];
195 - (NSDate*) validFrom {return [super validFrom];}
196 - (NSDate*) validTo {return [super validTo];}
198 - (void) setValidFrom: (NSDate*)validFrom {
199 [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
202 - (void) setValidTo: (NSDate*)validTo {
203 [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
207 - (void) fillInValues {
208 NSMutableArray *info = (NSMutableArray*)self._info;
209 // Set serial number if there isn't one yet:
210 if (!$castIf(NSNumber, [info objectAtIndex: 1])) {
211 UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000);
212 [info replaceObjectAtIndex: 1 withObject: $object(serial)];
215 // Set up valid date range if there isn't one yet:
216 NSDate *validFrom = self.validFrom;
218 validFrom = self.validFrom = [NSDate date];
219 NSDate *validTo = self.validTo;
221 self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime];
225 - (NSData*) requestData: (NSError**)outError {
227 return [MYDEREncoder encodeRootObject: self._info error: outError];
231 - (NSData*) selfSignWithPrivateKey: (MYPrivateKey*)privateKey
232 error: (NSError**)outError
234 AssertEqual(privateKey.publicKey, _publicKey); // Keys must form a pair
236 // Copy subject to issuer:
237 NSMutableArray *info = (NSMutableArray*)self._info;
238 [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
241 NSData *dataToSign = [self requestData: outError];
244 MYBitString *signature = [MYBitString bitStringWithData: [privateKey signData: dataToSign]];
246 // Generate and encode the certificate:
247 NSArray *root = $array(info,
248 $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
250 return [MYDEREncoder encodeRootObject: root error: outError];
254 - (MYIdentity*) createSelfSignedIdentityWithPrivateKey: (MYPrivateKey*)privateKey
255 error: (NSError**)outError
257 Assert(privateKey.keychain!=nil);
258 NSData *certData = [self selfSignWithPrivateKey: privateKey error: outError];
261 MYCertificate *cert = [privateKey.keychain importCertificate: certData];
263 Assert(cert.keychain!=nil);
264 AssertEqual(cert.publicKey.keyData, _publicKey.keyData);
265 MYIdentity *identity = cert.identity;
266 Assert(identity!=nil);
276 @implementation MYCertificateName
278 - (id) _initWithComponents: (NSArray*)components
282 _components = [components retain];
289 [_components release];
293 - (BOOL) isEqual: (id)object {
294 return [object isKindOfClass: [MYCertificateName class]]
295 && [_components isEqual: ((MYCertificateName*)object)->_components];
298 - (NSArray*) _pairForOID: (MYOID*)oid {
299 for (id nameEntry in _components) {
300 for (id pair in $castIf(NSSet,nameEntry)) {
301 if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) {
302 if ($equal(oid, [pair objectAtIndex: 0]))
310 - (NSString*) stringForOID: (MYOID*)oid {
311 return [[self _pairForOID: oid] objectAtIndex: 1];
314 - (void) setString: (NSString*)value forOID: (MYOID*)oid {
315 NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
317 [pair replaceObjectAtIndex: 1 withObject: value];
319 [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
322 - (NSString*) commonName {return [self stringForOID: kCommonNameOID];}
323 - (NSString*) givenName {return [self stringForOID: kGivenNameOID];}
324 - (NSString*) surname {return [self stringForOID: kSurnameOID];}
325 - (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];}
326 - (NSString*) emailAddress {return [self stringForOID: kEmailOID];}
328 - (void) setCommonName: (NSString*)commonName {[self setString: commonName forOID: kCommonNameOID];}
329 - (void) setGivenName: (NSString*)givenName {[self setString: givenName forOID: kGivenNameOID];}
330 - (void) setSurname: (NSString*)surname {[self setString: surname forOID: kSurnameOID];}
331 - (void) setNameDescription: (NSString*)desc {[self setString: desc forOID: kDescriptionOID];}
332 - (void) setEmailAddress: (NSString*)email {[self setString: email forOID: kEmailOID];}
340 #pragma mark TEST CASES:
345 static MYCertificateInfo* testCertData(NSData *certData, BOOL selfSigned) {
346 //Log(@"Cert Data =\n%@", certData);
347 CAssert(certData!=nil);
348 NSError *error = nil;
349 MYCertificateInfo *pcert = [[MYCertificateInfo alloc] initWithCertificateData: certData
352 CAssert(pcert != nil);
354 CAssertEq(pcert.isRoot, selfSigned);
356 Log(@"Subject Public Key = %@", pcert.subjectPublicKey);
357 CAssert(pcert.subjectPublicKey);
358 MYCertificateName *subject = pcert.subject;
359 Log(@"Common Name = %@", subject.commonName);
360 Log(@"Given Name = %@", subject.givenName);
361 Log(@"Surname = %@", subject.surname);
362 Log(@"Desc = %@", subject.nameDescription);
363 Log(@"Email = %@", subject.emailAddress);
364 CAssert(subject.commonName);
366 // Now go through MYCertificate:
367 MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
369 CAssertEqual(cert.info, pcert);
374 static MYCertificateInfo* testCert(NSString *filename, BOOL selfSigned) {
376 filename = [[NSBundle mainBundle] pathForResource: filename ofType: @"cer"];
378 filename = [[@"../../Tests/" stringByAppendingPathComponent: filename]
379 stringByAppendingPathExtension: @"cer"];
381 Log(@"--- Creating MYCertificateInfo from %@", filename);
382 return testCertData([NSData dataWithContentsOfFile: filename], selfSigned);
386 TestCase(ParsedCert) {
387 testCert(@"selfsigned", YES);
388 testCert(@"iphonedev", NO);
392 #import "MYCrypto_Private.h"
394 TestCase(CreateCert) {
395 MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512];
397 MYIdentity *identity = nil;
399 MYCertificateRequest *pcert = [[MYCertificateRequest alloc] initWithPublicKey: privateKey.publicKey];
400 MYCertificateName *subject = pcert.subject;
401 subject.commonName = @"testcase";
402 subject.givenName = @"Test";
403 subject.surname = @"Case";
404 subject.nameDescription = @"Just a test certificate created by MYCrypto";
405 subject.emailAddress = @"testcase@example.com";
407 subject = pcert.subject;
408 CAssertEqual(subject.commonName, @"testcase");
409 CAssertEqual(subject.givenName, @"Test");
410 CAssertEqual(subject.surname, @"Case");
411 CAssertEqual(subject.nameDescription, @"Just a test certificate created by MYCrypto");
412 CAssertEqual(subject.emailAddress, @"testcase@example.com");
416 NSData *certData = [pcert selfSignWithPrivateKey: privateKey error: &error];
417 Log(@"Generated cert = \n%@", certData);
421 #if !TARGET_OS_IPHONE
422 [certData writeToFile: @"../../Tests/generated.cer" atomically: YES];
424 MYCertificateInfo *pcert2 = testCertData(certData, YES);
426 Log(@"Verifying Info...");
427 MYCertificateName *subject2 = pcert2.subject;
428 CAssertEqual(subject2,subject);
429 CAssertEqual(subject2.commonName, @"testcase");
430 CAssertEqual(subject2.givenName, @"Test");
431 CAssertEqual(subject2.surname, @"Case");
432 CAssertEqual(subject2.nameDescription, @"Just a test certificate created by MYCrypto");
433 CAssertEqual(subject2.emailAddress, @"testcase@example.com");
435 Log(@"Verifying Signature...");
436 MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
437 Log(@"Loaded %@", cert);
439 MYPublicKey *certKey = cert.publicKey;
440 Log(@"Its public key = %@", certKey);
441 CAssertEqual(certKey.keyData, privateKey.publicKey.keyData);
443 Log(@"Creating Identity...");
444 identity = [pcert createSelfSignedIdentityWithPrivateKey: privateKey error: &error];
445 Log(@"Identity = %@", identity);
448 CAssertEqual(identity.keychain, [MYKeychain defaultKeychain]);
449 CAssertEqual(identity.privateKey, privateKey);
450 CAssert([identity isEqualToCertificate: cert]);
455 [privateKey removeFromKeychain];
456 [identity removeFromKeychain];
464 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
466 Redistribution and use in source and binary forms, with or without modification, are permitted
467 provided that the following conditions are met:
469 * Redistributions of source code must retain the above copyright notice, this list of conditions
470 and the following disclaimer.
471 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
472 and the following disclaimer in the documentation and/or other materials provided with the
475 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
476 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
477 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
478 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
479 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
480 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
481 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
482 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.