# HG changeset patch # User Jens Alfke # Date 1244165790 25200 # Node ID f6c91b9da05b9d2881347dd8f3b83def137d6d02 # Parent a06e44b9b8989f32316bfe1bcc34b2b3e16ce7c3 Whew! MYParsedCertificate can now generate certs from scratch. Also added improvements and fixes to the BER/DER codecs. diff -r a06e44b9b898 -r f6c91b9da05b MYASN1Object.h --- a/MYASN1Object.h Wed Jun 03 17:22:42 2009 -0700 +++ b/MYASN1Object.h Thu Jun 04 18:36:30 2009 -0700 @@ -9,8 +9,9 @@ #import -/** A generic ASN.1 data value. The BER parser instantiates these to represent parsed values that - it doesn't know how to represent otherwise. */ +/* A generic ASN.1 data value. The BER parser instantiates these to represent parsed values that + it doesn't know how to represent otherwise. + This is mostly used internally by MYParsedCertificate. */ @interface MYASN1Object : NSObject { @private @@ -40,22 +41,26 @@ @end -/** An ASN.1 "big" (arbitrary-length) integer. - The value contains the bytes of the integer, in big-endian order. */ +/* An ASN.1 "big" (arbitrary-length) integer. + The value contains the bytes of the integer, in big-endian order. + This is mostly used internally by MYParsedCertificate. */ @interface MYASN1BigInteger : MYASN1Object @end -/** An ordered string of bits, as used in ASN.1. +/* An ordered string of bits, as used in ASN.1. This differs from NSData in that it need not occupy a whole number of bytes; - that is, the number of bits need not be a multiple of 8. */ + that is, the number of bits need not be a multiple of 8. + This is mostly used internally by MYParsedCertificate. */ @interface MYBitString : NSObject { + @private NSData *_bits; NSUInteger _bitCount; } - (id)initWithBits: (NSData*)bits count: (NSUInteger)bitCount; ++ (MYBitString*) bitStringWithData: (NSData*)bits; @property (readonly, nonatomic) NSData *bits; @property (readonly, nonatomic) NSUInteger bitCount; diff -r a06e44b9b898 -r f6c91b9da05b MYASN1Object.m --- a/MYASN1Object.m Wed Jun 03 17:22:42 2009 -0700 +++ b/MYASN1Object.m Thu Jun 04 18:36:30 2009 -0700 @@ -61,6 +61,15 @@ return $sprintf(@"%@[%hhu/%u/%u, %u bytes]", self.class, _tagClass,(unsigned)_constructed,_tag, _value.length); } +- (BOOL) isEqual: (id)object { + return [object isKindOfClass: [MYASN1Object class]] + && _tag==[object tag] + && _tagClass==[object tagClass] + && _constructed==[object constructed] + && $equal(_value,[object value]) + && $equal(_components,[object components]); +} + static void dump(id object, NSMutableString *output, NSString *indent) { if ([object isKindOfClass: [MYASN1Object class]]) { MYASN1Object *asn1Obj = object; @@ -108,8 +117,7 @@ @implementation MYBitString -- (id)initWithBits: (NSData*)bits count: (unsigned)bitCount; -{ +- (id)initWithBits: (NSData*)bits count: (unsigned)bitCount { Assert(bits); Assert(bitCount <= 8*bits.length); self = [super init]; @@ -120,6 +128,10 @@ return self; } ++ (MYBitString*) bitStringWithData: (NSData*)bits { + return [[[self alloc] initWithBits: bits count: 8*bits.length] autorelease]; +} + - (void) dealloc { [_bits release]; @@ -132,4 +144,14 @@ return $sprintf(@"%@%@", [self class], _bits); } +- (unsigned) hash { + return _bits.hash ^ _bitCount; +} + +- (BOOL) isEqual: (id)object { + return [object isKindOfClass: [MYBitString class]] + && _bitCount==[object bitCount] + && [_bits isEqual: [object bits]]; +} + @end diff -r a06e44b9b898 -r f6c91b9da05b MYBERParser.h --- a/MYBERParser.h Wed Jun 03 17:22:42 2009 -0700 +++ b/MYBERParser.h Thu Jun 04 18:36:30 2009 -0700 @@ -6,13 +6,14 @@ // Copyright 2009 Jens Alfke. All rights reserved. // -#import +#import #define MYASN1ErrorDomain @"MYASN1ErrorDomain" -/** Parses a block of BER-formatted data into an object tree. */ +/** Parses a block of BER-formatted data into an object tree. + This is mostly used internally by MYParsedCertificate. */ id MYBERParse (NSData *ber, NSError **outError); size_t MYBERGetLength (NSData *ber, NSError **outError); diff -r a06e44b9b898 -r f6c91b9da05b MYBERParser.m --- a/MYBERParser.m Wed Jun 03 17:22:42 2009 -0700 +++ b/MYBERParser.m Thu Jun 04 18:36:30 2009 -0700 @@ -194,7 +194,15 @@ return readStringOrDie(input,length,NSUTF8StringEncoding); case 18: // numeric string case 19: // printable string: - return readStringOrDie(input,length,NSASCIIStringEncoding); + case 22: // IA5 string: + case 20: // T61 string: + { + NSString *string = readStringOrDie(input,length,NSASCIIStringEncoding); + if (string) + return string; + else + break; // if decoding fails, fall back to generic MYASN1Object + } case 23: // UTC time: case 24: // Generalized time: return parseDate(readStringOrDie(input,length,NSASCIIStringEncoding), header.tag); @@ -328,9 +336,7 @@ MYCertificate *myCert = [[MYCertificate alloc] initWithCertificateData: cert]; CAssert(myCert); - const CSSM_KEY *pubKey = myCert.publicKey.cssmKey; - CSSM_DATA pubKeyData = pubKey->KeyData; - id parsedPubKey = MYBERParse([NSData dataWithBytes: pubKeyData.Data length: pubKeyData.Length],NULL); + id parsedPubKey = MYBERParse(myCert.publicKey.keyData, NULL); Log(@"Parsed public key:\n%@", [MYASN1Object dump: parsedPubKey]); cert = [NSData dataWithContentsOfFile: @"../../Tests/iphonedev.cer"]; diff -r a06e44b9b898 -r f6c91b9da05b MYCrypto-iPhone.xcodeproj/project.pbxproj --- a/MYCrypto-iPhone.xcodeproj/project.pbxproj Wed Jun 03 17:22:42 2009 -0700 +++ b/MYCrypto-iPhone.xcodeproj/project.pbxproj Thu Jun 04 18:36:30 2009 -0700 @@ -16,6 +16,11 @@ 273392120F8283B8009414D9 /* MYKeychain-iPhone.m in Sources */ = {isa = PBXBuildFile; fileRef = 273392110F8283B8009414D9 /* MYKeychain-iPhone.m */; }; 274110090F99234100AD413F /* MYSymmetricKey-iPhone.m in Sources */ = {isa = PBXBuildFile; fileRef = 274110080F99234100AD413F /* MYSymmetricKey-iPhone.m */; }; 2748607F0F8D5E0600FE617B /* MYPrivateKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 2748607E0F8D5E0600FE617B /* MYPrivateKey.m */; }; + 275D9FEE0FD8795300D85A86 /* MYParsedCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 275D9FE40FD8795300D85A86 /* MYParsedCertificate.m */; }; + 275D9FEF0FD8795300D85A86 /* MYASN1Object.m in Sources */ = {isa = PBXBuildFile; fileRef = 275D9FE60FD8795300D85A86 /* MYASN1Object.m */; }; + 275D9FF00FD8795300D85A86 /* MYBERParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 275D9FE80FD8795300D85A86 /* MYBERParser.m */; }; + 275D9FF10FD8795300D85A86 /* MYDEREncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 275D9FEA0FD8795300D85A86 /* MYDEREncoder.m */; }; + 275D9FF20FD8795300D85A86 /* MYOID.m in Sources */ = {isa = PBXBuildFile; fileRef = 275D9FEC0FD8795300D85A86 /* MYOID.m */; }; 276FB13F0F84090900CB326E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 276FB13E0F84090900CB326E /* Security.framework */; }; 276FB16E0F84152B00CB326E /* MYCryptoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 276FB16D0F84152B00CB326E /* MYCryptoTest.m */; }; 276FB3190F856AA700CB326E /* MYPublicKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 276FB3180F856AA700CB326E /* MYPublicKey.m */; }; @@ -23,7 +28,6 @@ 276FB34B0F856CA400CB326E /* MYCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 276FB34A0F856CA400CB326E /* MYCertificate.m */; }; 27A430120F87C6C10063D362 /* MYKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E823110F81D56E0019BE60 /* MYKey.m */; }; 27A430140F87C6D50063D362 /* MYSymmetricKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A430130F87C6D50063D362 /* MYSymmetricKey.m */; }; - 27B8522D0FCEE6F9005631F9 /* MYASN1.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B8522C0FCEE6F9005631F9 /* MYASN1.m */; }; 27E3A6AA0F91B8B30079D4D9 /* MYCryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E3A6A70F91B8B30079D4D9 /* MYCryptor.m */; }; 27E823240F81D56E0019BE60 /* MYKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E823150F81D56E0019BE60 /* MYKeychainItem.m */; }; 27E823270F81D56E0019BE60 /* MYDigest.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E8231C0F81D56E0019BE60 /* MYDigest.m */; }; @@ -53,6 +57,16 @@ 274110080F99234100AD413F /* MYSymmetricKey-iPhone.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MYSymmetricKey-iPhone.m"; sourceTree = ""; }; 2748607D0F8D5DF200FE617B /* MYPrivateKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYPrivateKey.h; sourceTree = ""; }; 2748607E0F8D5E0600FE617B /* MYPrivateKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYPrivateKey.m; sourceTree = ""; }; + 275D9FE30FD8795300D85A86 /* MYParsedCertificate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYParsedCertificate.h; sourceTree = ""; }; + 275D9FE40FD8795300D85A86 /* MYParsedCertificate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYParsedCertificate.m; sourceTree = ""; }; + 275D9FE50FD8795300D85A86 /* MYASN1Object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYASN1Object.h; sourceTree = ""; }; + 275D9FE60FD8795300D85A86 /* MYASN1Object.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYASN1Object.m; sourceTree = ""; }; + 275D9FE70FD8795300D85A86 /* MYBERParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYBERParser.h; sourceTree = ""; }; + 275D9FE80FD8795300D85A86 /* MYBERParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYBERParser.m; sourceTree = ""; }; + 275D9FE90FD8795300D85A86 /* MYDEREncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYDEREncoder.h; sourceTree = ""; }; + 275D9FEA0FD8795300D85A86 /* MYDEREncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYDEREncoder.m; sourceTree = ""; }; + 275D9FEB0FD8795300D85A86 /* MYOID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYOID.h; sourceTree = ""; }; + 275D9FEC0FD8795300D85A86 /* MYOID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYOID.m; sourceTree = ""; }; 276FB13E0F84090900CB326E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 276FB16D0F84152B00CB326E /* MYCryptoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYCryptoTest.m; sourceTree = ""; }; 276FB3180F856AA700CB326E /* MYPublicKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYPublicKey.m; sourceTree = ""; }; @@ -61,8 +75,6 @@ 27A430130F87C6D50063D362 /* MYSymmetricKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYSymmetricKey.m; sourceTree = ""; }; 27A430150F87C6DB0063D362 /* MYSymmetricKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYSymmetricKey.h; sourceTree = ""; }; 27AAD9710F8927DB0064DD7C /* MYCryptoConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYCryptoConfig.h; sourceTree = ""; }; - 27B8522B0FCEE6F9005631F9 /* MYASN1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYASN1.h; sourceTree = ""; }; - 27B8522C0FCEE6F9005631F9 /* MYASN1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYASN1.m; sourceTree = ""; }; 27E3A6A60F91B8B30079D4D9 /* MYCryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYCryptor.h; sourceTree = ""; }; 27E3A6A70F91B8B30079D4D9 /* MYCryptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYCryptor.m; sourceTree = ""; }; 27E8230C0F81D56E0019BE60 /* MYCertificate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYCertificate.h; sourceTree = ""; }; @@ -130,6 +142,23 @@ name = Products; sourceTree = ""; }; + 275D9FE00FD8795300D85A86 /* Certificates */ = { + isa = PBXGroup; + children = ( + 275D9FE30FD8795300D85A86 /* MYParsedCertificate.h */, + 275D9FE40FD8795300D85A86 /* MYParsedCertificate.m */, + 275D9FE50FD8795300D85A86 /* MYASN1Object.h */, + 275D9FE60FD8795300D85A86 /* MYASN1Object.m */, + 275D9FE70FD8795300D85A86 /* MYBERParser.h */, + 275D9FE80FD8795300D85A86 /* MYBERParser.m */, + 275D9FE90FD8795300D85A86 /* MYDEREncoder.h */, + 275D9FEA0FD8795300D85A86 /* MYDEREncoder.m */, + 275D9FEB0FD8795300D85A86 /* MYOID.h */, + 275D9FEC0FD8795300D85A86 /* MYOID.m */, + ); + name = Certificates; + sourceTree = ""; + }; 27E3A6A10F91B8B30079D4D9 /* Encryption */ = { isa = PBXGroup; children = ( @@ -142,8 +171,6 @@ 27E8230B0F81D56E0019BE60 /* Source */ = { isa = PBXGroup; children = ( - 27B8522B0FCEE6F9005631F9 /* MYASN1.h */, - 27B8522C0FCEE6F9005631F9 /* MYASN1.m */, 27AAD9710F8927DB0064DD7C /* MYCryptoConfig.h */, 27E8230C0F81D56E0019BE60 /* MYCertificate.h */, 276FB34A0F856CA400CB326E /* MYCertificate.m */, @@ -198,6 +225,7 @@ children = ( 27E8230B0F81D56E0019BE60 /* Source */, 27E3A6A10F91B8B30079D4D9 /* Encryption */, + 275D9FE00FD8795300D85A86 /* Certificates */, 27E8232B0F81D5760019BE60 /* MYUtilities */, 080E96DDFE201D6D7F000001 /* iPhone */, 29B97323FDCFA39411CA2CEA /* Frameworks */, @@ -295,7 +323,11 @@ 27059CF30F8F0F9200A8422F /* MYIdentity.m in Sources */, 27E3A6AA0F91B8B30079D4D9 /* MYCryptor.m in Sources */, 274110090F99234100AD413F /* MYSymmetricKey-iPhone.m in Sources */, - 27B8522D0FCEE6F9005631F9 /* MYASN1.m in Sources */, + 275D9FEE0FD8795300D85A86 /* MYParsedCertificate.m in Sources */, + 275D9FEF0FD8795300D85A86 /* MYASN1Object.m in Sources */, + 275D9FF00FD8795300D85A86 /* MYBERParser.m in Sources */, + 275D9FF10FD8795300D85A86 /* MYDEREncoder.m in Sources */, + 275D9FF20FD8795300D85A86 /* MYOID.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r a06e44b9b898 -r f6c91b9da05b MYCrypto.xcodeproj/project.pbxproj --- a/MYCrypto.xcodeproj/project.pbxproj Wed Jun 03 17:22:42 2009 -0700 +++ b/MYCrypto.xcodeproj/project.pbxproj Thu Jun 04 18:36:30 2009 -0700 @@ -278,10 +278,10 @@ 274861440F8D757600FE617B /* Certificates */ = { isa = PBXGroup; children = ( + 27A42ECC0F8689D30063D362 /* MYCertGen.h */, + 27A42ECD0F8689D30063D362 /* MYCertGen.m */, 275D9D900FD5FB4100D85A86 /* MYParsedCertificate.h */, 275D9D910FD5FB4100D85A86 /* MYParsedCertificate.m */, - 27A42ECC0F8689D30063D362 /* MYCertGen.h */, - 27A42ECD0F8689D30063D362 /* MYCertGen.m */, 27B852F30FCF4EB6005631F9 /* MYASN1Object.h */, 27B852F40FCF4EB7005631F9 /* MYASN1Object.m */, 270A7A710FD58FF200770C4D /* MYBERParser.h */, diff -r a06e44b9b898 -r f6c91b9da05b MYDEREncoder.h --- a/MYDEREncoder.h Wed Jun 03 17:22:42 2009 -0700 +++ b/MYDEREncoder.h Thu Jun 04 18:36:30 2009 -0700 @@ -11,9 +11,11 @@ @interface MYDEREncoder : NSObject { + @private id _rootObject; NSMutableData *_output; NSError *_error; + BOOL _forcePrintableStrings; } - (id) initWithRootObject: (id)object; diff -r a06e44b9b898 -r f6c91b9da05b MYDEREncoder.m --- a/MYDEREncoder.m Wed Jun 03 17:22:42 2009 -0700 +++ b/MYDEREncoder.m Thu Jun 04 18:36:30 2009 -0700 @@ -19,6 +19,7 @@ @interface MYDEREncoder () - (void) _encode: (id)object; @property (retain) NSError *error; +@property BOOL _forcePrintableStrings; @end @@ -46,9 +47,15 @@ { [_rootObject release]; [_output release]; + [_error release]; [super dealloc]; } +- (id) copyWithZone: (NSZone*)zone { + MYDEREncoder *copy = [[[self class] alloc] init]; + copy->_forcePrintableStrings = _forcePrintableStrings; + return copy; +} static unsigned sizeOfUnsignedInt (UInt64 n) { @@ -185,11 +192,22 @@ - (void) _encodeString: (NSString*)string { + static NSMutableCharacterSet *kNotPrintableCharSet; + if (!kNotPrintableCharSet) { + kNotPrintableCharSet = [[NSMutableCharacterSet characterSetWithCharactersInString: @" '()+,-./:=?"] retain]; + [kNotPrintableCharSet formUnionWithCharacterSet: [NSCharacterSet alphanumericCharacterSet]]; + [kNotPrintableCharSet invert]; + } NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding]; - if (data) - [self _writeTag: 19 class: 0 constructed: NO data: data]; - else + if (data) { + unsigned tag = 19; // printablestring (a silly arbitrary subset of ASCII defined by ASN.1) + if (!_forcePrintableStrings && [string rangeOfCharacterFromSet: kNotPrintableCharSet].length > 0) + tag = 20; // IA5string (full 7-bit ASCII) + [self _writeTag: tag class: 0 constructed: NO data: data]; + } else { + // fall back to UTF-8: [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]]; + } } @@ -209,7 +227,7 @@ - (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass { - MYDEREncoder *subEncoder = [[[self class] alloc] init]; + MYDEREncoder *subEncoder = [self copy]; for (id object in collection) [subEncoder _encode: object]; [self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output]; @@ -268,7 +286,7 @@ return _output; } -@synthesize error=_error; +@synthesize error=_error, _forcePrintableStrings; @end @@ -342,11 +360,13 @@ id certObjects = MYBERParse(cert,&error); CAssertNil(error); Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]); - NSData *encoded = [MYDEREncoder encodeRootObject: certObjects error: &error]; + MYDEREncoder *encoder = [[MYDEREncoder alloc] initWithRootObject: certObjects]; + encoder._forcePrintableStrings = YES; // hack for compatibility with the way CDSA writes ASN.1 + NSData *encoded = encoder.output; CAssertNil(error); id reDecoded = MYBERParse(encoded, &error); CAssertNil(error); Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]); - [encoded writeToFile: @"../../Tests/iphonedev_reencoded.cer" atomically: YES]; + [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES]; CAssertEqual(encoded,cert); } diff -r a06e44b9b898 -r f6c91b9da05b MYOID.h --- a/MYOID.h Wed Jun 03 17:22:42 2009 -0700 +++ b/MYOID.h Thu Jun 04 18:36:30 2009 -0700 @@ -6,10 +6,11 @@ // Copyright 2009 Jens Alfke. All rights reserved. // -#import +#import -/** An ASN.1 Object-ID, which is a sequence of integer components that define namespaces. */ +/* An ASN.1 Object-ID, which is a sequence of integer components that define namespaces. + This is mostly used internally by MYParsedCertificate. */ @interface MYOID : NSObject { NSData *_data; diff -r a06e44b9b898 -r f6c91b9da05b MYParsedCertificate.h --- a/MYParsedCertificate.h Wed Jun 03 17:22:42 2009 -0700 +++ b/MYParsedCertificate.h Thu Jun 04 18:36:30 2009 -0700 @@ -7,23 +7,79 @@ // #import -@class MYCertificate, MYOID; +@class 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. */ @interface MYParsedCertificate : NSObject { + @private NSData *_data; - id _root; + NSArray *_root; MYCertificate *_issuer; } -+ (MYOID*) RSAWithSHA1AlgorithmID; - +/** Initializes an instance by parsing an existing X.509 certificate's data. */ - (id) initWithCertificateData: (NSData*)data error: (NSError**)outError; +/** The raw data of the certificate. */ +@property (readonly) NSData* certificateData; + +/** The date/time at which the certificate first becomes valid. */ +@property (retain) NSDate *validFrom; + +/** 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; + +/** The given/first name of the subject/owner of the certificate. */ +@property (copy) NSString *givenName; + +/** 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; + +/** 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 verifying. */ + If the cert is not self-signed, you must manually set this property before validating. */ @property (retain) MYCertificate* issuer; +/** 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; + otherwise, the issuer property needs to have been set and its publicKey will be used. */ +- (BOOL) validateSignature; + + +// Generating certificates: + +/** Initializes a blank instance which can be used to create a new certificate. + The certificate will not contain anything yet other than the public key. + The desired attributes should be set, and then the -selfSignWithPrivateKey:error method called. */ +- (id) initWithPublicKey: (MYPublicKey*)pubKey; + +/** Has the certificate been signed yet? */ +@property (readonly) BOOL isSigned; + +/** Signs the certificate using the given private key, which must be the counterpart of the + public key stored in the certificate. + The subject attributes will be copied to the issuer attributes. + If no valid date range has been set yet, it will be set to a range of one year starting from + the current time. + A unique serial number based on the current time will be set. + After this method returns successfully, access the certificateData property to get the + encoded certificate. */ +- (BOOL) selfSignWithPrivateKey: (MYPrivateKey*)privateKey error: (NSError**)outError; + @end diff -r a06e44b9b898 -r f6c91b9da05b MYParsedCertificate.m --- a/MYParsedCertificate.m Wed Jun 03 17:22:42 2009 -0700 +++ b/MYParsedCertificate.m Thu Jun 04 18:36:30 2009 -0700 @@ -7,6 +7,7 @@ // // References: +// // // @@ -17,10 +18,14 @@ #import "MYBERParser.h" #import "MYDEREncoder.h" #import "MYPublicKey.h" +#import "MYPrivateKey.h" #import "MYCertificate.h" #import "MYErrorUtils.h" +#define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0) + + static id $atIf(NSArray *array, NSUInteger index) { return index < array.count ?[array objectAtIndex: index] :nil; } @@ -29,24 +34,30 @@ @implementation MYParsedCertificate -static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID; +static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID, + *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID; + (void) initialize { - if (!kRSAAlgorithmID) { - UInt32 components[7] = {1, 2, 840, 113549, 1, 1, 1,}; - kRSAAlgorithmID = [[MYOID alloc] initWithComponents: components count: 7]; + if (!kEmailOID) { + kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,} + count: 7]; + kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5} + count: 7]; + kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3} + count: 4]; + kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42} + count: 4]; + kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4} + count: 4]; + kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13} + count: 7]; + kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1} + count: 7]; } - if (!kRSAWithSHA1AlgorithmID) { - UInt32 components[7] = {1, 2, 840, 113549, 1, 1, 5}; - kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: components count: 7]; - } + } -+ (MYOID*) RSAAlgorithmID {return kRSAAlgorithmID;} -+ (MYOID*) RSAWithSHA1AlgorithmID {return kRSAWithSHA1AlgorithmID;} - - + (NSString*) validate: (id)root { NSArray *top = $castIf(NSArray,root); if (top.count < 3) @@ -91,48 +102,71 @@ [_root release]; [_issuer release]; + [_data release]; [super dealloc]; } -@synthesize issuer=_issuer; +- (NSArray*) _info {return $castIf(NSArray,$atIf(_root,0));} +- (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);} -- (NSArray*) info {return $castIf(NSArray,$atIf(_root,0));} - -- (BOOL) isSelfSigned { - id issuer = $atIf(self.info,3); - id subject = $atIf(self.info,5); - return $equal(issuer,subject); +- (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; + + +- (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];} + +- (BOOL) isSigned {return [_root count] >= 3;} + +- (BOOL) isRoot { + id issuer = $atIf(self._info,3); + return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array()); +} + + - (MYPublicKey*) subjectPublicKey { - NSArray *keyInfo = $cast(NSArray, $atIf(self.info, 6)); + NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6)); MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0)); if (!$equal(keyAlgorithmID, kRSAAlgorithmID)) return nil; MYBitString *keyData = $cast(MYBitString, $atIf(keyInfo, 1)); if (!keyData) return nil; return [[[MYPublicKey alloc] initWithKeyData: keyData.bits] autorelease]; - /* - NSArray *keyParts = $castIf(NSArray, MYBERParse(keyData, nil)); - if (!keyParts) return nil; - MYBitString *modulus = $castIf(MYBitString, $atIf(keyParts,0)); - int exponent = [$castIf(NSNumber, $atIf(keyParts,1)) intValue]; - if (!modulus || exponent<3) return nil; - */ } - (MYPublicKey*) issuerPublicKey { if (_issuer) return _issuer.publicKey; - else if (self.isSelfSigned) + else if (self.isRoot) return self.subjectPublicKey; else return nil; } - - (NSData*) signedData { // The root object is a sequence; we want to extract the 1st object of that sequence. const UInt8 *certStart = _data.bytes; @@ -159,7 +193,7 @@ } - (BOOL) validateSignature { - if (!$equal(self.signatureAlgorithmID, [MYParsedCertificate RSAWithSHA1AlgorithmID])) + if (!$equal(self.signatureAlgorithmID, kRSAWithSHA1AlgorithmID)) return NO; NSData *signedData = self.signedData; NSData *signature = self.signature; @@ -169,45 +203,191 @@ } +#pragma mark - +#pragma mark CERTIFICATE GENERATION: + + +- (id) initWithPublicKey: (MYPublicKey*)pubKey { + Assert(pubKey); + self = [super init]; + if (self != nil) { + id empty = [NSNull null]; + id version = [[MYASN1Object alloc] initWithTag: 0 ofClass: 2 components: $array($object(0))]; + _root = $array( $marray(version, + empty, // serial # + $array(kRSAAlgorithmID), + $marray(), + $marray(empty, empty), + $marray(), + $array( $array(kRSAAlgorithmID, empty), + [MYBitString bitStringWithData: pubKey.keyData] ) ) ); + [version release]; + [_root retain]; + } + return self; +} + + +- (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]; +} + +- (void) setValidTo: (NSDate*)validTo { + [(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: + NSMutableArray *info = (NSMutableArray*)self._info; + [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]]; + + // Set serial number if there isn't one yet: + if (!$castIf(NSNumber, [info objectAtIndex: 1])) { + UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000); + [info replaceObjectAtIndex: 1 withObject: $object(serial)]; + } + + // Set up valid date range if there isn't one yet: + NSDate *validFrom = self.validFrom; + if (!validFrom) + validFrom = self.validFrom = [NSDate date]; + NSDate *validTo = self.validTo; + if (!validTo) + self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; + + // Append signature to cert structure: + NSData *dataToSign = [MYDEREncoder encodeRootObject: info error: outError]; + if (!dataToSign) + return NO; + setObj(&_root, $array(info, + $array(kRSAWithSHA1AlgorithmID, [NSNull null]), + [MYBitString bitStringWithData: [privateKey signData: dataToSign]])); + + setObj(&_data, [MYDEREncoder encodeRootObject: _root error: outError]); + return _data!=nil; +} + + @end +#if DEBUG + + +static MYParsedCertificate* testCert(NSString *filename, BOOL selfSigned) { + Log(@"--- Creating MYParsedCertificate from %@", filename); + NSData *certData = [NSData dataWithContentsOfFile: filename]; + //Log(@"Cert Data =\n%@", certData); + NSError *error = nil; + MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithCertificateData: certData + error: &error]; + CAssertNil(error); + CAssert(pcert != nil); + + CAssertEq(pcert.isRoot, selfSigned); + + NSData *signedData = pcert.signedData; + //Log(@"Signed Data = (length=%x)\n%@", signedData.length, signedData); + CAssertEqual(signedData, [certData subdataWithRange: NSMakeRange(4,signedData.length)]); + + Log(@"AlgID = %@", pcert.signatureAlgorithmID); + Log(@"Signature = %@", pcert.signature); + CAssertEqual(pcert.signatureAlgorithmID, kRSAWithSHA1AlgorithmID); + CAssert(pcert.signature != nil); + Log(@"Subject Public Key = %@", pcert.subjectPublicKey); + CAssert(pcert.subjectPublicKey); + if (selfSigned) { + Log(@"Issuer Public Key = %@", pcert.issuerPublicKey); + CAssert(pcert.issuerPublicKey); + + 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); + return pcert; +} + + TestCase(ParsedCert) { - auto void testCert(NSString *filename, BOOL selfSigned); testCert(@"../../Tests/selfsigned.cer", YES); testCert(@"../../Tests/iphonedev.cer", NO); - auto void testCert(NSString *filename, BOOL selfSigned) { - Log(@"--- Creating MYParsedCertificate from %@", filename); - NSData *certData = [NSData dataWithContentsOfFile: filename]; - //Log(@"Cert Data =\n%@", certData); - NSError *error = nil; - MYParsedCertificate *pcert = [[MYParsedCertificate alloc] initWithCertificateData: certData - error: &error]; - CAssertNil(error); - CAssert(pcert != nil); - - CAssertEq(pcert.isSelfSigned, selfSigned); - - NSData *signedData = pcert.signedData; - //Log(@"Signed Data = (length=%x)\n%@", signedData.length, signedData); - CAssertEqual(signedData, [certData subdataWithRange: NSMakeRange(4,signedData.length)]); - - Log(@"AlgID = %@", pcert.signatureAlgorithmID); - Log(@"Signature = %@", pcert.signature); - CAssertEqual(pcert.signatureAlgorithmID, [MYParsedCertificate RSAWithSHA1AlgorithmID]); - CAssert(pcert.signature != nil); - Log(@"Subject Public Key = %@", pcert.subjectPublicKey); - CAssert(pcert.subjectPublicKey); - if (selfSigned) { - Log(@"Issuer Public Key = %@", pcert.issuerPublicKey); - CAssert(pcert.issuerPublicKey); - - CAssert(pcert.validateSignature); - } - } -} +} + + +#import "MYKeychain.h" + +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"; + + 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"); + + Log(@"Signing..."); + NSError *error; + CAssert([pcert selfSignWithPrivateKey: privateKey error: &error]); + CAssertNil(error); + NSData *certData = pcert.certificateData; + Log(@"Generated cert = \n%@", certData); + CAssert(certData); + [certData writeToFile: @"../../Tests/generated.cer" atomically: YES]; + 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"); +} + +#endif + @@ -215,8 +395,8 @@ Sequence: <-- top Sequence: <-- info - MYASN1Object[2/0]: <-- version (int, constructed) - 2 + MYASN1Object[2/0]: <-- version (tag=0, constructed) + 2 1 <-- serial number Sequence: {1 2 840 113549 1 1 1} <-- algorithm ID @@ -246,23 +426,23 @@ 2010-04-13 21:54:35 -0700 Sequence: <-- subject Set: - Sequence: + Sequence: <-- surname {2 5 4 4} Widdershins Set: - Sequence: + Sequence: <-- email {1 2 840 113549 1 9 1} waldo@example.com Set: - Sequence: + Sequence: <-- common name {2 5 4 3} waldo Set: - Sequence: + Sequence: <-- first name {2 5 4 42} Waldo Set: - Sequence: + Sequence: <-- description {2 5 4 13} Just a fictitious person Sequence: <-- public key info