MYCertificateInfo.m
author Jens Alfke <jens@mooseyard.com>
Sun Jun 07 21:53:56 2009 -0700 (2009-06-07)
changeset 23 39fec79de6e8
parent 21 2c300b15b381
child 24 6856e071d25a
permissions -rw-r--r--
A snapshot taken during the long, agonizing crawl toward getting everything running on iPhone.
     1 //
     2 //  MYCertificateInfo.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 "MYCertificateInfo.h"
    16 #import "MYCrypto.h"
    17 #import "MYASN1Object.h"
    18 #import "MYOID.h"
    19 #import "MYBERParser.h"
    20 #import "MYDEREncoder.h"
    21 #import "MYErrorUtils.h"
    22 
    23 
    24 #define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)
    25 
    26 
    27 static id $atIf(NSArray *array, NSUInteger index) {
    28     return index < array.count ?[array objectAtIndex: index] :nil;
    29 }
    30 
    31 
    32 @interface MYCertificateName ()
    33 - (id) _initWithComponents: (NSArray*)components;
    34 @end
    35 
    36 @interface MYCertificateInfo ()
    37 @property (retain) NSArray *_root;
    38 @end
    39 
    40 
    41 #pragma mark -
    42 @implementation MYCertificateInfo
    43 
    44 
    45 static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
    46             *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
    47 
    48 
    49 + (void) initialize {
    50     if (!kEmailOID) {
    51         kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
    52                                                       count: 7];
    53         kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
    54                                                               count: 7];
    55         kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
    56                                                      count: 4];
    57         kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
    58                                                     count: 4];
    59         kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
    60                                                   count: 4];
    61         kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
    62                                                 count: 7];
    63         kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
    64                                                 count: 7];
    65     }
    66 }
    67 
    68 
    69 - (id) initWithRoot: (NSArray*)root
    70 {
    71     self = [super init];
    72     if (self != nil) {
    73         _root = [root retain];
    74     }
    75     return self;
    76 }
    77 
    78 + (NSString*) validate: (id)root {
    79     NSArray *top = $castIf(NSArray,root);
    80     if (top.count < 3)
    81         return @"Too few top-level components";
    82     NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]);
    83     if (info.count < 7)
    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";
    94     return nil;
    95 }
    96 
    97 
    98 - (id) initWithCertificateData: (NSData*)data error: (NSError**)outError;
    99 {
   100     if (outError) *outError = nil;
   101     id root = MYBERParse(data,outError);
   102     NSString *errorMsg = [[self class] validate: root];
   103     if (errorMsg) {
   104         if (outError && !*outError)
   105             *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg);
   106         [self release];
   107         return nil;
   108     }
   109 
   110     return [self initWithRoot: root];
   111 }
   112 
   113 - (void) dealloc
   114 {
   115     [_root release];
   116     [super dealloc];
   117 }
   118 
   119 - (BOOL) isEqual: (id)object {
   120     return [object isKindOfClass: [MYCertificateInfo class]]
   121         && [_root isEqual: ((MYCertificateInfo*)object)->_root];
   122 }
   123 
   124 - (NSArray*) _info       {return $castIf(NSArray,$atIf(_root,0));}
   125 
   126 - (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
   127 
   128 @synthesize _root;
   129 
   130 
   131 - (NSDate*) validFrom       {return $castIf(NSDate, $atIf(self._validDates, 0));}
   132 - (NSDate*) validTo         {return $castIf(NSDate, $atIf(self._validDates, 1));}
   133 
   134 - (MYCertificateName*) subject {
   135     return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease];
   136 }
   137 
   138 - (MYCertificateName*) issuer {
   139     return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease];
   140 }
   141 
   142 - (BOOL) isSigned           {return [_root count] >= 3;}
   143 
   144 - (BOOL) isRoot {
   145     id issuer = $atIf(self._info,3);
   146     return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
   147 }
   148 
   149 
   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))
   154         return nil;
   155     MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1));
   156     if (!keyData) return nil;
   157     return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease];
   158 }
   159 
   160 @end
   161 
   162 
   163 
   164 
   165 #pragma mark -
   166 @implementation MYCertificateRequest
   167 
   168 - (id) initWithPublicKey: (MYPublicKey*)publicKey {
   169     Assert(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,
   173                                     empty,       // serial #
   174                                     $array(kRSAAlgorithmID),
   175                                     $marray(),
   176                                     $marray(empty, empty),
   177                                     $marray(),
   178                                     $array( $array(kRSAAlgorithmID, empty),
   179                                            [MYBitString bitStringWithData: publicKey.keyData] ) ) );
   180     self = [super initWithRoot: root];
   181     [version release];
   182     if (self) {
   183         _publicKey = publicKey.retain;
   184     }
   185     return self;
   186 }
   187     
   188 - (void) dealloc
   189 {
   190     [_publicKey release];
   191     [super dealloc];
   192 }
   193 
   194 
   195 - (NSDate*) validFrom       {return [super validFrom];}
   196 - (NSDate*) validTo         {return [super validTo];}
   197 
   198 - (void) setValidFrom: (NSDate*)validFrom {
   199     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
   200 }
   201 
   202 - (void) setValidTo: (NSDate*)validTo {
   203     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
   204 }
   205 
   206 
   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)];
   213     }
   214     
   215     // Set up valid date range if there isn't one yet:
   216     NSDate *validFrom = self.validFrom;
   217     if (!validFrom)
   218         validFrom = self.validFrom = [NSDate date];
   219     NSDate *validTo = self.validTo;
   220     if (!validTo)
   221         self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; 
   222 }
   223 
   224 
   225 - (NSData*) requestData: (NSError**)outError {
   226     [self fillInValues];
   227     return [MYDEREncoder encodeRootObject: self._info error: outError];
   228 }
   229 
   230 
   231 - (NSData*) selfSignWithPrivateKey: (MYPrivateKey*)privateKey 
   232                              error: (NSError**)outError 
   233 {
   234     AssertEqual(privateKey.publicKey, _publicKey);  // Keys must form a pair
   235     
   236     // Copy subject to issuer:
   237     NSMutableArray *info = (NSMutableArray*)self._info;
   238     [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
   239     
   240     // Sign the request:
   241     NSData *dataToSign = [self requestData: outError];
   242     if (!dataToSign)
   243         return nil;
   244     MYBitString *signature = [MYBitString bitStringWithData: [privateKey signData: dataToSign]];
   245     
   246     // Generate and encode the certificate:
   247     NSArray *root = $array(info, 
   248                            $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
   249                            signature);
   250     return [MYDEREncoder encodeRootObject: root error: outError];
   251 }
   252 
   253 
   254 - (MYIdentity*) createSelfSignedIdentityWithPrivateKey: (MYPrivateKey*)privateKey
   255                                                  error: (NSError**)outError
   256 {
   257     Assert(privateKey.keychain!=nil);
   258     NSData *certData = [self selfSignWithPrivateKey: privateKey error: outError];
   259     if (!certData)
   260         return nil;
   261     MYCertificate *cert = [privateKey.keychain importCertificate: certData];
   262     Assert(cert!=nil);
   263     Assert(cert.keychain!=nil);
   264     AssertEqual(cert.publicKey.keyData, _publicKey.keyData);
   265     MYIdentity *identity = cert.identity;
   266     Assert(identity!=nil);
   267     return identity;
   268 }
   269 
   270 
   271 @end
   272 
   273 
   274 
   275 #pragma mark -
   276 @implementation MYCertificateName
   277 
   278 - (id) _initWithComponents: (NSArray*)components
   279 {
   280     self = [super init];
   281     if (self != nil) {
   282         _components = [components retain];
   283     }
   284     return self;
   285 }
   286 
   287 - (void) dealloc
   288 {
   289     [_components release];
   290     [super dealloc];
   291 }
   292 
   293 - (BOOL) isEqual: (id)object {
   294     return [object isKindOfClass: [MYCertificateName class]]
   295         && [_components isEqual: ((MYCertificateName*)object)->_components];
   296 }
   297 
   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]))
   303                     return pair;
   304             }
   305         }
   306     }
   307     return nil;
   308 }
   309 
   310 - (NSString*) stringForOID: (MYOID*)oid {
   311     return [[self _pairForOID: oid] objectAtIndex: 1];
   312 }
   313 
   314 - (void) setString: (NSString*)value forOID: (MYOID*)oid {
   315     NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
   316     if (pair)
   317         [pair replaceObjectAtIndex: 1 withObject: value];
   318     else
   319         [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
   320 }
   321 
   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];}
   327 
   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];}
   333 
   334 
   335 @end
   336 
   337 
   338 
   339 #pragma mark -
   340 #pragma mark TEST CASES:
   341 
   342 #if DEBUG
   343 
   344 
   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 
   350                                                                             error: &error];
   351     CAssertNil(error);
   352     CAssert(pcert != nil);
   353     
   354     CAssertEq(pcert.isRoot, selfSigned);
   355         
   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);
   365     
   366     // Now go through MYCertificate:
   367     MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
   368     CAssert(cert);
   369     CAssertEqual(cert.info, pcert);
   370     
   371     return pcert;
   372 }
   373 
   374 static MYCertificateInfo* testCert(NSString *filename, BOOL selfSigned) {
   375 #if TARGET_OS_IPHONE
   376     filename = [[NSBundle mainBundle] pathForResource: filename ofType: @"cer"];
   377 #else
   378     filename = [[@"../../Tests/" stringByAppendingPathComponent: filename]
   379                 stringByAppendingPathExtension: @"cer"];
   380 #endif
   381     Log(@"--- Creating MYCertificateInfo from %@", filename);
   382     return testCertData([NSData dataWithContentsOfFile: filename], selfSigned);
   383 }
   384 
   385 
   386 TestCase(ParsedCert) {
   387     testCert(@"selfsigned", YES);
   388     testCert(@"iphonedev", NO);
   389 }
   390 
   391 
   392 #import "MYCrypto_Private.h"
   393 
   394 TestCase(CreateCert) {
   395     MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512];
   396     CAssert(privateKey);
   397     MYIdentity *identity = nil;
   398     @try{
   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";
   406 
   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");
   413         
   414         Log(@"Signing...");
   415         NSError *error;
   416         NSData *certData = [pcert selfSignWithPrivateKey: privateKey error: &error];
   417         Log(@"Generated cert = \n%@", certData);
   418         CAssert(certData);
   419         CAssertNil(error);
   420         CAssert(certData);
   421 #if !TARGET_OS_IPHONE
   422         [certData writeToFile: @"../../Tests/generated.cer" atomically: YES];
   423 #endif
   424         MYCertificateInfo *pcert2 = testCertData(certData, YES);
   425         
   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");
   434         
   435         Log(@"Verifying Signature...");
   436         MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
   437         Log(@"Loaded %@", cert);
   438         CAssert(cert);
   439         MYPublicKey *certKey = cert.publicKey;
   440         Log(@"Its public key = %@", certKey);
   441         CAssertEqual(certKey.keyData, privateKey.publicKey.keyData);
   442         
   443         Log(@"Creating Identity...");
   444         identity = [pcert createSelfSignedIdentityWithPrivateKey: privateKey error: &error];
   445         Log(@"Identity = %@", identity);
   446         CAssert(identity);
   447         CAssertNil(error);
   448         CAssertEqual(identity.keychain, [MYKeychain defaultKeychain]);
   449         CAssertEqual(identity.privateKey, privateKey);
   450         CAssert([identity isEqualToCertificate: cert]);
   451         
   452         [pcert release];
   453         
   454     } @finally {
   455         [privateKey removeFromKeychain];
   456         [identity removeFromKeychain];
   457     }
   458 }
   459 
   460 #endif
   461 
   462 
   463 /*
   464  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   465  
   466  Redistribution and use in source and binary forms, with or without modification, are permitted
   467  provided that the following conditions are met:
   468  
   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
   473  distribution.
   474  
   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.
   483  */