# HG changeset patch # User Jens Alfke # Date 1244217438 25200 # Node ID df9da0f6b358e4954c6ecb860ee792355b64f7ac # Parent f6c91b9da05b9d2881347dd8f3b83def137d6d02 Factored out the name accessors of MYParsedCertificate into a new class MYCertificateName, so that both subject and issuer can be accessed. A bit of other cleanup too. diff -r f6c91b9da05b -r df9da0f6b358 MYASN1Object.m --- a/MYASN1Object.m Thu Jun 04 18:36:30 2009 -0700 +++ b/MYASN1Object.m Fri Jun 05 08:57:18 2009 -0700 @@ -6,6 +6,9 @@ // Copyright 2009 Jens Alfke. All rights reserved. // +// Reference: +// "Layman's Guide To ASN.1/BER/DER" + #import "MYASN1Object.h" diff -r f6c91b9da05b -r df9da0f6b358 MYBERParser.m --- a/MYBERParser.m Thu Jun 04 18:36:30 2009 -0700 +++ b/MYBERParser.m Fri Jun 05 08:57:18 2009 -0700 @@ -6,6 +6,9 @@ // Copyright 2009 Jens Alfke. All rights reserved. // +// Reference: +// "Layman's Guide To ASN.1/BER/DER" + #import "MYBERParser.h" #import "MYASN1Object.h" #import "MYOID.h" @@ -103,6 +106,7 @@ } static NSDate* parseDate (NSString *dateStr, unsigned tag) { + //FIX: There are more date formats possible; need to try them all. (see "Layman's Guide", 5.17) NSDateFormatter *fmt = (tag==23 ?MYBERUTCTimeFormatter() :MYBERGeneralizedTimeFormatter()); NSDate *date = [fmt dateFromString: dateStr]; if (!date) diff -r f6c91b9da05b -r df9da0f6b358 MYDEREncoder.m --- a/MYDEREncoder.m Thu Jun 04 18:36:30 2009 -0700 +++ b/MYDEREncoder.m Fri Jun 05 08:57:18 2009 -0700 @@ -6,6 +6,9 @@ // Copyright 2009 Jens Alfke. All rights reserved. // +// Reference: +// "Layman's Guide To ASN.1/BER/DER" + #import "MYDEREncoder.h" #import "MYASN1Object.h" #import "MYBERParser.h" @@ -19,6 +22,10 @@ @interface MYDEREncoder () - (void) _encode: (id)object; @property (retain) NSError *error; + +/* Forces use of PrintableString tag for ASCII strings that contain characters not valid + for that encoding (notably '@'). Provided to get byte-for-byte compatibility with certs + generated by CDSA, for test cases that check this. */ @property BOOL _forcePrintableStrings; @end diff -r f6c91b9da05b -r df9da0f6b358 MYParsedCertificate.h --- a/MYParsedCertificate.h Thu Jun 04 18:36:30 2009 -0700 +++ b/MYParsedCertificate.h Fri Jun 05 08:57:18 2009 -0700 @@ -7,16 +7,16 @@ // #import -@class MYCertificate, MYPublicKey, MYPrivateKey, MYOID; +@class MYCertificateName, MYCertificate, MYPublicKey, MYPrivateKey, MYOID; /** A parsed X.509 certificate. Can be used to get more info about an existing cert, - or to modify a self-signed cert and regenerate it. */ + to modify and regenerate a self-signed cert, or to create a new self-signed cert. */ @interface MYParsedCertificate : NSObject { @private NSData *_data; NSArray *_root; - MYCertificate *_issuer; + MYCertificate *_issuerCertificate; } /** Initializes an instance by parsing an existing X.509 certificate's data. */ @@ -31,30 +31,21 @@ /** The date/time at which the certificate expires. */ @property (retain) NSDate *validTo; -/** The "common name" (nickname, whatever) of the subject/owner of the certificate. */ -@property (copy) NSString *commonName; +/** Information about the identity of the owner of this certificate. */ +@property (readonly) MYCertificateName *subject; -/** The given/first name of the subject/owner of the certificate. */ -@property (copy) NSString *givenName; +/** Information about the identity that signed/authorized this certificate. */ +@property (readonly) MYCertificateName *issuer; -/** The surname / last name / family name of the subject/owner of the certificate. */ -@property (copy) NSString *surname; - -/** A description of the subject/owner of the certificate. */ -@property (copy) NSString *description; - -/** The raw email address of the subject of the certificate. */ -@property (copy) NSString *emailAddress; +/** Returns YES if the issuer is the same as the subject. (Aka a "self-signed" certificate.) */ +@property (readonly) BOOL isRoot; /** The public key of the subject of the certificate. */ @property (readonly) MYPublicKey *subjectPublicKey; -/** Returns YES if the issuer is the same as the subject. (Aka a "self-signed" certificate.) */ -@property (readonly) BOOL isRoot; - /** Associates the certificate to its issuer. If the cert is not self-signed, you must manually set this property before validating. */ -@property (retain) MYCertificate* issuer; +@property (retain) MYCertificate* issuerCertificate; /** Checks that the issuer's signature is valid and hasn't been tampered with. If the certificate is root/self-signed, the subjectPublicKey is used to check the signature; @@ -83,3 +74,37 @@ - (BOOL) selfSignWithPrivateKey: (MYPrivateKey*)privateKey error: (NSError**)outError; @end + + + +/** An X.509 Name structure, describing the subject or issuer of a certificate. + Changing a property value of an instance associated with an already-signed certificate will + raise an exception. */ +@interface MYCertificateName : NSObject +{ + @private + NSArray *_components; +} + +/** The "common name" (nickname, whatever). */ +@property (copy) NSString *commonName; + +/** The given/first name. */ +@property (copy) NSString *givenName; + +/** The surname / last name / family name. */ +@property (copy) NSString *surname; + +/** A description. */ +@property (copy) NSString *nameDescription; + +/** The raw email address. */ +@property (copy) NSString *emailAddress; + +/** Lower-level accessor that returns the value associated with the given OID. */ +- (NSString*) stringForOID: (MYOID*)oid; + +/** Lower-level accessor that sets the value associated with the given OID. */ +- (void) setString: (NSString*)value forOID: (MYOID*)oid; + +@end diff -r f6c91b9da05b -r df9da0f6b358 MYParsedCertificate.m --- a/MYParsedCertificate.m Thu Jun 04 18:36:30 2009 -0700 +++ b/MYParsedCertificate.m Fri Jun 05 08:57:18 2009 -0700 @@ -7,9 +7,9 @@ // // References: -// -// -// +// "Layman's Guide To ASN.1/BER/DER" +// "X.509 Style Guide" +// Wikipedia article on X.509 #import "MYParsedCertificate.h" @@ -31,6 +31,12 @@ } +@interface MYCertificateName () +- (id) _initWithComponents: (NSArray*)components; +@end + + +#pragma mark - @implementation MYParsedCertificate @@ -101,7 +107,7 @@ { [_root release]; - [_issuer release]; + [_issuerCertificate release]; [_data release]; [super dealloc]; } @@ -111,34 +117,19 @@ - (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);} -- (NSArray*) _pairForOID: (MYOID*)oid atInfoIndex: (unsigned)infoIndex { - NSArray *names = $castIf(NSArray, $atIf(self._info, infoIndex)); - for (id nameEntry in names) { - for (id pair in $castIf(NSSet,nameEntry)) { - if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) { - if ($equal(oid, [pair objectAtIndex: 0])) - return pair; - } - } - } - return nil; -} - -- (NSString*) _stringForOID: (MYOID*)oid atInfoIndex: (unsigned)infoIndex { - return [[self _pairForOID: oid atInfoIndex: infoIndex] objectAtIndex: 1]; -} - - -@synthesize issuer=_issuer, certificateData=_data; +@synthesize issuerCertificate=_issuerCertificate, certificateData=_data; - (NSDate*) validFrom {return $castIf(NSDate, $atIf(self._validDates, 0));} - (NSDate*) validTo {return $castIf(NSDate, $atIf(self._validDates, 1));} -- (NSString*) commonName {return [self _stringForOID: kCommonNameOID atInfoIndex: 5];} -- (NSString*) givenName {return [self _stringForOID: kGivenNameOID atInfoIndex: 5];} -- (NSString*) surname {return [self _stringForOID: kSurnameOID atInfoIndex: 5];} -- (NSString*) description {return [self _stringForOID: kDescriptionOID atInfoIndex: 5];} -- (NSString*) emailAddress {return [self _stringForOID: kEmailOID atInfoIndex: 5];} + +- (MYCertificateName*) subject { + return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease]; +} + +- (MYCertificateName*) issuer { + return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease]; +} - (BOOL) isSigned {return [_root count] >= 3;} @@ -159,8 +150,8 @@ } - (MYPublicKey*) issuerPublicKey { - if (_issuer) - return _issuer.publicKey; + if (_issuerCertificate) + return _issuerCertificate.publicKey; else if (self.isRoot) return self.subjectPublicKey; else @@ -228,17 +219,6 @@ } -- (void) _setString: (NSString*)value forOID: (MYOID*)oid atInfoIndex: (unsigned)infoIndex { - NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid atInfoIndex: infoIndex]; - if (pair) { - [pair replaceObjectAtIndex: 1 withObject: value]; - } else { - NSMutableArray *names = $castIf(NSMutableArray, $atIf(self._info, infoIndex)); - [names addObject: [NSSet setWithObject: $marray(oid,value)]]; - } -} - - - (void) setValidFrom: (NSDate*)validFrom { [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom]; } @@ -247,26 +227,6 @@ [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo]; } -- (void) setCommonName: (NSString*)commonName { - [self _setString: commonName forOID: kCommonNameOID atInfoIndex: 5]; -} - -- (void) setGivenName: (NSString*)givenName { - [self _setString: givenName forOID: kGivenNameOID atInfoIndex: 5]; -} - -- (void) setSurname: (NSString*)surname { - [self _setString: surname forOID: kSurnameOID atInfoIndex: 5]; -} - -- (void) setDescription: (NSString*)description { - [self _setString: description forOID: kDescriptionOID atInfoIndex: 5]; -} - -- (void) setEmailAddress: (NSString*)emailAddress { - [self _setString: emailAddress forOID: kEmailOID atInfoIndex: 5]; -} - - (BOOL) selfSignWithPrivateKey: (MYPrivateKey*)privateKey error: (NSError**)outError { // Copy subject to issuer: @@ -304,6 +264,72 @@ +#pragma mark - +@implementation MYCertificateName + +- (id) _initWithComponents: (NSArray*)components +{ + self = [super init]; + if (self != nil) { + _components = [components retain]; + } + return self; +} + +- (void) dealloc +{ + [_components release]; + [super dealloc]; +} + +- (BOOL) isEqual: (id)object { + return [object isKindOfClass: [MYCertificateName class]] + && [_components isEqual: ((MYCertificateName*)object)->_components]; +} + +- (NSArray*) _pairForOID: (MYOID*)oid { + for (id nameEntry in _components) { + for (id pair in $castIf(NSSet,nameEntry)) { + if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) { + if ($equal(oid, [pair objectAtIndex: 0])) + return pair; + } + } + } + return nil; +} + +- (NSString*) stringForOID: (MYOID*)oid { + return [[self _pairForOID: oid] objectAtIndex: 1]; +} + +- (void) setString: (NSString*)value forOID: (MYOID*)oid { + NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid]; + if (pair) + [pair replaceObjectAtIndex: 1 withObject: value]; + else + [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]]; +} + +- (NSString*) commonName {return [self stringForOID: kCommonNameOID];} +- (NSString*) givenName {return [self stringForOID: kGivenNameOID];} +- (NSString*) surname {return [self stringForOID: kSurnameOID];} +- (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];} +- (NSString*) emailAddress {return [self stringForOID: kEmailOID];} + +- (void) setCommonName: (NSString*)commonName {[self setString: commonName forOID: kCommonNameOID];} +- (void) setGivenName: (NSString*)givenName {[self setString: givenName forOID: kGivenNameOID];} +- (void) setSurname: (NSString*)surname {[self setString: surname forOID: kSurnameOID];} +- (void) setNameDescription: (NSString*)desc {[self setString: desc forOID: kDescriptionOID];} +- (void) setEmailAddress: (NSString*)email {[self setString: email forOID: kEmailOID];} + + +@end + + + +#pragma mark - +#pragma mark TEST CASES: #if DEBUG @@ -336,11 +362,12 @@ CAssert(pcert.validateSignature); } - Log(@"Common Name = %@", pcert.commonName); - Log(@"Given Name = %@", pcert.givenName); - Log(@"Surname = %@", pcert.surname); - Log(@"Desc = %@", pcert.description); - Log(@"Email = %@", pcert.emailAddress); + MYCertificateName *subject = pcert.subject; + Log(@"Common Name = %@", subject.commonName); + Log(@"Given Name = %@", subject.givenName); + Log(@"Surname = %@", subject.surname); + Log(@"Desc = %@", subject.nameDescription); + Log(@"Email = %@", subject.emailAddress); return pcert; } @@ -356,17 +383,19 @@ TestCase(CreateCert) { MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512]; MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithPublicKey: privateKey.publicKey]; - pcert.commonName = @"testcase"; - pcert.givenName = @"Test"; - pcert.surname = @"Case"; - pcert.description = @"Just a test certificate created by MYCrypto"; - pcert.emailAddress = @"testcase@example.com"; + MYCertificateName *subject = pcert.subject; + subject.commonName = @"testcase"; + subject.givenName = @"Test"; + subject.surname = @"Case"; + subject.nameDescription = @"Just a test certificate created by MYCrypto"; + subject.emailAddress = @"testcase@example.com"; - CAssertEqual(pcert.commonName, @"testcase"); - CAssertEqual(pcert.givenName, @"Test"); - CAssertEqual(pcert.surname, @"Case"); - CAssertEqual(pcert.description, @"Just a test certificate created by MYCrypto"); - CAssertEqual(pcert.emailAddress, @"testcase@example.com"); + subject = pcert.subject; + CAssertEqual(subject.commonName, @"testcase"); + CAssertEqual(subject.givenName, @"Test"); + CAssertEqual(subject.surname, @"Case"); + CAssertEqual(subject.nameDescription, @"Just a test certificate created by MYCrypto"); + CAssertEqual(subject.emailAddress, @"testcase@example.com"); Log(@"Signing..."); NSError *error; @@ -379,11 +408,13 @@ MYParsedCertificate *pcert2 = testCert(@"../../Tests/generated.cer", YES); Log(@"Verifying..."); - CAssertEqual(pcert2.commonName, @"testcase"); - CAssertEqual(pcert2.givenName, @"Test"); - CAssertEqual(pcert2.surname, @"Case"); - CAssertEqual(pcert2.description, @"Just a test certificate created by MYCrypto"); - CAssertEqual(pcert2.emailAddress, @"testcase@example.com"); + MYCertificateName *subject2 = pcert2.subject; + CAssertEqual(subject2,subject); + CAssertEqual(subject2.commonName, @"testcase"); + CAssertEqual(subject2.givenName, @"Test"); + CAssertEqual(subject2.surname, @"Case"); + CAssertEqual(subject2.nameDescription, @"Just a test certificate created by MYCrypto"); + CAssertEqual(subject2.emailAddress, @"testcase@example.com"); } #endif