MYParsedCertificate.m
changeset 19 f6c91b9da05b
parent 17 90a70925562b
child 20 df9da0f6b358
     1.1 --- a/MYParsedCertificate.m	Wed Jun 03 17:20:53 2009 -0700
     1.2 +++ b/MYParsedCertificate.m	Thu Jun 04 18:36:30 2009 -0700
     1.3 @@ -7,6 +7,7 @@
     1.4  //
     1.5  
     1.6  // References:
     1.7 +// <http://www.columbia.edu/~ariel/ssleay/layman.html>
     1.8  // <http://en.wikipedia.org/wiki/X.509>
     1.9  // <http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt>
    1.10  
    1.11 @@ -17,10 +18,14 @@
    1.12  #import "MYBERParser.h"
    1.13  #import "MYDEREncoder.h"
    1.14  #import "MYPublicKey.h"
    1.15 +#import "MYPrivateKey.h"
    1.16  #import "MYCertificate.h"
    1.17  #import "MYErrorUtils.h"
    1.18  
    1.19  
    1.20 +#define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)
    1.21 +
    1.22 +
    1.23  static id $atIf(NSArray *array, NSUInteger index) {
    1.24      return index < array.count ?[array objectAtIndex: index] :nil;
    1.25  }
    1.26 @@ -29,24 +34,30 @@
    1.27  @implementation MYParsedCertificate
    1.28  
    1.29  
    1.30 -static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID;
    1.31 +static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
    1.32 +            *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
    1.33  
    1.34  
    1.35  + (void) initialize {
    1.36 -    if (!kRSAAlgorithmID) {
    1.37 -        UInt32 components[7] = {1, 2, 840, 113549, 1, 1, 1,};
    1.38 -        kRSAAlgorithmID = [[MYOID alloc] initWithComponents: components count: 7];
    1.39 +    if (!kEmailOID) {
    1.40 +        kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
    1.41 +                                                      count: 7];
    1.42 +        kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
    1.43 +                                                              count: 7];
    1.44 +        kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
    1.45 +                                                     count: 4];
    1.46 +        kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
    1.47 +                                                    count: 4];
    1.48 +        kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
    1.49 +                                                  count: 4];
    1.50 +        kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
    1.51 +                                                count: 7];
    1.52 +        kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
    1.53 +                                                count: 7];
    1.54      }
    1.55 -    if (!kRSAWithSHA1AlgorithmID) {
    1.56 -        UInt32 components[7] = {1, 2, 840, 113549, 1, 1, 5};
    1.57 -        kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: components count: 7];
    1.58 -    }
    1.59 +    
    1.60  }
    1.61  
    1.62 -+ (MYOID*) RSAAlgorithmID           {return kRSAAlgorithmID;}
    1.63 -+ (MYOID*) RSAWithSHA1AlgorithmID   {return kRSAWithSHA1AlgorithmID;}
    1.64 -
    1.65 -
    1.66  + (NSString*) validate: (id)root {
    1.67      NSArray *top = $castIf(NSArray,root);
    1.68      if (top.count < 3)
    1.69 @@ -91,48 +102,71 @@
    1.70      
    1.71      [_root release];
    1.72      [_issuer release];
    1.73 +    [_data release];
    1.74      [super dealloc];
    1.75  }
    1.76  
    1.77  
    1.78 -@synthesize issuer=_issuer;
    1.79 +- (NSArray*) _info       {return $castIf(NSArray,$atIf(_root,0));}
    1.80  
    1.81 +- (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
    1.82  
    1.83 -- (NSArray*) info    {return $castIf(NSArray,$atIf(_root,0));}
    1.84 -
    1.85 -- (BOOL) isSelfSigned {
    1.86 -    id issuer  = $atIf(self.info,3);
    1.87 -    id subject = $atIf(self.info,5);
    1.88 -    return $equal(issuer,subject);
    1.89 +- (NSArray*) _pairForOID: (MYOID*)oid atInfoIndex: (unsigned)infoIndex {
    1.90 +    NSArray *names = $castIf(NSArray, $atIf(self._info, infoIndex));
    1.91 +    for (id nameEntry in names) {
    1.92 +        for (id pair in $castIf(NSSet,nameEntry)) {
    1.93 +            if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) {
    1.94 +                if ($equal(oid, [pair objectAtIndex: 0]))
    1.95 +                    return pair;
    1.96 +            }
    1.97 +        }
    1.98 +    }
    1.99 +    return nil;
   1.100  }
   1.101  
   1.102 +- (NSString*) _stringForOID: (MYOID*)oid atInfoIndex: (unsigned)infoIndex {
   1.103 +    return [[self _pairForOID: oid atInfoIndex: infoIndex] objectAtIndex: 1];
   1.104 +}
   1.105 +
   1.106 +
   1.107 +@synthesize issuer=_issuer, certificateData=_data;
   1.108 +
   1.109 +
   1.110 +- (NSDate*) validFrom       {return $castIf(NSDate, $atIf(self._validDates, 0));}
   1.111 +- (NSDate*) validTo         {return $castIf(NSDate, $atIf(self._validDates, 1));}
   1.112 +- (NSString*) commonName    {return [self _stringForOID: kCommonNameOID atInfoIndex: 5];}
   1.113 +- (NSString*) givenName     {return [self _stringForOID: kGivenNameOID atInfoIndex: 5];}
   1.114 +- (NSString*) surname       {return [self _stringForOID: kSurnameOID atInfoIndex: 5];}
   1.115 +- (NSString*) description   {return [self _stringForOID: kDescriptionOID atInfoIndex: 5];}
   1.116 +- (NSString*) emailAddress  {return [self _stringForOID: kEmailOID atInfoIndex: 5];}
   1.117 +
   1.118 +- (BOOL) isSigned           {return [_root count] >= 3;}
   1.119 +
   1.120 +- (BOOL) isRoot {
   1.121 +    id issuer = $atIf(self._info,3);
   1.122 +    return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
   1.123 +}
   1.124 +
   1.125 +
   1.126  - (MYPublicKey*) subjectPublicKey {
   1.127 -    NSArray *keyInfo = $cast(NSArray, $atIf(self.info, 6));
   1.128 +    NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6));
   1.129      MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0));
   1.130      if (!$equal(keyAlgorithmID, kRSAAlgorithmID))
   1.131          return nil;
   1.132      MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1));
   1.133      if (!keyData) return nil;
   1.134      return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease];
   1.135 -    /*
   1.136 -    NSArray *keyParts = $castIf(NSArray, MYBERParse(keyData, nil));
   1.137 -    if (!keyParts) return nil;
   1.138 -    MYBitString *modulus = $castIf(MYBitString, $atIf(keyParts,0));
   1.139 -    int exponent = [$castIf(NSNumber, $atIf(keyParts,1)) intValue];
   1.140 -    if (!modulus || exponent<3) return nil;
   1.141 -    */
   1.142  }
   1.143  
   1.144  - (MYPublicKey*) issuerPublicKey {
   1.145      if (_issuer)
   1.146          return _issuer.publicKey;
   1.147 -    else if (self.isSelfSigned)
   1.148 +    else if (self.isRoot)
   1.149          return self.subjectPublicKey;
   1.150      else
   1.151          return nil;
   1.152  }
   1.153  
   1.154 -
   1.155  - (NSData*) signedData {
   1.156      // The root object is a sequence; we want to extract the 1st object of that sequence.
   1.157      const UInt8 *certStart = _data.bytes;
   1.158 @@ -159,7 +193,7 @@
   1.159  }
   1.160  
   1.161  - (BOOL) validateSignature {
   1.162 -    if (!$equal(self.signatureAlgorithmID, [MYParsedCertificate RSAWithSHA1AlgorithmID]))
   1.163 +    if (!$equal(self.signatureAlgorithmID, kRSAWithSHA1AlgorithmID))
   1.164          return NO;
   1.165      NSData *signedData = self.signedData;
   1.166      NSData *signature = self.signature;
   1.167 @@ -169,45 +203,191 @@
   1.168  }
   1.169  
   1.170  
   1.171 +#pragma mark -
   1.172 +#pragma mark CERTIFICATE GENERATION:
   1.173 +
   1.174 +
   1.175 +- (id) initWithPublicKey: (MYPublicKey*)pubKey {
   1.176 +    Assert(pubKey);
   1.177 +    self = [super init];
   1.178 +    if (self != nil) {
   1.179 +        id empty = [NSNull null];
   1.180 +        id version = [[MYASN1Object alloc] initWithTag: 0 ofClass: 2 components: $array($object(0))];
   1.181 +        _root = $array( $marray(version,
   1.182 +                                empty,       // serial #
   1.183 +                                $array(kRSAAlgorithmID),
   1.184 +                                $marray(),
   1.185 +                                $marray(empty, empty),
   1.186 +                                $marray(),
   1.187 +                                $array( $array(kRSAAlgorithmID, empty),
   1.188 +                                       [MYBitString bitStringWithData: pubKey.keyData] ) ) );
   1.189 +        [version release];
   1.190 +        [_root retain];
   1.191 +    }
   1.192 +    return self;
   1.193 +}
   1.194 +
   1.195 +
   1.196 +- (void) _setString: (NSString*)value forOID: (MYOID*)oid atInfoIndex: (unsigned)infoIndex {
   1.197 +    NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid atInfoIndex: infoIndex];
   1.198 +    if (pair) {
   1.199 +        [pair replaceObjectAtIndex: 1 withObject: value];
   1.200 +    } else {
   1.201 +        NSMutableArray *names = $castIf(NSMutableArray, $atIf(self._info, infoIndex));
   1.202 +        [names addObject: [NSSet setWithObject: $marray(oid,value)]];
   1.203 +    }
   1.204 +}
   1.205 +
   1.206 +
   1.207 +- (void) setValidFrom: (NSDate*)validFrom {
   1.208 +    [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
   1.209 +}
   1.210 +
   1.211 +- (void) setValidTo: (NSDate*)validTo {
   1.212 +    [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
   1.213 +}
   1.214 +
   1.215 +- (void) setCommonName: (NSString*)commonName {
   1.216 +    [self _setString: commonName forOID: kCommonNameOID atInfoIndex: 5];
   1.217 +}
   1.218 +
   1.219 +- (void) setGivenName: (NSString*)givenName {
   1.220 +    [self _setString: givenName forOID: kGivenNameOID atInfoIndex: 5];
   1.221 +}
   1.222 +
   1.223 +- (void) setSurname: (NSString*)surname {
   1.224 +    [self _setString: surname forOID: kSurnameOID atInfoIndex: 5];
   1.225 +}
   1.226 +
   1.227 +- (void) setDescription: (NSString*)description {
   1.228 +    [self _setString: description forOID: kDescriptionOID atInfoIndex: 5];
   1.229 +}
   1.230 +
   1.231 +- (void) setEmailAddress: (NSString*)emailAddress {
   1.232 +    [self _setString: emailAddress forOID: kEmailOID atInfoIndex: 5];
   1.233 +}
   1.234 +
   1.235 +
   1.236 +- (BOOL) selfSignWithPrivateKey: (MYPrivateKey*)privateKey error: (NSError**)outError {
   1.237 +    // Copy subject to issuer:
   1.238 +    NSMutableArray *info = (NSMutableArray*)self._info;
   1.239 +    [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
   1.240 +    
   1.241 +    // Set serial number if there isn't one yet:
   1.242 +    if (!$castIf(NSNumber, [info objectAtIndex: 1])) {
   1.243 +        UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000);
   1.244 +        [info replaceObjectAtIndex: 1 withObject: $object(serial)];
   1.245 +    }
   1.246 +    
   1.247 +    // Set up valid date range if there isn't one yet:
   1.248 +    NSDate *validFrom = self.validFrom;
   1.249 +    if (!validFrom)
   1.250 +        validFrom = self.validFrom = [NSDate date];
   1.251 +    NSDate *validTo = self.validTo;
   1.252 +    if (!validTo)
   1.253 +        self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; 
   1.254 +    
   1.255 +    // Append signature to cert structure:
   1.256 +    NSData *dataToSign = [MYDEREncoder encodeRootObject: info error: outError];
   1.257 +    if (!dataToSign)
   1.258 +        return NO;
   1.259 +    setObj(&_root, $array(info, 
   1.260 +                          $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
   1.261 +                          [MYBitString bitStringWithData: [privateKey signData: dataToSign]]));
   1.262 +    
   1.263 +    setObj(&_data, [MYDEREncoder encodeRootObject: _root error: outError]);
   1.264 +    return _data!=nil;
   1.265 +}
   1.266 +
   1.267 +
   1.268  @end
   1.269  
   1.270  
   1.271  
   1.272  
   1.273 +#if DEBUG
   1.274 +
   1.275 +
   1.276 +static MYParsedCertificate* testCert(NSString *filename, BOOL selfSigned) {
   1.277 +    Log(@"--- Creating MYParsedCertificate from %@", filename);
   1.278 +    NSData *certData = [NSData dataWithContentsOfFile: filename];
   1.279 +    //Log(@"Cert Data =\n%@", certData);
   1.280 +    NSError *error = nil;
   1.281 +    MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithCertificateData: certData 
   1.282 +                                                                                error: &error];
   1.283 +    CAssertNil(error);
   1.284 +    CAssert(pcert != nil);
   1.285 +    
   1.286 +    CAssertEq(pcert.isRoot, selfSigned);
   1.287 +    
   1.288 +    NSData *signedData = pcert.signedData;
   1.289 +    //Log(@"Signed Data = (length=%x)\n%@", signedData.length, signedData);
   1.290 +    CAssertEqual(signedData, [certData subdataWithRange: NSMakeRange(4,signedData.length)]);
   1.291 +    
   1.292 +    Log(@"AlgID = %@", pcert.signatureAlgorithmID);
   1.293 +    Log(@"Signature = %@", pcert.signature);
   1.294 +    CAssertEqual(pcert.signatureAlgorithmID, kRSAWithSHA1AlgorithmID);
   1.295 +    CAssert(pcert.signature != nil);
   1.296 +    Log(@"Subject Public Key = %@", pcert.subjectPublicKey);
   1.297 +    CAssert(pcert.subjectPublicKey);
   1.298 +    if (selfSigned) {
   1.299 +        Log(@"Issuer Public Key = %@", pcert.issuerPublicKey);
   1.300 +        CAssert(pcert.issuerPublicKey);
   1.301 +        
   1.302 +        CAssert(pcert.validateSignature);
   1.303 +    }
   1.304 +    Log(@"Common Name = %@", pcert.commonName);
   1.305 +    Log(@"Given Name  = %@", pcert.givenName);
   1.306 +    Log(@"Surname     = %@", pcert.surname);
   1.307 +    Log(@"Desc        = %@", pcert.description);
   1.308 +    Log(@"Email       = %@", pcert.emailAddress);
   1.309 +    return pcert;
   1.310 +}
   1.311 +
   1.312 +
   1.313  TestCase(ParsedCert) {
   1.314 -    auto void testCert(NSString *filename, BOOL selfSigned);
   1.315      testCert(@"../../Tests/selfsigned.cer", YES);
   1.316      testCert(@"../../Tests/iphonedev.cer", NO);
   1.317 -    auto void testCert(NSString *filename, BOOL selfSigned) {
   1.318 -        Log(@"--- Creating MYParsedCertificate from %@", filename);
   1.319 -        NSData *certData = [NSData dataWithContentsOfFile: filename];
   1.320 -        //Log(@"Cert Data =\n%@", certData);
   1.321 -        NSError *error = nil;
   1.322 -        MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithCertificateData: certData 
   1.323 -                                                                                    error: &error];
   1.324 -        CAssertNil(error);
   1.325 -        CAssert(pcert != nil);
   1.326 -        
   1.327 -        CAssertEq(pcert.isSelfSigned, selfSigned);
   1.328 -        
   1.329 -        NSData *signedData = pcert.signedData;
   1.330 -        //Log(@"Signed Data = (length=%x)\n%@", signedData.length, signedData);
   1.331 -        CAssertEqual(signedData, [certData subdataWithRange: NSMakeRange(4,signedData.length)]);
   1.332 -        
   1.333 -        Log(@"AlgID = %@", pcert.signatureAlgorithmID);
   1.334 -        Log(@"Signature = %@", pcert.signature);
   1.335 -        CAssertEqual(pcert.signatureAlgorithmID, [MYParsedCertificate RSAWithSHA1AlgorithmID]);
   1.336 -        CAssert(pcert.signature != nil);
   1.337 -        Log(@"Subject Public Key = %@", pcert.subjectPublicKey);
   1.338 -        CAssert(pcert.subjectPublicKey);
   1.339 -        if (selfSigned) {
   1.340 -            Log(@"Issuer Public Key = %@", pcert.issuerPublicKey);
   1.341 -            CAssert(pcert.issuerPublicKey);
   1.342 -            
   1.343 -            CAssert(pcert.validateSignature);
   1.344 -        }
   1.345 -    }
   1.346 -}    
   1.347 +}
   1.348 +
   1.349 +
   1.350 +#import "MYKeychain.h"
   1.351 +
   1.352 +TestCase(CreateCert) {
   1.353 +    MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512];
   1.354 +    MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithPublicKey: privateKey.publicKey];
   1.355 +    pcert.commonName = @"testcase";
   1.356 +    pcert.givenName = @"Test";
   1.357 +    pcert.surname = @"Case";
   1.358 +    pcert.description = @"Just a test certificate created by MYCrypto";
   1.359 +    pcert.emailAddress = @"testcase@example.com";
   1.360 +
   1.361 +    CAssertEqual(pcert.commonName, @"testcase");
   1.362 +    CAssertEqual(pcert.givenName, @"Test");
   1.363 +    CAssertEqual(pcert.surname, @"Case");
   1.364 +    CAssertEqual(pcert.description, @"Just a test certificate created by MYCrypto");
   1.365 +    CAssertEqual(pcert.emailAddress, @"testcase@example.com");
   1.366 +    
   1.367 +    Log(@"Signing...");
   1.368 +    NSError *error;
   1.369 +    CAssert([pcert selfSignWithPrivateKey: privateKey error: &error]);
   1.370 +    CAssertNil(error);
   1.371 +    NSData *certData = pcert.certificateData;
   1.372 +    Log(@"Generated cert = \n%@", certData);
   1.373 +    CAssert(certData);
   1.374 +    [certData writeToFile: @"../../Tests/generated.cer" atomically: YES];
   1.375 +    MYParsedCertificate *pcert2 = testCert(@"../../Tests/generated.cer", YES);
   1.376 +    
   1.377 +    Log(@"Verifying...");
   1.378 +    CAssertEqual(pcert2.commonName, @"testcase");
   1.379 +    CAssertEqual(pcert2.givenName, @"Test");
   1.380 +    CAssertEqual(pcert2.surname, @"Case");
   1.381 +    CAssertEqual(pcert2.description, @"Just a test certificate created by MYCrypto");
   1.382 +    CAssertEqual(pcert2.emailAddress, @"testcase@example.com");
   1.383 +}
   1.384 +
   1.385 +#endif
   1.386 +
   1.387  
   1.388  
   1.389  
   1.390 @@ -215,8 +395,8 @@
   1.391   
   1.392  Sequence:                           <-- top
   1.393      Sequence:                       <-- info
   1.394 -        MYASN1Object[2/0]:          <-- version (int, constructed)
   1.395 -            2
   1.396 +        MYASN1Object[2/0]:          <-- version (tag=0, constructed)
   1.397 +            2                       
   1.398          1                           <-- serial number
   1.399          Sequence:
   1.400              {1 2 840 113549 1 1 1}  <-- algorithm ID
   1.401 @@ -246,23 +426,23 @@
   1.402              2010-04-13 21:54:35 -0700
   1.403          Sequence:                       <-- subject
   1.404              Set:
   1.405 -                Sequence:
   1.406 +                Sequence:                   <-- surname
   1.407                      {2 5 4 4}
   1.408                      Widdershins
   1.409              Set:
   1.410 -                Sequence:
   1.411 +                Sequence:                   <-- email
   1.412                      {1 2 840 113549 1 9 1}
   1.413                      waldo@example.com
   1.414              Set:
   1.415 -                Sequence:
   1.416 +                Sequence:                   <-- common name
   1.417                      {2 5 4 3}
   1.418                      waldo
   1.419              Set:
   1.420 -                Sequence:
   1.421 +                Sequence:                   <-- first name
   1.422                      {2 5 4 42}
   1.423                      Waldo
   1.424              Set:
   1.425 -                Sequence:
   1.426 +                Sequence:                   <-- description
   1.427                      {2 5 4 13}
   1.428                      Just a fictitious person
   1.429          Sequence:                               <-- public key info