MYCertificateInfo.m
author Jens Alfke <jens@mooseyard.com>
Tue Jul 21 10:13:08 2009 -0700 (2009-07-21)
changeset 27 d0aadddb9c64
parent 25 38c3c3923e1f
permissions -rw-r--r--
MYCertificate now checks validity of self-signed certs loaded from the keychain (because the Security framework doesn't validate self-signed certs.)
     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://tools.ietf.org/html/rfc3280> "RFC 3280: Internet X.509 Certificate Profile"
    11 // <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
    12 // <http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt> "X.509 Style Guide"
    13 // <http://en.wikipedia.org/wiki/X.509> Wikipedia article on X.509
    14 
    15 
    16 #import "MYCertificateInfo.h"
    17 #import "MYCrypto.h"
    18 #import "MYASN1Object.h"
    19 #import "MYOID.h"
    20 #import "MYBERParser.h"
    21 #import "MYDEREncoder.h"
    22 #import "MYErrorUtils.h"
    23 
    24 
    25 #define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)     /* that's 1 year */
    26 
    27 /*  X.509 version number to generate. Even though my code doesn't (yet) add any of the post-v1
    28     metadata, it's necessary to write v3 or the resulting certs won't be accepted on some platforms,
    29     notably iPhone OS.
    30     "This field is used mainly for marketing purposes to claim that software is X.509v3 compliant 
    31     (even when it isn't)." --Peter Gutmann */
    32 #define kCertRequestVersionNumber 3
    33 
    34 
    35 /* "Safe" NSArray accessor -- returns nil if out of range. */
    36 static id $atIf(NSArray *array, NSUInteger index) {
    37     return index < array.count ?[array objectAtIndex: index] :nil;
    38 }
    39 
    40 
    41 @interface MYCertificateName ()
    42 - (id) _initWithComponents: (NSArray*)components;
    43 @end
    44 
    45 @interface MYCertificateInfo ()
    46 @property (retain) NSArray *_root;
    47 @end
    48 
    49 
    50 #pragma mark -
    51 @implementation MYCertificateInfo
    52 
    53 
    54 static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
    55              *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
    56 
    57 
    58 + (void) initialize {
    59     if (!kEmailOID) {
    60         kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
    61                                                       count: 7];
    62         kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
    63                                                               count: 7];
    64         kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
    65                                                      count: 4];
    66         kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
    67                                                     count: 4];
    68         kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
    69                                                   count: 4];
    70         kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
    71                                                 count: 4];
    72         kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
    73                                                 count: 7];
    74     }
    75 }
    76 
    77 
    78 - (id) initWithRoot: (NSArray*)root
    79 {
    80     self = [super init];
    81     if (self != nil) {
    82         _root = [root retain];
    83     }
    84     return self;
    85 }
    86 
    87 + (NSString*) validate: (id)root {
    88     NSArray *top = $castIf(NSArray,root);
    89     if (top.count < 3)
    90         return @"Too few top-level components";
    91     NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]);
    92     if (info.count < 7)
    93         return @"Too few identity components";
    94     MYASN1Object *version = $castIf(MYASN1Object, [info objectAtIndex: 0]);
    95     if (!version || version.tag != 0)
    96         return @"Missing or invalid version";
    97     NSArray *versionComps = $castIf(NSArray, version.components);
    98     if (!versionComps || versionComps.count != 1)
    99         return @"Invalid version";
   100     NSNumber *versionNum = $castIf(NSNumber, [versionComps objectAtIndex: 0]);
   101     if (!versionNum || versionNum.intValue < 0 || versionNum.intValue > 2)
   102         return @"Unrecognized version number";
   103     return nil;
   104 }
   105 
   106 
   107 - (id) initWithCertificateData: (NSData*)data error: (NSError**)outError;
   108 {
   109     if (outError) *outError = nil;
   110     id root = MYBERParse(data,outError);
   111     NSString *errorMsg = [[self class] validate: root];
   112     if (errorMsg) {
   113         if (outError && !*outError)
   114             *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg);
   115         [self release];
   116         return nil;
   117     }
   118 
   119     self = [self initWithRoot: root];
   120     if (self) {
   121         _data = [data copy];
   122     }
   123     return self;
   124 }
   125 
   126 - (void) dealloc
   127 {
   128     [_root release];
   129     [_data release];
   130     [super dealloc];
   131 }
   132 
   133 - (BOOL) isEqual: (id)object {
   134     return [object isKindOfClass: [MYCertificateInfo class]]
   135         && [_root isEqual: ((MYCertificateInfo*)object)->_root];
   136 }
   137 
   138 - (NSArray*) _info       {return $castIf(NSArray,$atIf(_root,0));}
   139 
   140 - (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
   141 
   142 @synthesize _root;
   143 
   144 
   145 - (NSDate*) validFrom       {return $castIf(NSDate, $atIf(self._validDates, 0));}
   146 - (NSDate*) validTo         {return $castIf(NSDate, $atIf(self._validDates, 1));}
   147 
   148 - (MYCertificateName*) subject {
   149     return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease];
   150 }
   151 
   152 - (MYCertificateName*) issuer {
   153     return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease];
   154 }
   155 
   156 - (BOOL) isSigned           {return [_root count] >= 3;}
   157 
   158 - (BOOL) isRoot {
   159     id issuer = $atIf(self._info,3);
   160     return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
   161 }
   162 
   163 
   164 - (NSData*) subjectPublicKeyData {
   165     NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6));
   166     MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0));
   167     if (!$equal(keyAlgorithmID, kRSAAlgorithmID))
   168         return nil;
   169     return $cast(MYBitString, $atIf(keyInfo, 1)).bits;
   170 }
   171 
   172 - (MYPublicKey*) subjectPublicKey {
   173     NSData *keyData = self.subjectPublicKeyData;
   174     if (!keyData) return nil;
   175     return [[[MYPublicKey alloc] initWithKeyData: keyData] autorelease];
   176 }
   177 
   178 - (NSData*) signedData {
   179     if (!_data)
   180         return nil;
   181     // The root object is a sequence; we want to extract the 1st object of that sequence.
   182     const UInt8 *certStart = _data.bytes;
   183     const UInt8 *start = MYBERGetContents(_data, nil);
   184     if (!start) return nil;
   185     size_t length = MYBERGetLength([NSData dataWithBytesNoCopy: (void*)start
   186                                                         length: _data.length - (start-certStart)
   187                                                   freeWhenDone: NO],
   188                                    NULL);
   189     if (length==0)
   190         return nil;
   191     return [NSData dataWithBytes: start length: (start + length - certStart)];
   192 }
   193 
   194 - (MYOID*) signatureAlgorithmID {
   195     return $castIf(MYOID, $atIf($castIf(NSArray,$atIf(_root,1)), 0));
   196 }
   197 
   198 - (NSData*) signature {
   199     id signature = $atIf(_root,2);
   200     if ([signature isKindOfClass: [MYBitString class]])
   201         signature = [signature bits];
   202     return $castIf(NSData,signature);
   203 }
   204 
   205 - (BOOL) verifySignatureWithKey: (MYPublicKey*)issuerPublicKey {
   206     if (!$equal(self.signatureAlgorithmID, kRSAWithSHA1AlgorithmID))
   207         return NO;
   208     NSData *signedData = self.signedData;
   209     NSData *signature = self.signature;
   210     return signedData && signature && [issuerPublicKey verifySignature: signature ofData: signedData];
   211 }
   212 
   213 
   214 @end
   215 
   216 
   217 
   218 
   219 #pragma mark -
   220 @implementation MYCertificateRequest
   221 
   222 - (id) initWithPublicKey: (MYPublicKey*)publicKey {
   223     Assert(publicKey);
   224     id empty = [NSNull null];
   225     id version = [[MYASN1Object alloc] initWithTag: 0 
   226                                            ofClass: 2
   227                                         components: $array($object(kCertRequestVersionNumber - 1))];
   228     NSArray *root = $array( $marray(version,
   229                                     empty,       // serial #
   230                                     $array(kRSAAlgorithmID),
   231                                     $marray(),
   232                                     $marray(empty, empty),
   233                                     $marray(),
   234                                     $array( $array(kRSAAlgorithmID, empty),
   235                                            [MYBitString bitStringWithData: publicKey.keyData] ) ) );
   236     self = [super initWithRoot: root];
   237     [version release];
   238     if (self) {
   239         _publicKey = publicKey.retain;
   240     }
   241     return self;
   242 }
   243     
   244 - (void) dealloc
   245 {
   246     [_publicKey release];
   247     [super dealloc];
   248 }
   249 
   250 
   251 - (NSDate*) validFrom       {return [super validFrom];}
   252 - (NSDate*) validTo         {return [super validTo];}
   253 
   254 - (void) setValidFrom: (NSDate*)validFrom {
   255     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
   256 }
   257 
   258 - (void) setValidTo: (NSDate*)validTo {
   259     [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
   260 }
   261 
   262 
   263 - (void) fillInValues {
   264     NSMutableArray *info = (NSMutableArray*)self._info;
   265     // Set serial number if there isn't one yet:
   266     if (!$castIf(NSNumber, [info objectAtIndex: 1])) {
   267         UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000);
   268         [info replaceObjectAtIndex: 1 withObject: $object(serial)];
   269     }
   270     
   271     // Set up valid date range if there isn't one yet:
   272     NSDate *validFrom = self.validFrom;
   273     if (!validFrom)
   274         validFrom = self.validFrom = [NSDate date];
   275     NSDate *validTo = self.validTo;
   276     if (!validTo)
   277         self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; 
   278 }
   279 
   280 
   281 - (NSData*) requestData: (NSError**)outError {
   282     [self fillInValues];
   283     return [MYDEREncoder encodeRootObject: self._info error: outError];
   284 }
   285 
   286 
   287 - (NSData*) selfSignWithPrivateKey: (MYPrivateKey*)privateKey 
   288                              error: (NSError**)outError 
   289 {
   290     AssertEqual(privateKey.publicKey, _publicKey);  // Keys must form a pair
   291     
   292     // Copy subject to issuer:
   293     NSMutableArray *info = (NSMutableArray*)self._info;
   294     [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
   295     
   296     // Sign the request:
   297     NSData *dataToSign = [self requestData: outError];
   298     if (!dataToSign)
   299         return nil;
   300     MYBitString *signature = [MYBitString bitStringWithData: [privateKey signData: dataToSign]];
   301     
   302     // Generate and encode the certificate:
   303     NSArray *root = $array(info, 
   304                            $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
   305                            signature);
   306     return [MYDEREncoder encodeRootObject: root error: outError];
   307 }
   308 
   309 
   310 - (MYIdentity*) createSelfSignedIdentityWithPrivateKey: (MYPrivateKey*)privateKey
   311                                                  error: (NSError**)outError
   312 {
   313     Assert(privateKey.keychain!=nil);
   314     NSData *certData = [self selfSignWithPrivateKey: privateKey error: outError];
   315     if (!certData)
   316         return nil;
   317     MYCertificate *cert = [privateKey.keychain importCertificate: certData];
   318     Assert(cert!=nil);
   319     Assert(cert.keychain!=nil);
   320     AssertEqual(cert.publicKey.keyData, _publicKey.keyData);
   321     MYIdentity *identity = cert.identity;
   322     Assert(identity!=nil);
   323     return identity;
   324 }
   325 
   326 
   327 @end
   328 
   329 
   330 
   331 #pragma mark -
   332 @implementation MYCertificateName
   333 
   334 - (id) _initWithComponents: (NSArray*)components
   335 {
   336     self = [super init];
   337     if (self != nil) {
   338         _components = [components retain];
   339     }
   340     return self;
   341 }
   342 
   343 - (void) dealloc
   344 {
   345     [_components release];
   346     [super dealloc];
   347 }
   348 
   349 - (BOOL) isEqual: (id)object {
   350     return [object isKindOfClass: [MYCertificateName class]]
   351         && [_components isEqual: ((MYCertificateName*)object)->_components];
   352 }
   353 
   354 - (NSArray*) _pairForOID: (MYOID*)oid {
   355     for (id nameEntry in _components) {
   356         for (id pair in $castIf(NSSet,nameEntry)) {
   357             if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) {
   358                 if ($equal(oid, [pair objectAtIndex: 0]))
   359                     return pair;
   360             }
   361         }
   362     }
   363     return nil;
   364 }
   365 
   366 - (NSString*) stringForOID: (MYOID*)oid {
   367     return [[self _pairForOID: oid] objectAtIndex: 1];
   368 }
   369 
   370 - (void) setString: (NSString*)value forOID: (MYOID*)oid {
   371     NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
   372     if (pair) {
   373         if (value)
   374             [pair replaceObjectAtIndex: 1 withObject: value];
   375         else
   376             Assert(NO,@"-setString:forOID: removing strings is unimplemented");//FIX
   377     } else {
   378         if (value)
   379             [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
   380     }
   381 }
   382 
   383 - (NSString*) commonName    {return [self stringForOID: kCommonNameOID];}
   384 - (NSString*) givenName     {return [self stringForOID: kGivenNameOID];}
   385 - (NSString*) surname       {return [self stringForOID: kSurnameOID];}
   386 - (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];}
   387 - (NSString*) emailAddress  {return [self stringForOID: kEmailOID];}
   388 
   389 - (void) setCommonName: (NSString*)commonName   {[self setString: commonName forOID: kCommonNameOID];}
   390 - (void) setGivenName: (NSString*)givenName     {[self setString: givenName forOID: kGivenNameOID];}
   391 - (void) setSurname: (NSString*)surname         {[self setString: surname forOID: kSurnameOID];}
   392 - (void) setNameDescription: (NSString*)desc    {[self setString: desc forOID: kDescriptionOID];}
   393 - (void) setEmailAddress: (NSString*)email      {[self setString: email forOID: kEmailOID];}
   394 
   395 
   396 @end
   397 
   398 
   399 /*
   400  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   401  
   402  Redistribution and use in source and binary forms, with or without modification, are permitted
   403  provided that the following conditions are met:
   404  
   405  * Redistributions of source code must retain the above copyright notice, this list of conditions
   406  and the following disclaimer.
   407  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   408  and the following disclaimer in the documentation and/or other materials provided with the
   409  distribution.
   410  
   411  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   412  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   413  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   414  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   415  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   416   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   417  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   418  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   419  */