Commits

Jens Alfke committed df9da0f

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.

Comments (0)

Files changed (5)

 //  Copyright 2009 Jens Alfke. All rights reserved.
 //
 
+// Reference:
+// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
+
 #import "MYASN1Object.h"
 
 
 //  Copyright 2009 Jens Alfke. All rights reserved.
 //
 
+// Reference:
+// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
+
 #import "MYBERParser.h"
 #import "MYASN1Object.h"
 #import "MYOID.h"
 }
 
 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)
 //  Copyright 2009 Jens Alfke. All rights reserved.
 //
 
+// Reference:
+// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
+
 #import "MYDEREncoder.h"
 #import "MYASN1Object.h"
 #import "MYBERParser.h"
 @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
 

MYParsedCertificate.h

 //
 
 #import <Foundation/Foundation.h>
-@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. */
 /** 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;
 - (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

MYParsedCertificate.m

 //
 
 // References:
-// <http://www.columbia.edu/~ariel/ssleay/layman.html>
-// <http://en.wikipedia.org/wiki/X.509>
-// <http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt>
+// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
+// <http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt> "X.509 Style Guide"
+// <http://en.wikipedia.org/wiki/X.509> Wikipedia article on X.509
 
 
 #import "MYParsedCertificate.h"
 }
 
 
+@interface MYCertificateName ()
+- (id) _initWithComponents: (NSArray*)components;
+@end
+
+
+#pragma mark -
 @implementation MYParsedCertificate
 
 
 {
     
     [_root release];
-    [_issuer release];
+    [_issuerCertificate release];
     [_data release];
     [super dealloc];
 }
 
 - (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;}
 
 }
 
 - (MYPublicKey*) issuerPublicKey {
-    if (_issuer)
-        return _issuer.publicKey;
+    if (_issuerCertificate)
+        return _issuerCertificate.publicKey;
     else if (self.isRoot)
         return self.subjectPublicKey;
     else
 }
 
 
-- (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];
 }
     [(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:
 
 
 
+#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
 
         
         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;
 }
 
 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;
     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