MYParsedCertificate.m
author Jens Alfke <jens@mooseyard.com>
Fri Jun 05 08:57:18 2009 -0700 (2009-06-05)
changeset 20 df9da0f6b358
parent 19 f6c91b9da05b
permissions -rw-r--r--
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.
     1 //
     2 //  MYParsedCertificate.m
     3 //  MYCrypto
     4 //
     5 //  Created by Jens Alfke on 6/2/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 // References:
    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
    13 
    14 
    15 #import "MYParsedCertificate.h"
    16 #import "MYASN1Object.h"
    17 #import "MYOID.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"
    24 
    25 
    26 #define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)
    27 
    28 
    29 static id $atIf(NSArray *array, NSUInteger index) {
    30     return index < array.count ?[array objectAtIndex: index] :nil;
    31 }
    32 
    33 
    34 @interface MYCertificateName ()
    35 - (id) _initWithComponents: (NSArray*)components;
    36 @end
    37 
    38 
    39 #pragma mark -
    40 @implementation MYParsedCertificate
    41 
    42 
    43 static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
    44             *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
    45 
    46 
    47 + (void) initialize {
    48     if (!kEmailOID) {
    49         kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
    50                                                       count: 7];
    51         kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
    52                                                               count: 7];
    53         kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
    54                                                      count: 4];
    55         kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
    56                                                     count: 4];
    57         kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
    58                                                   count: 4];
    59         kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
    60                                                 count: 7];
    61         kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
    62                                                 count: 7];
    63     }
    64     
    65 }
    66 
    67 + (NSString*) validate: (id)root {
    68     NSArray *top = $castIf(NSArray,root);
    69     if (top.count < 3)
    70         return @"Too few top-level components";
    71     NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]);
    72     if (info.count < 7)
    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";
    83     return nil;
    84 }
    85 
    86 
    87 - (id) initWithCertificateData: (NSData*)data error: (NSError**)outError;
    88 {
    89     self = [super init];
    90     if (self != nil) {
    91         if (outError) *outError = nil;
    92         id root = MYBERParse(data,outError);
    93         NSString *errorMsg = [[self class] validate: root];
    94         if (errorMsg) {
    95             if (!*outError)
    96                 *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg);
    97             [self release];
    98             return nil;
    99         }
   100         _root = [root retain];
   101         _data = [data copy];
   102     }
   103     return self;
   104 }
   105 
   106 - (void) dealloc
   107 {
   108     
   109     [_root release];
   110     [_issuerCertificate release];
   111     [_data release];
   112     [super dealloc];
   113 }
   114 
   115 
   116 - (NSArray*) _info       {return $castIf(NSArray,$atIf(_root,0));}
   117 
   118 - (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
   119 
   120 @synthesize issuerCertificate=_issuerCertificate, certificateData=_data;
   121 
   122 
   123 - (NSDate*) validFrom       {return $castIf(NSDate, $atIf(self._validDates, 0));}
   124 - (NSDate*) validTo         {return $castIf(NSDate, $atIf(self._validDates, 1));}
   125 
   126 - (MYCertificateName*) subject {
   127     return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease];
   128 }
   129 
   130 - (MYCertificateName*) issuer {
   131     return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease];
   132 }
   133 
   134 - (BOOL) isSigned           {return [_root count] >= 3;}
   135 
   136 - (BOOL) isRoot {
   137     id issuer = $atIf(self._info,3);
   138     return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
   139 }
   140 
   141 
   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))
   146         return nil;
   147     MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1));
   148     if (!keyData) return nil;
   149     return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease];
   150 }
   151 
   152 - (MYPublicKey*) issuerPublicKey {
   153     if (_issuerCertificate)
   154         return _issuerCertificate.publicKey;
   155     else if (self.isRoot)
   156         return self.subjectPublicKey;
   157     else
   158         return nil;
   159 }
   160 
   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)
   168                                                   freeWhenDone: NO],
   169                                    NULL);
   170     if (length==0)
   171         return nil;
   172     return [NSData dataWithBytes: start length: (start + length - certStart)];
   173 }
   174 
   175 - (MYOID*) signatureAlgorithmID {
   176     return $castIf(MYOID, $atIf($castIf(NSArray,$atIf(_root,1)), 0));
   177 }
   178 
   179 - (NSData*) signature {
   180     id signature = $atIf(_root,2);
   181     if ([signature isKindOfClass: [MYBitString class]])
   182         signature = [signature bits];
   183     return $castIf(NSData,signature);
   184 }
   185 
   186 - (BOOL) validateSignature {
   187     if (!$equal(self.signatureAlgorithmID, kRSAWithSHA1AlgorithmID))
   188         return NO;
   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];
   194 }
   195 
   196 
   197 #pragma mark -
   198 #pragma mark CERTIFICATE GENERATION:
   199 
   200 
   201 - (id) initWithPublicKey: (MYPublicKey*)pubKey {
   202     Assert(pubKey);
   203     self = [super init];
   204     if (self != nil) {
   205         id empty = [NSNull null];
   206         id version = [[MYASN1Object alloc] initWithTag: 0 ofClass: 2 components: $array($object(0))];
   207         _root = $array( $marray(version,
   208                                 empty,       // serial #
   209                                 $array(kRSAAlgorithmID),
   210                                 $marray(),
   211                                 $marray(empty, empty),
   212                                 $marray(),
   213                                 $array( $array(kRSAAlgorithmID, empty),
   214                                        [MYBitString bitStringWithData: pubKey.keyData] ) ) );
   215         [version release];
   216         [_root retain];
   217     }
   218     return self;
   219 }
   220 
   221 
   222 - (void) setValidFrom: (NSDate*)validFrom {
   223     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
   224 }
   225 
   226 - (void) setValidTo: (NSDate*)validTo {
   227     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
   228 }
   229 
   230 
   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]];
   235     
   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)];
   240     }
   241     
   242     // Set up valid date range if there isn't one yet:
   243     NSDate *validFrom = self.validFrom;
   244     if (!validFrom)
   245         validFrom = self.validFrom = [NSDate date];
   246     NSDate *validTo = self.validTo;
   247     if (!validTo)
   248         self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; 
   249     
   250     // Append signature to cert structure:
   251     NSData *dataToSign = [MYDEREncoder encodeRootObject: info error: outError];
   252     if (!dataToSign)
   253         return NO;
   254     setObj(&_root, $array(info, 
   255                           $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
   256                           [MYBitString bitStringWithData: [privateKey signData: dataToSign]]));
   257     
   258     setObj(&_data, [MYDEREncoder encodeRootObject: _root error: outError]);
   259     return _data!=nil;
   260 }
   261 
   262 
   263 @end
   264 
   265 
   266 
   267 #pragma mark -
   268 @implementation MYCertificateName
   269 
   270 - (id) _initWithComponents: (NSArray*)components
   271 {
   272     self = [super init];
   273     if (self != nil) {
   274         _components = [components retain];
   275     }
   276     return self;
   277 }
   278 
   279 - (void) dealloc
   280 {
   281     [_components release];
   282     [super dealloc];
   283 }
   284 
   285 - (BOOL) isEqual: (id)object {
   286     return [object isKindOfClass: [MYCertificateName class]]
   287         && [_components isEqual: ((MYCertificateName*)object)->_components];
   288 }
   289 
   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]))
   295                     return pair;
   296             }
   297         }
   298     }
   299     return nil;
   300 }
   301 
   302 - (NSString*) stringForOID: (MYOID*)oid {
   303     return [[self _pairForOID: oid] objectAtIndex: 1];
   304 }
   305 
   306 - (void) setString: (NSString*)value forOID: (MYOID*)oid {
   307     NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
   308     if (pair)
   309         [pair replaceObjectAtIndex: 1 withObject: value];
   310     else
   311         [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
   312 }
   313 
   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];}
   319 
   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];}
   325 
   326 
   327 @end
   328 
   329 
   330 
   331 #pragma mark -
   332 #pragma mark TEST CASES:
   333 
   334 #if DEBUG
   335 
   336 
   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 
   343                                                                                 error: &error];
   344     CAssertNil(error);
   345     CAssert(pcert != nil);
   346     
   347     CAssertEq(pcert.isRoot, selfSigned);
   348     
   349     NSData *signedData = pcert.signedData;
   350     //Log(@"Signed Data = (length=%x)\n%@", signedData.length, signedData);
   351     CAssertEqual(signedData, [certData subdataWithRange: NSMakeRange(4,signedData.length)]);
   352     
   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);
   359     if (selfSigned) {
   360         Log(@"Issuer Public Key = %@", pcert.issuerPublicKey);
   361         CAssert(pcert.issuerPublicKey);
   362         
   363         CAssert(pcert.validateSignature);
   364     }
   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);
   371     return pcert;
   372 }
   373 
   374 
   375 TestCase(ParsedCert) {
   376     testCert(@"../../Tests/selfsigned.cer", YES);
   377     testCert(@"../../Tests/iphonedev.cer", NO);
   378 }
   379 
   380 
   381 #import "MYKeychain.h"
   382 
   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";
   392 
   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");
   399     
   400     Log(@"Signing...");
   401     NSError *error;
   402     CAssert([pcert selfSignWithPrivateKey: privateKey error: &error]);
   403     CAssertNil(error);
   404     NSData *certData = pcert.certificateData;
   405     Log(@"Generated cert = \n%@", certData);
   406     CAssert(certData);
   407     [certData writeToFile: @"../../Tests/generated.cer" atomically: YES];
   408     MYParsedCertificate *pcert2 = testCert(@"../../Tests/generated.cer", YES);
   409     
   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");
   418 }
   419 
   420 #endif
   421 
   422 
   423 
   424 
   425 /* Parsed form of selfsigned.cer:
   426  
   427 Sequence:                           <-- top
   428     Sequence:                       <-- info
   429         MYASN1Object[2/0]:          <-- version (tag=0, constructed)
   430             2                       
   431         1                           <-- serial number
   432         Sequence:
   433             {1 2 840 113549 1 1 1}  <-- algorithm ID
   434         Sequence:                   <-- issuer
   435             Set:
   436                 Sequence:
   437                     {2 5 4 4}
   438                     Widdershins
   439             Set:
   440                 Sequence:
   441                     {1 2 840 113549 1 9 1}
   442                     waldo@example.com
   443             Set:
   444                 Sequence:
   445                     {2 5 4 3}
   446                     waldo
   447             Set:
   448                 Sequence:
   449                     {2 5 4 42}
   450                     Waldo
   451             Set:
   452                 Sequence:
   453                     {2 5 4 13}
   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
   459             Set:
   460                 Sequence:                   <-- surname
   461                     {2 5 4 4}
   462                     Widdershins
   463             Set:
   464                 Sequence:                   <-- email
   465                     {1 2 840 113549 1 9 1}
   466                     waldo@example.com
   467             Set:
   468                 Sequence:                   <-- common name
   469                     {2 5 4 3}
   470                     waldo
   471             Set:
   472                 Sequence:                   <-- first name
   473                     {2 5 4 42}
   474                     Waldo
   475             Set:
   476                 Sequence:                   <-- description
   477                     {2 5 4 13}
   478                     Just a fictitious person
   479         Sequence:                               <-- public key info
   480             Sequence:
   481                 {1 2 840 113549 1 1 1}          <-- algorithm ID (RSA)
   482                 <null>
   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
   484         MYASN1Object[2/3]:
   485             Sequence:
   486                 Sequence:
   487                     {2 5 29 15}
   488                     <030202fc>
   489                 Sequence:
   490                     {2 5 29 37}
   491                     <301a0608 2b060105 05070301 06082b06 01050507 03020604 551d2500>
   492     Sequence:
   493         {1 2 840 113549 1 1 5}
   494         <null>
   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>
   496 */