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.
5 // Created by Jens Alfke on 6/2/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
10 // <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
12 #import "MYBERParser.h"
13 #import "MYASN1Object.h"
15 #import "MYErrorUtils.h"
16 #import "CollectionUtils.h"
20 #define MYBERParserException @"MYBERParserException"
26 unsigned isConstructed :1;
29 unsigned isLengthLong :1;
33 const uint8_t *nextChar;
38 static void requireLength (size_t length, size_t expectedLength) {
39 if (length != expectedLength)
40 [NSException raise: MYBERParserException format: @"Unexpected value length"];
44 static const void* readOrDie (InputData *input, size_t len) {
45 if (len > input->length)
46 [NSException raise: MYBERParserException format: @"Unexpected EOF on input"];
47 const void *bytes = input->nextChar;
48 input->nextChar += len;
54 static NSData* readDataOrDie(InputData *input, size_t length) {
55 return [NSMutableData dataWithBytes: readOrDie(input,length) length: length];
59 static NSString* readStringOrDie(InputData *input, size_t length, NSStringEncoding encoding) {
60 NSString *str = [[NSString alloc] initWithBytes: readOrDie(input,length)
64 [NSException raise: MYBERParserException format: @"Unparseable string"];
65 return [str autorelease];
69 static uint32_t readBigEndianUnsignedInteger (InputData *input, size_t length) {
70 if (length == 0 || length > 4)
71 [NSException raise: MYBERParserException format: @"Invalid integer length"];
73 memcpy(((uint8_t*)&result)+(4-length), readOrDie(input, length), length);
77 static int32_t readBigEndianSignedInteger (InputData *input, size_t length) {
78 int32_t result = (int32_t) readBigEndianUnsignedInteger(input,length);
79 uint8_t *dst = ((uint8_t*)&result)+(4-length);
80 if (*dst & 0x80) { // sign-extend negative value
81 while (--dst >= (uint8_t*)&result)
88 NSDateFormatter* MYBERGeneralizedTimeFormatter() {
89 static NSDateFormatter *sFmt;
91 sFmt = [[NSDateFormatter alloc] init];
92 sFmt.dateFormat = @"yyyyMMddHHmmss'Z'";
93 sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
98 NSDateFormatter* MYBERUTCTimeFormatter() {
99 static NSDateFormatter *sFmt;
101 sFmt = [[NSDateFormatter alloc] init];
102 sFmt.dateFormat = @"yyMMddHHmmss'Z'";
103 sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
108 static NSDate* parseDate (NSString *dateStr, unsigned tag) {
109 //FIX: There are more date formats possible; need to try them all. (see "Layman's Guide", 5.17)
110 NSDateFormatter *fmt = (tag==23 ?MYBERUTCTimeFormatter() :MYBERGeneralizedTimeFormatter());
111 NSDate *date = [fmt dateFromString: dateStr];
113 [NSException raise: MYBERParserException format: @"Unparseable date '%@'", dateStr];
118 static size_t readHeader(InputData *input, BERHeader *header) {
119 memcpy(header, readOrDie(input,2), 2);
120 if (header->tag == 0x1F)
121 [NSException raise: MYBERParserException format: @"Long tags not supported"];
122 if (!header->isLengthLong)
123 return header->length;
125 if (header->length == 0)
126 [NSException raise: MYBERParserException format: @"Indefinite length not supported"];
127 return NSSwapBigIntToHost(readBigEndianUnsignedInteger(input,header->length));
132 static id parseBER(InputData *input) {
134 size_t length = readHeader(input,&header);
136 Class defaultClass = [MYASN1Object class];
138 // Tag values can be found in <Security/x509defs.h>. I'm not using them here because that
139 // header does not exist on iPhone!
141 if (header.isConstructed) {
143 NSMutableArray *items = $marray();
144 InputData subInput = {input->nextChar, length};
145 while (subInput.length > 0) {
146 [items addObject: parseBER(&subInput)];
148 input->nextChar += length;
149 input->length -= length;
151 switch (header.tag) {
155 return [NSSet setWithArray: items];
157 return [[[MYASN1Object alloc] initWithTag: header.tag
158 ofClass: header.tagClass
159 components: items] autorelease];
163 switch (header.tag) {
165 requireLength(length,1);
166 return *(const uint8_t*)readOrDie(input, 1) ?$true :$false;
172 int32_t value = NSSwapBigIntToHost(readBigEndianSignedInteger(input,length));
173 return [NSNumber numberWithInteger: value];
176 defaultClass = [MYASN1BigInteger class];
182 UInt8 unusedBits = *(const UInt8*) readOrDie(input, 1);
184 Log(@"Bit-string has %u unused bits", (unsigned)unusedBits);
185 if (unusedBits > 7 || length < 1)
186 [NSException raise: MYBERParserException format: @"Bogus bit-string"];
187 return [[[MYBitString alloc] initWithBits: readDataOrDie(input, length-1)
188 count: 8*(length-1) - unusedBits] autorelease];
190 case 4: // octetstring
191 return readDataOrDie(input, length);
193 requireLength(length,0);
194 return [NSNull null];
196 return [[[MYOID alloc] initWithBEREncoding: readDataOrDie(input, length)] autorelease];
197 case 12: // UTF8String
198 return readStringOrDie(input,length,NSUTF8StringEncoding);
199 case 18: // numeric string
200 case 19: // printable string:
201 case 22: // IA5 string:
202 case 20: // T61 string:
204 NSString *string = readStringOrDie(input,length,NSASCIIStringEncoding);
208 break; // if decoding fails, fall back to generic MYASN1Object
210 case 23: // UTC time:
211 case 24: // Generalized time:
212 return parseDate(readStringOrDie(input,length,NSASCIIStringEncoding), header.tag);
218 // Generic case -- create and return a MYASN1Object:
219 NSData *value = readDataOrDie(input, length);
220 id result = [[[defaultClass alloc] initWithTag: header.tag
221 ofClass: header.tagClass
222 constructed: header.isConstructed
223 value: value] autorelease];
224 if( defaultClass == [MYASN1Object class])
225 Warn(@"parseBER: Returning default %@", result);
230 static void exceptionToError (NSException *x, NSError **outError) {
231 if ($equal(x.name, MYBERParserException)) {
233 *outError = MYError(1,MYASN1ErrorDomain, @"%@", x.reason);
240 id MYBERParse (NSData *ber, NSError **outError) {
242 InputData input = {ber.bytes, ber.length};
243 return parseBER(&input);
244 }@catch (NSException *x) {
245 exceptionToError(x,outError);
251 size_t MYBERGetLength (NSData *ber, NSError **outError) {
253 InputData input = {ber.bytes, ber.length};
255 return readHeader(&input,&header);
256 }@catch (NSException *x) {
257 exceptionToError(x,outError);
262 const void* MYBERGetContents (NSData *ber, NSError **outError) {
264 InputData input = {ber.bytes, ber.length};
266 readHeader(&input,&header);
267 return input.nextChar;
268 }@catch (NSException *x) {
269 exceptionToError(x,outError);
277 #pragma mark TEST CASES:
280 #define $data(BYTES...) ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
283 CAssertEqual(MYBERParse($data(0x05, 0x00), nil),
285 CAssertEqual(MYBERParse($data(0x01, 0x01, 0xFF), nil),
287 CAssertEqual(MYBERParse($data(0x01, 0x01, 0x00), nil),
291 CAssertEqual(MYBERParse($data(0x02, 0x01, 0x00), nil),
293 CAssertEqual(MYBERParse($data(0x02, 0x01, 0x48), nil),
295 CAssertEqual(MYBERParse($data(0x02, 0x01, 0x80), nil),
297 CAssertEqual(MYBERParse($data(0x02, 0x02, 0x00, 0x80), nil),
299 CAssertEqual(MYBERParse($data(0x02, 0x02, 0x30,0x39), nil),
301 CAssertEqual(MYBERParse($data(0x02, 0x02, 0xCF, 0xC7), nil),
303 CAssertEqual(MYBERParse($data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15), nil),
305 CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
306 $object(-123456789));
307 CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
308 $object(-123456789));
311 CAssertEqual(MYBERParse($data(0x04, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
312 [@"hello" dataUsingEncoding: NSASCIIStringEncoding]);
313 CAssertEqual(MYBERParse($data(0x04, 0x00), nil),
315 CAssertEqual(MYBERParse($data(0x0C, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
319 CAssertEqual(MYBERParse($data(0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF), nil),
320 $array($object(72), $true));
321 CAssertEqual(MYBERParse($data(0x30, 0x10,
322 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF,
323 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF), nil),
324 $array( $array($object(72), $true), $array($object(72), $true)));
328 #import "MYCertificate.h"
329 #import "MYPublicKey.h"
331 TestCase(ParseCert) {
332 NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];
333 NSError *error = nil;
334 id parsed = MYBERParse(cert,&error);
337 NSString *dump = [MYASN1Object dump: parsed];
339 Log(@"Parsed selfsigned.cer:\n%@", dump);
341 MYCertificate *myCert = [[MYCertificate alloc] initWithCertificateData: cert];
343 id parsedPubKey = MYBERParse(myCert.publicKey.keyData, NULL);
344 Log(@"Parsed public key:\n%@", [MYASN1Object dump: parsedPubKey]);
346 cert = [NSData dataWithContentsOfFile: @"../../Tests/iphonedev.cer"];
347 parsed = MYBERParse(cert,&error);
350 dump = [MYASN1Object dump: parsed];