MYCertificateInfo.m
changeset 21 2c300b15b381
parent 20 df9da0f6b358
child 23 39fec79de6e8
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/MYCertificateInfo.m	Sat Jun 06 15:01:28 2009 -0700
     1.3 @@ -0,0 +1,458 @@
     1.4 +//
     1.5 +//  MYCertificateInfo.m
     1.6 +//  MYCrypto
     1.7 +//
     1.8 +//  Created by Jens Alfke on 6/2/09.
     1.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    1.10 +//
    1.11 +
    1.12 +// References:
    1.13 +// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
    1.14 +// <http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt> "X.509 Style Guide"
    1.15 +// <http://en.wikipedia.org/wiki/X.509> Wikipedia article on X.509
    1.16 +
    1.17 +
    1.18 +#import "MYCertificateInfo.h"
    1.19 +#import "MYCrypto.h"
    1.20 +#import "MYASN1Object.h"
    1.21 +#import "MYOID.h"
    1.22 +#import "MYBERParser.h"
    1.23 +#import "MYDEREncoder.h"
    1.24 +#import "MYErrorUtils.h"
    1.25 +
    1.26 +
    1.27 +#define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)
    1.28 +
    1.29 +
    1.30 +static id $atIf(NSArray *array, NSUInteger index) {
    1.31 +    return index < array.count ?[array objectAtIndex: index] :nil;
    1.32 +}
    1.33 +
    1.34 +
    1.35 +@interface MYCertificateName ()
    1.36 +- (id) _initWithComponents: (NSArray*)components;
    1.37 +@end
    1.38 +
    1.39 +@interface MYCertificateInfo ()
    1.40 +@property (retain) NSArray *_root;
    1.41 +@end
    1.42 +
    1.43 +
    1.44 +#pragma mark -
    1.45 +@implementation MYCertificateInfo
    1.46 +
    1.47 +
    1.48 +static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
    1.49 +            *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
    1.50 +
    1.51 +
    1.52 ++ (void) initialize {
    1.53 +    if (!kEmailOID) {
    1.54 +        kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
    1.55 +                                                      count: 7];
    1.56 +        kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
    1.57 +                                                              count: 7];
    1.58 +        kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
    1.59 +                                                     count: 4];
    1.60 +        kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
    1.61 +                                                    count: 4];
    1.62 +        kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
    1.63 +                                                  count: 4];
    1.64 +        kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
    1.65 +                                                count: 7];
    1.66 +        kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
    1.67 +                                                count: 7];
    1.68 +    }
    1.69 +}
    1.70 +
    1.71 +
    1.72 +- (id) initWithRoot: (NSArray*)root
    1.73 +{
    1.74 +    self = [super init];
    1.75 +    if (self != nil) {
    1.76 +        _root = [root retain];
    1.77 +    }
    1.78 +    return self;
    1.79 +}
    1.80 +
    1.81 ++ (NSString*) validate: (id)root {
    1.82 +    NSArray *top = $castIf(NSArray,root);
    1.83 +    if (top.count < 3)
    1.84 +        return @"Too few top-level components";
    1.85 +    NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]);
    1.86 +    if (info.count < 7)
    1.87 +        return @"Too few identity components";
    1.88 +    MYASN1Object *version = $castIf(MYASN1Object, [info objectAtIndex: 0]);
    1.89 +    if (!version || version.tag != 0)
    1.90 +        return @"Missing or invalid version";
    1.91 +    NSArray *versionComps = $castIf(NSArray, version.components);
    1.92 +    if (!versionComps || versionComps.count != 1)
    1.93 +        return @"Invalid version";
    1.94 +    NSNumber *versionNum = $castIf(NSNumber, [versionComps objectAtIndex: 0]);
    1.95 +    if (!versionNum || versionNum.intValue < 0 || versionNum.intValue > 2)
    1.96 +        return @"Unrecognized version number";
    1.97 +    return nil;
    1.98 +}
    1.99 +
   1.100 +
   1.101 +- (id) initWithCertificateData: (NSData*)data error: (NSError**)outError;
   1.102 +{
   1.103 +    if (outError) *outError = nil;
   1.104 +    id root = MYBERParse(data,outError);
   1.105 +    NSString *errorMsg = [[self class] validate: root];
   1.106 +    if (errorMsg) {
   1.107 +        if (outError && !*outError)
   1.108 +            *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg);
   1.109 +        [self release];
   1.110 +        return nil;
   1.111 +    }
   1.112 +
   1.113 +    return [self initWithRoot: root];
   1.114 +}
   1.115 +
   1.116 +- (void) dealloc
   1.117 +{
   1.118 +    [_root release];
   1.119 +    [super dealloc];
   1.120 +}
   1.121 +
   1.122 +- (BOOL) isEqual: (id)object {
   1.123 +    return [object isKindOfClass: [MYCertificateInfo class]]
   1.124 +        && [_root isEqual: ((MYCertificateInfo*)object)->_root];
   1.125 +}
   1.126 +
   1.127 +- (NSArray*) _info       {return $castIf(NSArray,$atIf(_root,0));}
   1.128 +
   1.129 +- (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
   1.130 +
   1.131 +@synthesize _root;
   1.132 +
   1.133 +
   1.134 +- (NSDate*) validFrom       {return $castIf(NSDate, $atIf(self._validDates, 0));}
   1.135 +- (NSDate*) validTo         {return $castIf(NSDate, $atIf(self._validDates, 1));}
   1.136 +
   1.137 +- (MYCertificateName*) subject {
   1.138 +    return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease];
   1.139 +}
   1.140 +
   1.141 +- (MYCertificateName*) issuer {
   1.142 +    return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease];
   1.143 +}
   1.144 +
   1.145 +- (BOOL) isSigned           {return [_root count] >= 3;}
   1.146 +
   1.147 +- (BOOL) isRoot {
   1.148 +    id issuer = $atIf(self._info,3);
   1.149 +    return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
   1.150 +}
   1.151 +
   1.152 +
   1.153 +- (MYPublicKey*) subjectPublicKey {
   1.154 +    NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6));
   1.155 +    MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0));
   1.156 +    if (!$equal(keyAlgorithmID, kRSAAlgorithmID))
   1.157 +        return nil;
   1.158 +    MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1));
   1.159 +    if (!keyData) return nil;
   1.160 +    return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease];
   1.161 +}
   1.162 +
   1.163 +@end
   1.164 +
   1.165 +
   1.166 +
   1.167 +
   1.168 +#pragma mark -
   1.169 +@implementation MYCertificateRequest
   1.170 +
   1.171 +- (id) initWithPublicKey: (MYPublicKey*)publicKey {
   1.172 +    Assert(publicKey);
   1.173 +    id empty = [NSNull null];
   1.174 +    id version = [[MYASN1Object alloc] initWithTag: 0 ofClass: 2 components: $array($object(0))];
   1.175 +    NSArray *root = $array( $marray(version,
   1.176 +                                    empty,       // serial #
   1.177 +                                    $array(kRSAAlgorithmID),
   1.178 +                                    $marray(),
   1.179 +                                    $marray(empty, empty),
   1.180 +                                    $marray(),
   1.181 +                                    $array( $array(kRSAAlgorithmID, empty),
   1.182 +                                           [MYBitString bitStringWithData: publicKey.keyData] ) ) );
   1.183 +    self = [super initWithRoot: root];
   1.184 +    [version release];
   1.185 +    return self;
   1.186 +}
   1.187 +
   1.188 +- (NSDate*) validFrom       {return [super validFrom];}
   1.189 +- (NSDate*) validTo         {return [super validTo];}
   1.190 +
   1.191 +- (void) setValidFrom: (NSDate*)validFrom {
   1.192 +    [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
   1.193 +}
   1.194 +
   1.195 +- (void) setValidTo: (NSDate*)validTo {
   1.196 +    [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
   1.197 +}
   1.198 +
   1.199 +
   1.200 +- (void) fillInValues {
   1.201 +    NSMutableArray *info = (NSMutableArray*)self._info;
   1.202 +    // Set serial number if there isn't one yet:
   1.203 +    if (!$castIf(NSNumber, [info objectAtIndex: 1])) {
   1.204 +        UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000);
   1.205 +        [info replaceObjectAtIndex: 1 withObject: $object(serial)];
   1.206 +    }
   1.207 +    
   1.208 +    // Set up valid date range if there isn't one yet:
   1.209 +    NSDate *validFrom = self.validFrom;
   1.210 +    if (!validFrom)
   1.211 +        validFrom = self.validFrom = [NSDate date];
   1.212 +    NSDate *validTo = self.validTo;
   1.213 +    if (!validTo)
   1.214 +        self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; 
   1.215 +}
   1.216 +
   1.217 +
   1.218 +- (NSData*) requestData: (NSError**)outError {
   1.219 +    [self fillInValues];
   1.220 +    return [MYDEREncoder encodeRootObject: self._info error: outError];
   1.221 +}
   1.222 +
   1.223 +
   1.224 +- (NSData*) selfSignWithPrivateKey: (MYPrivateKey*)privateKey 
   1.225 +                             error: (NSError**)outError 
   1.226 +{
   1.227 +    AssertEqual(privateKey.publicKey, _publicKey);  // Keys must form a pair
   1.228 +    
   1.229 +    // Copy subject to issuer:
   1.230 +    NSMutableArray *info = (NSMutableArray*)self._info;
   1.231 +    [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
   1.232 +    
   1.233 +    // Sign the request:
   1.234 +    NSData *dataToSign = [self requestData: outError];
   1.235 +    if (!dataToSign)
   1.236 +        return nil;
   1.237 +    MYBitString *signature = [MYBitString bitStringWithData: [privateKey signData: dataToSign]];
   1.238 +    
   1.239 +    // Generate and encode the certificate:
   1.240 +    NSArray *root = $array(info, 
   1.241 +                           $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
   1.242 +                           signature);
   1.243 +    return [MYDEREncoder encodeRootObject: root error: outError];
   1.244 +}
   1.245 +
   1.246 +
   1.247 +- (MYIdentity*) createSelfSignedIdentityWithPrivateKey: (MYPrivateKey*)privateKey
   1.248 +                                                 error: (NSError**)outError
   1.249 +{
   1.250 +    NSData *certData = [self selfSignWithPrivateKey: privateKey error: outError];
   1.251 +    if (!certData)
   1.252 +        return nil;
   1.253 +    MYCertificate *cert = [privateKey.keychain importCertificate: certData];
   1.254 +    Assert(cert!=nil);
   1.255 +    MYIdentity *identity = cert.identity;
   1.256 +    Assert(identity!=nil);
   1.257 +    return identity;
   1.258 +}
   1.259 +
   1.260 +
   1.261 +@end
   1.262 +
   1.263 +
   1.264 +
   1.265 +#pragma mark -
   1.266 +@implementation MYCertificateName
   1.267 +
   1.268 +- (id) _initWithComponents: (NSArray*)components
   1.269 +{
   1.270 +    self = [super init];
   1.271 +    if (self != nil) {
   1.272 +        _components = [components retain];
   1.273 +    }
   1.274 +    return self;
   1.275 +}
   1.276 +
   1.277 +- (void) dealloc
   1.278 +{
   1.279 +    [_components release];
   1.280 +    [super dealloc];
   1.281 +}
   1.282 +
   1.283 +- (BOOL) isEqual: (id)object {
   1.284 +    return [object isKindOfClass: [MYCertificateName class]]
   1.285 +        && [_components isEqual: ((MYCertificateName*)object)->_components];
   1.286 +}
   1.287 +
   1.288 +- (NSArray*) _pairForOID: (MYOID*)oid {
   1.289 +    for (id nameEntry in _components) {
   1.290 +        for (id pair in $castIf(NSSet,nameEntry)) {
   1.291 +            if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) {
   1.292 +                if ($equal(oid, [pair objectAtIndex: 0]))
   1.293 +                    return pair;
   1.294 +            }
   1.295 +        }
   1.296 +    }
   1.297 +    return nil;
   1.298 +}
   1.299 +
   1.300 +- (NSString*) stringForOID: (MYOID*)oid {
   1.301 +    return [[self _pairForOID: oid] objectAtIndex: 1];
   1.302 +}
   1.303 +
   1.304 +- (void) setString: (NSString*)value forOID: (MYOID*)oid {
   1.305 +    NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
   1.306 +    if (pair)
   1.307 +        [pair replaceObjectAtIndex: 1 withObject: value];
   1.308 +    else
   1.309 +        [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
   1.310 +}
   1.311 +
   1.312 +- (NSString*) commonName    {return [self stringForOID: kCommonNameOID];}
   1.313 +- (NSString*) givenName     {return [self stringForOID: kGivenNameOID];}
   1.314 +- (NSString*) surname       {return [self stringForOID: kSurnameOID];}
   1.315 +- (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];}
   1.316 +- (NSString*) emailAddress  {return [self stringForOID: kEmailOID];}
   1.317 +
   1.318 +- (void) setCommonName: (NSString*)commonName   {[self setString: commonName forOID: kCommonNameOID];}
   1.319 +- (void) setGivenName: (NSString*)givenName     {[self setString: givenName forOID: kGivenNameOID];}
   1.320 +- (void) setSurname: (NSString*)surname         {[self setString: surname forOID: kSurnameOID];}
   1.321 +- (void) setNameDescription: (NSString*)desc    {[self setString: desc forOID: kDescriptionOID];}
   1.322 +- (void) setEmailAddress: (NSString*)email      {[self setString: email forOID: kEmailOID];}
   1.323 +
   1.324 +
   1.325 +@end
   1.326 +
   1.327 +
   1.328 +
   1.329 +#pragma mark -
   1.330 +#pragma mark TEST CASES:
   1.331 +
   1.332 +#if DEBUG
   1.333 +
   1.334 +
   1.335 +static MYCertificateInfo* testCert(NSString *filename, BOOL selfSigned) {
   1.336 +    Log(@"--- Creating MYCertificateInfo from %@", filename);
   1.337 +    NSData *certData = [NSData dataWithContentsOfFile: filename];
   1.338 +    //Log(@"Cert Data =\n%@", certData);
   1.339 +    NSError *error = nil;
   1.340 +    MYCertificateInfo *pcert = [[MYCertificateInfo alloc] initWithCertificateData: certData 
   1.341 +                                                                            error: &error];
   1.342 +    CAssertNil(error);
   1.343 +    CAssert(pcert != nil);
   1.344 +    
   1.345 +    CAssertEq(pcert.isRoot, selfSigned);
   1.346 +        
   1.347 +    Log(@"Subject Public Key = %@", pcert.subjectPublicKey);
   1.348 +    CAssert(pcert.subjectPublicKey);
   1.349 +    MYCertificateName *subject = pcert.subject;
   1.350 +    Log(@"Common Name = %@", subject.commonName);
   1.351 +    Log(@"Given Name  = %@", subject.givenName);
   1.352 +    Log(@"Surname     = %@", subject.surname);
   1.353 +    Log(@"Desc        = %@", subject.nameDescription);
   1.354 +    Log(@"Email       = %@", subject.emailAddress);
   1.355 +    CAssert(subject.commonName);
   1.356 +    
   1.357 +    // Now go through MYCertificate:
   1.358 +    MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
   1.359 +    CAssert(cert);
   1.360 +    CAssertEqual(cert.info, pcert);
   1.361 +    
   1.362 +    return pcert;
   1.363 +}
   1.364 +
   1.365 +
   1.366 +TestCase(ParsedCert) {
   1.367 +    testCert(@"../../Tests/selfsigned.cer", YES);
   1.368 +    testCert(@"../../Tests/iphonedev.cer", NO);
   1.369 +}
   1.370 +
   1.371 +
   1.372 +#import "MYCrypto_Private.h"
   1.373 +
   1.374 +TestCase(CreateCert) {
   1.375 +    MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512];
   1.376 +    CAssert(privateKey);
   1.377 +    MYIdentity *identity = nil;
   1.378 +    @try{
   1.379 +        MYCertificateRequest *pcert = [[MYCertificateRequest alloc] initWithPublicKey: privateKey.publicKey];
   1.380 +        MYCertificateName *subject = pcert.subject;
   1.381 +        subject.commonName = @"testcase";
   1.382 +        subject.givenName = @"Test";
   1.383 +        subject.surname = @"Case";
   1.384 +        subject.nameDescription = @"Just a test certificate created by MYCrypto";
   1.385 +        subject.emailAddress = @"testcase@example.com";
   1.386 +
   1.387 +        subject = pcert.subject;
   1.388 +        CAssertEqual(subject.commonName, @"testcase");
   1.389 +        CAssertEqual(subject.givenName, @"Test");
   1.390 +        CAssertEqual(subject.surname, @"Case");
   1.391 +        CAssertEqual(subject.nameDescription, @"Just a test certificate created by MYCrypto");
   1.392 +        CAssertEqual(subject.emailAddress, @"testcase@example.com");
   1.393 +        
   1.394 +        Log(@"Signing...");
   1.395 +        NSError *error;
   1.396 +        NSData *certData = [pcert selfSignWithPrivateKey: privateKey error: &error];
   1.397 +        Log(@"Generated cert = \n%@", certData);
   1.398 +        CAssert(certData);
   1.399 +        CAssertNil(error);
   1.400 +        CAssert(certData);
   1.401 +        [certData writeToFile: @"../../Tests/generated.cer" atomically: YES];
   1.402 +        MYCertificateInfo *pcert2 = testCert(@"../../Tests/generated.cer", YES);
   1.403 +        
   1.404 +        Log(@"Verifying Info...");
   1.405 +        MYCertificateName *subject2 = pcert2.subject;
   1.406 +        CAssertEqual(subject2,subject);
   1.407 +        CAssertEqual(subject2.commonName, @"testcase");
   1.408 +        CAssertEqual(subject2.givenName, @"Test");
   1.409 +        CAssertEqual(subject2.surname, @"Case");
   1.410 +        CAssertEqual(subject2.nameDescription, @"Just a test certificate created by MYCrypto");
   1.411 +        CAssertEqual(subject2.emailAddress, @"testcase@example.com");
   1.412 +        
   1.413 +        Log(@"Verifying Signature...");
   1.414 +        MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData];
   1.415 +        Log(@"Loaded %@", cert);
   1.416 +        CAssert(cert);
   1.417 +        MYPublicKey *certKey = cert.publicKey;
   1.418 +        Log(@"Its public key = %@", certKey);
   1.419 +        CAssertEqual(certKey.keyData, privateKey.publicKey.keyData);
   1.420 +        
   1.421 +        Log(@"Creating Identity...");
   1.422 +        identity = [pcert createSelfSignedIdentityWithPrivateKey: privateKey error: &error];
   1.423 +        Log(@"Identity = %@", identity);
   1.424 +        CAssert(identity);
   1.425 +        CAssertNil(error);
   1.426 +        CAssertEqual(identity.keychain, [MYKeychain defaultKeychain]);
   1.427 +        CAssertEqual(identity.privateKey, privateKey);
   1.428 +        CAssert([identity isEqualToCertificate: cert]);
   1.429 +        
   1.430 +        [pcert release];
   1.431 +        
   1.432 +    } @finally {
   1.433 +        [privateKey removeFromKeychain];
   1.434 +        [identity removeFromKeychain];
   1.435 +    }
   1.436 +}
   1.437 +
   1.438 +#endif
   1.439 +
   1.440 +
   1.441 +/*
   1.442 + Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   1.443 + 
   1.444 + Redistribution and use in source and binary forms, with or without modification, are permitted
   1.445 + provided that the following conditions are met:
   1.446 + 
   1.447 + * Redistributions of source code must retain the above copyright notice, this list of conditions
   1.448 + and the following disclaimer.
   1.449 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   1.450 + and the following disclaimer in the documentation and/or other materials provided with the
   1.451 + distribution.
   1.452 + 
   1.453 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   1.454 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   1.455 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   1.456 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   1.457 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   1.458 +  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   1.459 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   1.460 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   1.461 + */