jens@17: // jens@21: // MYCertificateInfo.m jens@17: // MYCrypto jens@17: // jens@17: // Created by Jens Alfke on 6/2/09. jens@17: // Copyright 2009 Jens Alfke. All rights reserved. jens@17: // jens@17: jens@17: // References: jens@20: // "Layman's Guide To ASN.1/BER/DER" jens@20: // "X.509 Style Guide" jens@20: // Wikipedia article on X.509 jens@17: jens@17: jens@21: #import "MYCertificateInfo.h" jens@21: #import "MYCrypto.h" jens@17: #import "MYASN1Object.h" jens@17: #import "MYOID.h" jens@17: #import "MYBERParser.h" jens@17: #import "MYDEREncoder.h" jens@17: #import "MYErrorUtils.h" jens@17: jens@17: jens@19: #define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0) jens@19: jens@19: jens@17: static id $atIf(NSArray *array, NSUInteger index) { jens@17: return index < array.count ?[array objectAtIndex: index] :nil; jens@17: } jens@17: jens@17: jens@20: @interface MYCertificateName () jens@20: - (id) _initWithComponents: (NSArray*)components; jens@20: @end jens@20: jens@21: @interface MYCertificateInfo () jens@21: @property (retain) NSArray *_root; jens@21: @end jens@21: jens@20: jens@20: #pragma mark - jens@21: @implementation MYCertificateInfo jens@17: jens@17: jens@19: static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID, jens@19: *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID; jens@17: jens@17: jens@17: + (void) initialize { jens@19: if (!kEmailOID) { jens@19: kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,} jens@19: count: 7]; jens@19: kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5} jens@19: count: 7]; jens@19: kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3} jens@19: count: 4]; jens@19: kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42} jens@19: count: 4]; jens@19: kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4} jens@19: count: 4]; jens@19: kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13} jens@19: count: 7]; jens@19: kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1} jens@19: count: 7]; jens@17: } jens@21: } jens@21: jens@21: jens@21: - (id) initWithRoot: (NSArray*)root jens@21: { jens@21: self = [super init]; jens@21: if (self != nil) { jens@21: _root = [root retain]; jens@21: } jens@21: return self; jens@17: } jens@17: jens@17: + (NSString*) validate: (id)root { jens@17: NSArray *top = $castIf(NSArray,root); jens@17: if (top.count < 3) jens@17: return @"Too few top-level components"; jens@17: NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]); jens@17: if (info.count < 7) jens@17: return @"Too few identity components"; jens@17: MYASN1Object *version = $castIf(MYASN1Object, [info objectAtIndex: 0]); jens@17: if (!version || version.tag != 0) jens@17: return @"Missing or invalid version"; jens@17: NSArray *versionComps = $castIf(NSArray, version.components); jens@17: if (!versionComps || versionComps.count != 1) jens@17: return @"Invalid version"; jens@17: NSNumber *versionNum = $castIf(NSNumber, [versionComps objectAtIndex: 0]); jens@17: if (!versionNum || versionNum.intValue < 0 || versionNum.intValue > 2) jens@17: return @"Unrecognized version number"; jens@17: return nil; jens@17: } jens@17: jens@17: jens@17: - (id) initWithCertificateData: (NSData*)data error: (NSError**)outError; jens@17: { jens@21: if (outError) *outError = nil; jens@21: id root = MYBERParse(data,outError); jens@21: NSString *errorMsg = [[self class] validate: root]; jens@21: if (errorMsg) { jens@21: if (outError && !*outError) jens@21: *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg); jens@21: [self release]; jens@21: return nil; jens@17: } jens@21: jens@21: return [self initWithRoot: root]; jens@17: } jens@17: jens@17: - (void) dealloc jens@17: { jens@17: [_root release]; jens@17: [super dealloc]; jens@17: } jens@17: jens@21: - (BOOL) isEqual: (id)object { jens@21: return [object isKindOfClass: [MYCertificateInfo class]] jens@21: && [_root isEqual: ((MYCertificateInfo*)object)->_root]; jens@21: } jens@17: jens@19: - (NSArray*) _info {return $castIf(NSArray,$atIf(_root,0));} jens@17: jens@19: - (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);} jens@17: jens@21: @synthesize _root; jens@19: jens@19: jens@19: - (NSDate*) validFrom {return $castIf(NSDate, $atIf(self._validDates, 0));} jens@19: - (NSDate*) validTo {return $castIf(NSDate, $atIf(self._validDates, 1));} jens@20: jens@20: - (MYCertificateName*) subject { jens@20: return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease]; jens@20: } jens@20: jens@20: - (MYCertificateName*) issuer { jens@20: return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease]; jens@20: } jens@19: jens@19: - (BOOL) isSigned {return [_root count] >= 3;} jens@19: jens@19: - (BOOL) isRoot { jens@19: id issuer = $atIf(self._info,3); jens@19: return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array()); jens@19: } jens@19: jens@19: jens@17: - (MYPublicKey*) subjectPublicKey { jens@19: NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6)); jens@17: MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0)); jens@17: if (!$equal(keyAlgorithmID, kRSAAlgorithmID)) jens@17: return nil; jens@17: MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1)); jens@17: if (!keyData) return nil; jens@17: return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease]; jens@17: } jens@17: jens@21: @end jens@17: jens@17: jens@17: jens@17: jens@19: #pragma mark - jens@21: @implementation MYCertificateRequest jens@19: jens@21: - (id) initWithPublicKey: (MYPublicKey*)publicKey { jens@21: Assert(publicKey); jens@21: id empty = [NSNull null]; jens@21: id version = [[MYASN1Object alloc] initWithTag: 0 ofClass: 2 components: $array($object(0))]; jens@21: NSArray *root = $array( $marray(version, jens@21: empty, // serial # jens@21: $array(kRSAAlgorithmID), jens@21: $marray(), jens@21: $marray(empty, empty), jens@21: $marray(), jens@21: $array( $array(kRSAAlgorithmID, empty), jens@21: [MYBitString bitStringWithData: publicKey.keyData] ) ) ); jens@21: self = [super initWithRoot: root]; jens@21: [version release]; jens@19: return self; jens@19: } jens@19: jens@21: - (NSDate*) validFrom {return [super validFrom];} jens@21: - (NSDate*) validTo {return [super validTo];} jens@19: jens@19: - (void) setValidFrom: (NSDate*)validFrom { jens@19: [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom]; jens@19: } jens@19: jens@19: - (void) setValidTo: (NSDate*)validTo { jens@19: [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo]; jens@19: } jens@19: jens@19: jens@21: - (void) fillInValues { jens@19: NSMutableArray *info = (NSMutableArray*)self._info; jens@19: // Set serial number if there isn't one yet: jens@19: if (!$castIf(NSNumber, [info objectAtIndex: 1])) { jens@19: UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000); jens@19: [info replaceObjectAtIndex: 1 withObject: $object(serial)]; jens@19: } jens@19: jens@19: // Set up valid date range if there isn't one yet: jens@19: NSDate *validFrom = self.validFrom; jens@19: if (!validFrom) jens@19: validFrom = self.validFrom = [NSDate date]; jens@19: NSDate *validTo = self.validTo; jens@19: if (!validTo) jens@19: self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; jens@21: } jens@21: jens@21: jens@21: - (NSData*) requestData: (NSError**)outError { jens@21: [self fillInValues]; jens@21: return [MYDEREncoder encodeRootObject: self._info error: outError]; jens@21: } jens@21: jens@21: jens@21: - (NSData*) selfSignWithPrivateKey: (MYPrivateKey*)privateKey jens@21: error: (NSError**)outError jens@21: { jens@21: AssertEqual(privateKey.publicKey, _publicKey); // Keys must form a pair jens@19: jens@21: // Copy subject to issuer: jens@21: NSMutableArray *info = (NSMutableArray*)self._info; jens@21: [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]]; jens@21: jens@21: // Sign the request: jens@21: NSData *dataToSign = [self requestData: outError]; jens@19: if (!dataToSign) jens@21: return nil; jens@21: MYBitString *signature = [MYBitString bitStringWithData: [privateKey signData: dataToSign]]; jens@19: jens@21: // Generate and encode the certificate: jens@21: NSArray *root = $array(info, jens@21: $array(kRSAWithSHA1AlgorithmID, [NSNull null]), jens@21: signature); jens@21: return [MYDEREncoder encodeRootObject: root error: outError]; jens@21: } jens@21: jens@21: jens@21: - (MYIdentity*) createSelfSignedIdentityWithPrivateKey: (MYPrivateKey*)privateKey jens@21: error: (NSError**)outError jens@21: { jens@21: NSData *certData = [self selfSignWithPrivateKey: privateKey error: outError]; jens@21: if (!certData) jens@21: return nil; jens@21: MYCertificate *cert = [privateKey.keychain importCertificate: certData]; jens@21: Assert(cert!=nil); jens@21: MYIdentity *identity = cert.identity; jens@21: Assert(identity!=nil); jens@21: return identity; jens@19: } jens@19: jens@19: jens@17: @end jens@17: jens@17: jens@17: jens@20: #pragma mark - jens@20: @implementation MYCertificateName jens@20: jens@20: - (id) _initWithComponents: (NSArray*)components jens@20: { jens@20: self = [super init]; jens@20: if (self != nil) { jens@20: _components = [components retain]; jens@20: } jens@20: return self; jens@20: } jens@20: jens@20: - (void) dealloc jens@20: { jens@20: [_components release]; jens@20: [super dealloc]; jens@20: } jens@20: jens@20: - (BOOL) isEqual: (id)object { jens@20: return [object isKindOfClass: [MYCertificateName class]] jens@20: && [_components isEqual: ((MYCertificateName*)object)->_components]; jens@20: } jens@20: jens@20: - (NSArray*) _pairForOID: (MYOID*)oid { jens@20: for (id nameEntry in _components) { jens@20: for (id pair in $castIf(NSSet,nameEntry)) { jens@20: if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) { jens@20: if ($equal(oid, [pair objectAtIndex: 0])) jens@20: return pair; jens@20: } jens@20: } jens@20: } jens@20: return nil; jens@20: } jens@20: jens@20: - (NSString*) stringForOID: (MYOID*)oid { jens@20: return [[self _pairForOID: oid] objectAtIndex: 1]; jens@20: } jens@20: jens@20: - (void) setString: (NSString*)value forOID: (MYOID*)oid { jens@20: NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid]; jens@20: if (pair) jens@20: [pair replaceObjectAtIndex: 1 withObject: value]; jens@20: else jens@20: [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]]; jens@20: } jens@20: jens@20: - (NSString*) commonName {return [self stringForOID: kCommonNameOID];} jens@20: - (NSString*) givenName {return [self stringForOID: kGivenNameOID];} jens@20: - (NSString*) surname {return [self stringForOID: kSurnameOID];} jens@20: - (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];} jens@20: - (NSString*) emailAddress {return [self stringForOID: kEmailOID];} jens@20: jens@20: - (void) setCommonName: (NSString*)commonName {[self setString: commonName forOID: kCommonNameOID];} jens@20: - (void) setGivenName: (NSString*)givenName {[self setString: givenName forOID: kGivenNameOID];} jens@20: - (void) setSurname: (NSString*)surname {[self setString: surname forOID: kSurnameOID];} jens@20: - (void) setNameDescription: (NSString*)desc {[self setString: desc forOID: kDescriptionOID];} jens@20: - (void) setEmailAddress: (NSString*)email {[self setString: email forOID: kEmailOID];} jens@20: jens@20: jens@20: @end jens@20: jens@20: jens@20: jens@20: #pragma mark - jens@20: #pragma mark TEST CASES: jens@17: jens@19: #if DEBUG jens@19: jens@19: jens@21: static MYCertificateInfo* testCert(NSString *filename, BOOL selfSigned) { jens@21: Log(@"--- Creating MYCertificateInfo from %@", filename); jens@19: NSData *certData = [NSData dataWithContentsOfFile: filename]; jens@19: //Log(@"Cert Data =\n%@", certData); jens@19: NSError *error = nil; jens@21: MYCertificateInfo *pcert = [[MYCertificateInfo alloc] initWithCertificateData: certData jens@21: error: &error]; jens@19: CAssertNil(error); jens@19: CAssert(pcert != nil); jens@19: jens@19: CAssertEq(pcert.isRoot, selfSigned); jens@21: jens@19: Log(@"Subject Public Key = %@", pcert.subjectPublicKey); jens@19: CAssert(pcert.subjectPublicKey); jens@20: MYCertificateName *subject = pcert.subject; jens@20: Log(@"Common Name = %@", subject.commonName); jens@20: Log(@"Given Name = %@", subject.givenName); jens@20: Log(@"Surname = %@", subject.surname); jens@20: Log(@"Desc = %@", subject.nameDescription); jens@20: Log(@"Email = %@", subject.emailAddress); jens@21: CAssert(subject.commonName); jens@21: jens@21: // Now go through MYCertificate: jens@21: MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData]; jens@21: CAssert(cert); jens@21: CAssertEqual(cert.info, pcert); jens@21: jens@19: return pcert; jens@19: } jens@19: jens@19: jens@17: TestCase(ParsedCert) { jens@17: testCert(@"../../Tests/selfsigned.cer", YES); jens@17: testCert(@"../../Tests/iphonedev.cer", NO); jens@19: } jens@19: jens@19: jens@21: #import "MYCrypto_Private.h" jens@19: jens@19: TestCase(CreateCert) { jens@19: MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512]; jens@21: CAssert(privateKey); jens@21: MYIdentity *identity = nil; jens@21: @try{ jens@21: MYCertificateRequest *pcert = [[MYCertificateRequest alloc] initWithPublicKey: privateKey.publicKey]; jens@21: MYCertificateName *subject = pcert.subject; jens@21: subject.commonName = @"testcase"; jens@21: subject.givenName = @"Test"; jens@21: subject.surname = @"Case"; jens@21: subject.nameDescription = @"Just a test certificate created by MYCrypto"; jens@21: subject.emailAddress = @"testcase@example.com"; jens@19: jens@21: subject = pcert.subject; jens@21: CAssertEqual(subject.commonName, @"testcase"); jens@21: CAssertEqual(subject.givenName, @"Test"); jens@21: CAssertEqual(subject.surname, @"Case"); jens@21: CAssertEqual(subject.nameDescription, @"Just a test certificate created by MYCrypto"); jens@21: CAssertEqual(subject.emailAddress, @"testcase@example.com"); jens@21: jens@21: Log(@"Signing..."); jens@21: NSError *error; jens@21: NSData *certData = [pcert selfSignWithPrivateKey: privateKey error: &error]; jens@21: Log(@"Generated cert = \n%@", certData); jens@21: CAssert(certData); jens@21: CAssertNil(error); jens@21: CAssert(certData); jens@21: [certData writeToFile: @"../../Tests/generated.cer" atomically: YES]; jens@21: MYCertificateInfo *pcert2 = testCert(@"../../Tests/generated.cer", YES); jens@21: jens@21: Log(@"Verifying Info..."); jens@21: MYCertificateName *subject2 = pcert2.subject; jens@21: CAssertEqual(subject2,subject); jens@21: CAssertEqual(subject2.commonName, @"testcase"); jens@21: CAssertEqual(subject2.givenName, @"Test"); jens@21: CAssertEqual(subject2.surname, @"Case"); jens@21: CAssertEqual(subject2.nameDescription, @"Just a test certificate created by MYCrypto"); jens@21: CAssertEqual(subject2.emailAddress, @"testcase@example.com"); jens@21: jens@21: Log(@"Verifying Signature..."); jens@21: MYCertificate *cert = [[MYCertificate alloc] initWithCertificateData: certData]; jens@21: Log(@"Loaded %@", cert); jens@21: CAssert(cert); jens@21: MYPublicKey *certKey = cert.publicKey; jens@21: Log(@"Its public key = %@", certKey); jens@21: CAssertEqual(certKey.keyData, privateKey.publicKey.keyData); jens@21: jens@21: Log(@"Creating Identity..."); jens@21: identity = [pcert createSelfSignedIdentityWithPrivateKey: privateKey error: &error]; jens@21: Log(@"Identity = %@", identity); jens@21: CAssert(identity); jens@21: CAssertNil(error); jens@21: CAssertEqual(identity.keychain, [MYKeychain defaultKeychain]); jens@21: CAssertEqual(identity.privateKey, privateKey); jens@21: CAssert([identity isEqualToCertificate: cert]); jens@21: jens@21: [pcert release]; jens@21: jens@21: } @finally { jens@21: [privateKey removeFromKeychain]; jens@21: [identity removeFromKeychain]; jens@21: } jens@19: } jens@19: jens@19: #endif jens@19: jens@17: jens@21: /* jens@21: Copyright (c) 2009, Jens Alfke . All rights reserved. jens@17: jens@21: Redistribution and use in source and binary forms, with or without modification, are permitted jens@21: provided that the following conditions are met: jens@21: jens@21: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@21: and the following disclaimer. jens@21: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@21: and the following disclaimer in the documentation and/or other materials provided with the jens@21: distribution. jens@21: jens@21: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@21: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@21: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@21: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@21: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@21: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@21: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@21: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@21: */