Factored out the name accessors of MYParsedCertificate into a new class MYCertificateName, so that both subject and issuer can be accessed. A bit of other cleanup too.
2 // MYParsedCertificate.m
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 "MYParsedCertificate.h"
16 #import "MYASN1Object.h"
18 #import "MYBERParser.h"
19 #import "MYDEREncoder.h"
20 #import "MYPublicKey.h"
21 #import "MYPrivateKey.h"
22 #import "MYCertificate.h"
23 #import "MYErrorUtils.h"
26 #define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)
29 static id $atIf(NSArray *array, NSUInteger index) {
30 return index < array.count ?[array objectAtIndex: index] :nil;
34 @interface MYCertificateName ()
35 - (id) _initWithComponents: (NSArray*)components;
40 @implementation MYParsedCertificate
43 static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
44 *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
49 kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
51 kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
53 kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
55 kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
57 kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
59 kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
61 kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
67 + (NSString*) validate: (id)root {
68 NSArray *top = $castIf(NSArray,root);
70 return @"Too few top-level components";
71 NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]);
73 return @"Too few identity components";
74 MYASN1Object *version = $castIf(MYASN1Object, [info objectAtIndex: 0]);
75 if (!version || version.tag != 0)
76 return @"Missing or invalid version";
77 NSArray *versionComps = $castIf(NSArray, version.components);
78 if (!versionComps || versionComps.count != 1)
79 return @"Invalid version";
80 NSNumber *versionNum = $castIf(NSNumber, [versionComps objectAtIndex: 0]);
81 if (!versionNum || versionNum.intValue < 0 || versionNum.intValue > 2)
82 return @"Unrecognized version number";
87 - (id) initWithCertificateData: (NSData*)data error: (NSError**)outError;
91 if (outError) *outError = nil;
92 id root = MYBERParse(data,outError);
93 NSString *errorMsg = [[self class] validate: root];
96 *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg);
100 _root = [root retain];
110 [_issuerCertificate release];
116 - (NSArray*) _info {return $castIf(NSArray,$atIf(_root,0));}
118 - (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
120 @synthesize issuerCertificate=_issuerCertificate, certificateData=_data;
123 - (NSDate*) validFrom {return $castIf(NSDate, $atIf(self._validDates, 0));}
124 - (NSDate*) validTo {return $castIf(NSDate, $atIf(self._validDates, 1));}
126 - (MYCertificateName*) subject {
127 return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease];
130 - (MYCertificateName*) issuer {
131 return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease];
134 - (BOOL) isSigned {return [_root count] >= 3;}
137 id issuer = $atIf(self._info,3);
138 return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
142 - (MYPublicKey*) subjectPublicKey {
143 NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6));
144 MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0));
145 if (!$equal(keyAlgorithmID, kRSAAlgorithmID))
147 MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1));
148 if (!keyData) return nil;
149 return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease];
152 - (MYPublicKey*) issuerPublicKey {
153 if (_issuerCertificate)
154 return _issuerCertificate.publicKey;
155 else if (self.isRoot)
156 return self.subjectPublicKey;
161 - (NSData*) signedData {
162 // The root object is a sequence; we want to extract the 1st object of that sequence.
163 const UInt8 *certStart = _data.bytes;
164 const UInt8 *start = MYBERGetContents(_data, nil);
165 if (!start) return nil;
166 size_t length = MYBERGetLength([NSData dataWithBytesNoCopy: (void*)start
167 length: _data.length - (start-certStart)
172 return [NSData dataWithBytes: start length: (start + length - certStart)];
175 - (MYOID*) signatureAlgorithmID {
176 return $castIf(MYOID, $atIf($castIf(NSArray,$atIf(_root,1)), 0));
179 - (NSData*) signature {
180 id signature = $atIf(_root,2);
181 if ([signature isKindOfClass: [MYBitString class]])
182 signature = [signature bits];
183 return $castIf(NSData,signature);
186 - (BOOL) validateSignature {
187 if (!$equal(self.signatureAlgorithmID, kRSAWithSHA1AlgorithmID))
189 NSData *signedData = self.signedData;
190 NSData *signature = self.signature;
191 MYPublicKey *pubKey = self.issuerPublicKey;
192 if (!signature || !pubKey) return NO;
193 return [pubKey verifySignature: signature ofData: signedData];
198 #pragma mark CERTIFICATE GENERATION:
201 - (id) initWithPublicKey: (MYPublicKey*)pubKey {
205 id empty = [NSNull null];
206 id version = [[MYASN1Object alloc] initWithTag: 0 ofClass: 2 components: $array($object(0))];
207 _root = $array( $marray(version,
209 $array(kRSAAlgorithmID),
211 $marray(empty, empty),
213 $array( $array(kRSAAlgorithmID, empty),
214 [MYBitString bitStringWithData: pubKey.keyData] ) ) );
222 - (void) setValidFrom: (NSDate*)validFrom {
223 [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
226 - (void) setValidTo: (NSDate*)validTo {
227 [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
231 - (BOOL) selfSignWithPrivateKey: (MYPrivateKey*)privateKey error: (NSError**)outError {
232 // Copy subject to issuer:
233 NSMutableArray *info = (NSMutableArray*)self._info;
234 [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
236 // Set serial number if there isn't one yet:
237 if (!$castIf(NSNumber, [info objectAtIndex: 1])) {
238 UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000);
239 [info replaceObjectAtIndex: 1 withObject: $object(serial)];
242 // Set up valid date range if there isn't one yet:
243 NSDate *validFrom = self.validFrom;
245 validFrom = self.validFrom = [NSDate date];
246 NSDate *validTo = self.validTo;
248 self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime];
250 // Append signature to cert structure:
251 NSData *dataToSign = [MYDEREncoder encodeRootObject: info error: outError];
254 setObj(&_root, $array(info,
255 $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
256 [MYBitString bitStringWithData: [privateKey signData: dataToSign]]));
258 setObj(&_data, [MYDEREncoder encodeRootObject: _root error: outError]);
268 @implementation MYCertificateName
270 - (id) _initWithComponents: (NSArray*)components
274 _components = [components retain];
281 [_components release];
285 - (BOOL) isEqual: (id)object {
286 return [object isKindOfClass: [MYCertificateName class]]
287 && [_components isEqual: ((MYCertificateName*)object)->_components];
290 - (NSArray*) _pairForOID: (MYOID*)oid {
291 for (id nameEntry in _components) {
292 for (id pair in $castIf(NSSet,nameEntry)) {
293 if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) {
294 if ($equal(oid, [pair objectAtIndex: 0]))
302 - (NSString*) stringForOID: (MYOID*)oid {
303 return [[self _pairForOID: oid] objectAtIndex: 1];
306 - (void) setString: (NSString*)value forOID: (MYOID*)oid {
307 NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
309 [pair replaceObjectAtIndex: 1 withObject: value];
311 [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
314 - (NSString*) commonName {return [self stringForOID: kCommonNameOID];}
315 - (NSString*) givenName {return [self stringForOID: kGivenNameOID];}
316 - (NSString*) surname {return [self stringForOID: kSurnameOID];}
317 - (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];}
318 - (NSString*) emailAddress {return [self stringForOID: kEmailOID];}
320 - (void) setCommonName: (NSString*)commonName {[self setString: commonName forOID: kCommonNameOID];}
321 - (void) setGivenName: (NSString*)givenName {[self setString: givenName forOID: kGivenNameOID];}
322 - (void) setSurname: (NSString*)surname {[self setString: surname forOID: kSurnameOID];}
323 - (void) setNameDescription: (NSString*)desc {[self setString: desc forOID: kDescriptionOID];}
324 - (void) setEmailAddress: (NSString*)email {[self setString: email forOID: kEmailOID];}
332 #pragma mark TEST CASES:
337 static MYParsedCertificate* testCert(NSString *filename, BOOL selfSigned) {
338 Log(@"--- Creating MYParsedCertificate from %@", filename);
339 NSData *certData = [NSData dataWithContentsOfFile: filename];
340 //Log(@"Cert Data =\n%@", certData);
341 NSError *error = nil;
342 MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithCertificateData: certData
345 CAssert(pcert != nil);
347 CAssertEq(pcert.isRoot, selfSigned);
349 NSData *signedData = pcert.signedData;
350 //Log(@"Signed Data = (length=%x)\n%@", signedData.length, signedData);
351 CAssertEqual(signedData, [certData subdataWithRange: NSMakeRange(4,signedData.length)]);
353 Log(@"AlgID = %@", pcert.signatureAlgorithmID);
354 Log(@"Signature = %@", pcert.signature);
355 CAssertEqual(pcert.signatureAlgorithmID, kRSAWithSHA1AlgorithmID);
356 CAssert(pcert.signature != nil);
357 Log(@"Subject Public Key = %@", pcert.subjectPublicKey);
358 CAssert(pcert.subjectPublicKey);
360 Log(@"Issuer Public Key = %@", pcert.issuerPublicKey);
361 CAssert(pcert.issuerPublicKey);
363 CAssert(pcert.validateSignature);
365 MYCertificateName *subject = pcert.subject;
366 Log(@"Common Name = %@", subject.commonName);
367 Log(@"Given Name = %@", subject.givenName);
368 Log(@"Surname = %@", subject.surname);
369 Log(@"Desc = %@", subject.nameDescription);
370 Log(@"Email = %@", subject.emailAddress);
375 TestCase(ParsedCert) {
376 testCert(@"../../Tests/selfsigned.cer", YES);
377 testCert(@"../../Tests/iphonedev.cer", NO);
381 #import "MYKeychain.h"
383 TestCase(CreateCert) {
384 MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512];
385 MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithPublicKey: privateKey.publicKey];
386 MYCertificateName *subject = pcert.subject;
387 subject.commonName = @"testcase";
388 subject.givenName = @"Test";
389 subject.surname = @"Case";
390 subject.nameDescription = @"Just a test certificate created by MYCrypto";
391 subject.emailAddress = @"testcase@example.com";
393 subject = pcert.subject;
394 CAssertEqual(subject.commonName, @"testcase");
395 CAssertEqual(subject.givenName, @"Test");
396 CAssertEqual(subject.surname, @"Case");
397 CAssertEqual(subject.nameDescription, @"Just a test certificate created by MYCrypto");
398 CAssertEqual(subject.emailAddress, @"testcase@example.com");
402 CAssert([pcert selfSignWithPrivateKey: privateKey error: &error]);
404 NSData *certData = pcert.certificateData;
405 Log(@"Generated cert = \n%@", certData);
407 [certData writeToFile: @"../../Tests/generated.cer" atomically: YES];
408 MYParsedCertificate *pcert2 = testCert(@"../../Tests/generated.cer", YES);
410 Log(@"Verifying...");
411 MYCertificateName *subject2 = pcert2.subject;
412 CAssertEqual(subject2,subject);
413 CAssertEqual(subject2.commonName, @"testcase");
414 CAssertEqual(subject2.givenName, @"Test");
415 CAssertEqual(subject2.surname, @"Case");
416 CAssertEqual(subject2.nameDescription, @"Just a test certificate created by MYCrypto");
417 CAssertEqual(subject2.emailAddress, @"testcase@example.com");
425 /* Parsed form of selfsigned.cer:
429 MYASN1Object[2/0]: <-- version (tag=0, constructed)
433 {1 2 840 113549 1 1 1} <-- algorithm ID
441 {1 2 840 113549 1 9 1}
454 Just a fictitious person
455 Sequence: <--validity
456 2009-04-12 21:54:35 -0700
457 2010-04-13 21:54:35 -0700
458 Sequence: <-- subject
460 Sequence: <-- surname
465 {1 2 840 113549 1 9 1}
468 Sequence: <-- common name
472 Sequence: <-- first name
476 Sequence: <-- description
478 Just a fictitious person
479 Sequence: <-- public key info
481 {1 2 840 113549 1 1 1} <-- algorithm ID (RSA)
483 MYBitString<3082010a 02820101 0095713c 360badf2 d8575ebd 278fa26b a2e6d05e 1eb04eaa 9fa6f11b fd341556 038b3077 525c7adb f5aedf3b 249b08e6 7f77af26 7ff2feb8 5f4ccb96 5269dbd2 f01f19b6 55fc4ea3 a85f2ede 11ff80f8 fc23e662 f263f685 06a9ec07 f7ee4249 af184f21 2d9253d8 7f6f7cbc 96e6ba5c abc8f4e7 3bf6100b 06dcf3ee 999d4170 f5dd005d a24a54a1 3edaddd5 0675409d 6728a387 5fa71898 ebf7d93d 4af8742d f9a0e9ad 6dc21cfa fc2d1967 e692575b 56e5376c 8cf008e8 a442d787 6843a92e 9501b144 8a75adef 5e804fec 6d09740d 1ea8442e 67fac3be c5ea3af5 d95d9f95 2c507711 01c45942 28ad1410 23525324 62848476 d987d3c7 d65f9057 daf1e853 77020301 0001> <-- DER-encoded key
491 <301a0608 2b060105 05070301 06082b06 01050507 03020604 551d2500>
493 {1 2 840 113549 1 1 5}
495 MYBitString<79c8e789 50a11fcb 7398f5fe 0cfa2595 b2476f53 62dfbea2 70ae3a8b fdaf5a57 39be6101 fc5e0929 e57a0b2b 41e3ab52 f78ef1b5 ecc8848c da7f42aa b57c3df4 df4125a9 db4e6388 197c2a1c e326c1a5 5203b4ef da057b91 4abc43aa 3eeee6aa fe4303c3 0f000175 16b916b5 72f8b74f c682a06f 920e3bbf a16cdad8 fce3f184 adccc61e 8d3b44e5 8bd103f0 46310f6a 992f240a b290354c 04c519c9 22276df6 310ccb8e 942e38f6 555ca40b 71482e52 146a9988 f021c2c0 2d285db5 59d48eaf 7b20559f 068ea1a0 f07fbaee 29284ada 28bf8344 f435f30f 6263f0c9 9c4920ce a1b7c6c0 9cfa3bbb af5a0fee 5b0e94eb 9c57d28b 1bb9c977 be53e4bb b675ffaa>