MYCertificateInfo.m
author Jens Alfke <jens@mooseyard.com>
Sat Jun 06 15:36:35 2009 -0700 (2009-06-06)
changeset 22 058394513f33
parent 20 df9da0f6b358
child 23 39fec79de6e8
permissions -rw-r--r--
Added a few comments. That is all.
     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     return self;
   183 }
   184 
   185 - (NSDate*) validFrom       {return [super validFrom];}
   186 - (NSDate*) validTo         {return [super validTo];}
   187 
   188 - (void) setValidFrom: (NSDate*)validFrom {
   189     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
   190 }
   191 
   192 - (void) setValidTo: (NSDate*)validTo {
   193     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
   194 }
   195 
   196 
   197 - (void) fillInValues {
   198     NSMutableArray *info = (NSMutableArray*)self._info;
   199     // Set serial number if there isn't one yet:
   200     if (!$castIf(NSNumber, [info objectAtIndex: 1])) {
   201         UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000);
   202         [info replaceObjectAtIndex: 1 withObject: $object(serial)];
   203     }
   204     
   205     // Set up valid date range if there isn't one yet:
   206     NSDate *validFrom = self.validFrom;
   207     if (!validFrom)
   208         validFrom = self.validFrom = [NSDate date];
   209     NSDate *validTo = self.validTo;
   210     if (!validTo)
   211         self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; 
   212 }
   213 
   214 
   215 - (NSData*) requestData: (NSError**)outError {
   216     [self fillInValues];
   217     return [MYDEREncoder encodeRootObject: self._info error: outError];
   218 }
   219 
   220 
   221 - (NSData*) selfSignWithPrivateKey: (MYPrivateKey*)privateKey 
   222                              error: (NSError**)outError 
   223 {
   224     AssertEqual(privateKey.publicKey, _publicKey);  // Keys must form a pair
   225     
   226     // Copy subject to issuer:
   227     NSMutableArray *info = (NSMutableArray*)self._info;
   228     [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
   229     
   230     // Sign the request:
   231     NSData *dataToSign = [self requestData: outError];
   232     if (!dataToSign)
   233         return nil;
   234     MYBitString *signature = [MYBitString bitStringWithData: [privateKey signData: dataToSign]];
   235     
   236     // Generate and encode the certificate:
   237     NSArray *root = $array(info, 
   238                            $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
   239                            signature);
   240     return [MYDEREncoder encodeRootObject: root error: outError];
   241 }
   242 
   243 
   244 - (MYIdentity*) createSelfSignedIdentityWithPrivateKey: (MYPrivateKey*)privateKey
   245                                                  error: (NSError**)outError
   246 {
   247     NSData *certData = [self selfSignWithPrivateKey: privateKey error: outError];
   248     if (!certData)
   249         return nil;
   250     MYCertificate *cert = [privateKey.keychain importCertificate: certData];
   251     Assert(cert!=nil);
   252     MYIdentity *identity = cert.identity;
   253     Assert(identity!=nil);
   254     return identity;
   255 }
   256 
   257 
   258 @end
   259 
   260 
   261 
   262 #pragma mark -
   263 @implementation MYCertificateName
   264 
   265 - (id) _initWithComponents: (NSArray*)components
   266 {
   267     self = [super init];
   268     if (self != nil) {
   269         _components = [components retain];
   270     }
   271     return self;
   272 }
   273 
   274 - (void) dealloc
   275 {
   276     [_components release];
   277     [super dealloc];
   278 }
   279 
   280 - (BOOL) isEqual: (id)object {
   281     return [object isKindOfClass: [MYCertificateName class]]
   282         && [_components isEqual: ((MYCertificateName*)object)->_components];
   283 }
   284 
   285 - (NSArray*) _pairForOID: (MYOID*)oid {
   286     for (id nameEntry in _components) {
   287         for (id pair in $castIf(NSSet,nameEntry)) {
   288             if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) {
   289                 if ($equal(oid, [pair objectAtIndex: 0]))
   290                     return pair;
   291             }
   292         }
   293     }
   294     return nil;
   295 }
   296 
   297 - (NSString*) stringForOID: (MYOID*)oid {
   298     return [[self _pairForOID: oid] objectAtIndex: 1];
   299 }
   300 
   301 - (void) setString: (NSString*)value forOID: (MYOID*)oid {
   302     NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
   303     if (pair)
   304         [pair replaceObjectAtIndex: 1 withObject: value];
   305     else
   306         [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
   307 }
   308 
   309 - (NSString*) commonName    {return [self stringForOID: kCommonNameOID];}
   310 - (NSString*) givenName     {return [self stringForOID: kGivenNameOID];}
   311 - (NSString*) surname       {return [self stringForOID: kSurnameOID];}
   312 - (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];}
   313 - (NSString*) emailAddress  {return [self stringForOID: kEmailOID];}
   314 
   315 - (void) setCommonName: (NSString*)commonName   {[self setString: commonName forOID: kCommonNameOID];}
   316 - (void) setGivenName: (NSString*)givenName     {[self setString: givenName forOID: kGivenNameOID];}
   317 - (void) setSurname: (NSString*)surname         {[self setString: surname forOID: kSurnameOID];}
   318 - (void) setNameDescription: (NSString*)desc    {[self setString: desc forOID: kDescriptionOID];}
   319 - (void) setEmailAddress: (NSString*)email      {[self setString: email forOID: kEmailOID];}
   320 
   321 
   322 @end
   323 
   324 
   325 
   326 #pragma mark -
   327 #pragma mark TEST CASES:
   328 
   329 #if DEBUG
   330 
   331 
   332 static MYCertificateInfo* testCert(NSString *filename, BOOL selfSigned) {
   333     Log(@"--- Creating MYCertificateInfo from %@", filename);
   334     NSData *certData = [NSData dataWithContentsOfFile: filename];
   335     //Log(@"Cert Data =\n%@", certData);
   336     NSError *error = nil;
   337     MYCertificateInfo *pcert = [[MYCertificateInfo alloc] initWithCertificateData: certData 
   338                                                                             error: &error];
   339     CAssertNil(error);
   340     CAssert(pcert != nil);
   341     
   342     CAssertEq(pcert.isRoot, selfSigned);
   343         
   344     Log(@"Subject Public Key = %@", pcert.subjectPublicKey);
   345     CAssert(pcert.subjectPublicKey);
   346     MYCertificateName *subject = pcert.subject;
   347     Log(@"Common Name = %@", subject.commonName);
   348     Log(@"Given Name  = %@", subject.givenName);
   349     Log(@"Surname     = %@", subject.surname);
   350     Log(@"Desc        = %@", subject.nameDescription);
   351     Log(@"Email       = %@", subject.emailAddress);
   352     CAssert(subject.commonName);
   353     
   354     // Now go through MYCertificate:
   355     MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
   356     CAssert(cert);
   357     CAssertEqual(cert.info, pcert);
   358     
   359     return pcert;
   360 }
   361 
   362 
   363 TestCase(ParsedCert) {
   364     testCert(@"../../Tests/selfsigned.cer", YES);
   365     testCert(@"../../Tests/iphonedev.cer", NO);
   366 }
   367 
   368 
   369 #import "MYCrypto_Private.h"
   370 
   371 TestCase(CreateCert) {
   372     MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512];
   373     CAssert(privateKey);
   374     MYIdentity *identity = nil;
   375     @try{
   376         MYCertificateRequest *pcert = [[MYCertificateRequest alloc] initWithPublicKey: privateKey.publicKey];
   377         MYCertificateName *subject = pcert.subject;
   378         subject.commonName = @"testcase";
   379         subject.givenName = @"Test";
   380         subject.surname = @"Case";
   381         subject.nameDescription = @"Just a test certificate created by MYCrypto";
   382         subject.emailAddress = @"testcase@example.com";
   383 
   384         subject = pcert.subject;
   385         CAssertEqual(subject.commonName, @"testcase");
   386         CAssertEqual(subject.givenName, @"Test");
   387         CAssertEqual(subject.surname, @"Case");
   388         CAssertEqual(subject.nameDescription, @"Just a test certificate created by MYCrypto");
   389         CAssertEqual(subject.emailAddress, @"testcase@example.com");
   390         
   391         Log(@"Signing...");
   392         NSError *error;
   393         NSData *certData = [pcert selfSignWithPrivateKey: privateKey error: &error];
   394         Log(@"Generated cert = \n%@", certData);
   395         CAssert(certData);
   396         CAssertNil(error);
   397         CAssert(certData);
   398         [certData writeToFile: @"../../Tests/generated.cer" atomically: YES];
   399         MYCertificateInfo *pcert2 = testCert(@"../../Tests/generated.cer", YES);
   400         
   401         Log(@"Verifying Info...");
   402         MYCertificateName *subject2 = pcert2.subject;
   403         CAssertEqual(subject2,subject);
   404         CAssertEqual(subject2.commonName, @"testcase");
   405         CAssertEqual(subject2.givenName, @"Test");
   406         CAssertEqual(subject2.surname, @"Case");
   407         CAssertEqual(subject2.nameDescription, @"Just a test certificate created by MYCrypto");
   408         CAssertEqual(subject2.emailAddress, @"testcase@example.com");
   409         
   410         Log(@"Verifying Signature...");
   411         MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
   412         Log(@"Loaded %@", cert);
   413         CAssert(cert);
   414         MYPublicKey *certKey = cert.publicKey;
   415         Log(@"Its public key = %@", certKey);
   416         CAssertEqual(certKey.keyData, privateKey.publicKey.keyData);
   417         
   418         Log(@"Creating Identity...");
   419         identity = [pcert createSelfSignedIdentityWithPrivateKey: privateKey error: &error];
   420         Log(@"Identity = %@", identity);
   421         CAssert(identity);
   422         CAssertNil(error);
   423         CAssertEqual(identity.keychain, [MYKeychain defaultKeychain]);
   424         CAssertEqual(identity.privateKey, privateKey);
   425         CAssert([identity isEqualToCertificate: cert]);
   426         
   427         [pcert release];
   428         
   429     } @finally {
   430         [privateKey removeFromKeychain];
   431         [identity removeFromKeychain];
   432     }
   433 }
   434 
   435 #endif
   436 
   437 
   438 /*
   439  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   440  
   441  Redistribution and use in source and binary forms, with or without modification, are permitted
   442  provided that the following conditions are met:
   443  
   444  * Redistributions of source code must retain the above copyright notice, this list of conditions
   445  and the following disclaimer.
   446  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   447  and the following disclaimer in the documentation and/or other materials provided with the
   448  distribution.
   449  
   450  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   451  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   452  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   453  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   454  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   455   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   456  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   457  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   458  */