Whew! MYParsedCertificate can now generate certs from scratch. Also added improvements and fixes to the BER/DER codecs.
5 // Created by Jens Alfke on 6/2/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYBERParser.h"
10 #import "MYASN1Object.h"
12 #import "MYErrorUtils.h"
13 #import "CollectionUtils.h"
17 #define MYBERParserException @"MYBERParserException"
23 unsigned isConstructed :1;
26 unsigned isLengthLong :1;
30 const uint8_t *nextChar;
35 static void requireLength (size_t length, size_t expectedLength) {
36 if (length != expectedLength)
37 [NSException raise: MYBERParserException format: @"Unexpected value length"];
41 static const void* readOrDie (InputData *input, size_t len) {
42 if (len > input->length)
43 [NSException raise: MYBERParserException format: @"Unexpected EOF on input"];
44 const void *bytes = input->nextChar;
45 input->nextChar += len;
51 static NSData* readDataOrDie(InputData *input, size_t length) {
52 return [NSMutableData dataWithBytes: readOrDie(input,length) length: length];
56 static NSString* readStringOrDie(InputData *input, size_t length, NSStringEncoding encoding) {
57 NSString *str = [[NSString alloc] initWithBytes: readOrDie(input,length)
61 [NSException raise: MYBERParserException format: @"Unparseable string"];
62 return [str autorelease];
66 static uint32_t readBigEndianUnsignedInteger (InputData *input, size_t length) {
67 if (length == 0 || length > 4)
68 [NSException raise: MYBERParserException format: @"Invalid integer length"];
70 memcpy(((uint8_t*)&result)+(4-length), readOrDie(input, length), length);
74 static int32_t readBigEndianSignedInteger (InputData *input, size_t length) {
75 int32_t result = (int32_t) readBigEndianUnsignedInteger(input,length);
76 uint8_t *dst = ((uint8_t*)&result)+(4-length);
77 if (*dst & 0x80) { // sign-extend negative value
78 while (--dst >= (uint8_t*)&result)
85 NSDateFormatter* MYBERGeneralizedTimeFormatter() {
86 static NSDateFormatter *sFmt;
88 sFmt = [[NSDateFormatter alloc] init];
89 sFmt.dateFormat = @"yyyyMMddHHmmss'Z'";
90 sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
95 NSDateFormatter* MYBERUTCTimeFormatter() {
96 static NSDateFormatter *sFmt;
98 sFmt = [[NSDateFormatter alloc] init];
99 sFmt.dateFormat = @"yyMMddHHmmss'Z'";
100 sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
105 static NSDate* parseDate (NSString *dateStr, unsigned tag) {
106 NSDateFormatter *fmt = (tag==23 ?MYBERUTCTimeFormatter() :MYBERGeneralizedTimeFormatter());
107 NSDate *date = [fmt dateFromString: dateStr];
109 [NSException raise: MYBERParserException format: @"Unparseable date '%@'", dateStr];
114 static size_t readHeader(InputData *input, BERHeader *header) {
115 memcpy(header, readOrDie(input,2), 2);
116 if (header->tag == 0x1F)
117 [NSException raise: MYBERParserException format: @"Long tags not supported"];
118 if (!header->isLengthLong)
119 return header->length;
121 if (header->length == 0)
122 [NSException raise: MYBERParserException format: @"Indefinite length not supported"];
123 return NSSwapBigIntToHost(readBigEndianUnsignedInteger(input,header->length));
128 static id parseBER(InputData *input) {
130 size_t length = readHeader(input,&header);
132 Class defaultClass = [MYASN1Object class];
134 // Tag values can be found in <Security/x509defs.h>. I'm not using them here because that
135 // header does not exist on iPhone!
137 if (header.isConstructed) {
139 NSMutableArray *items = $marray();
140 InputData subInput = {input->nextChar, length};
141 while (subInput.length > 0) {
142 [items addObject: parseBER(&subInput)];
144 input->nextChar += length;
145 input->length -= length;
147 switch (header.tag) {
151 return [NSSet setWithArray: items];
153 return [[[MYASN1Object alloc] initWithTag: header.tag
154 ofClass: header.tagClass
155 components: items] autorelease];
159 switch (header.tag) {
161 requireLength(length,1);
162 return *(const uint8_t*)readOrDie(input, 1) ?$true :$false;
168 int32_t value = NSSwapBigIntToHost(readBigEndianSignedInteger(input,length));
169 return [NSNumber numberWithInteger: value];
172 defaultClass = [MYASN1BigInteger class];
178 UInt8 unusedBits = *(const UInt8*) readOrDie(input, 1);
180 Log(@"Bit-string has %u unused bits", (unsigned)unusedBits);
181 if (unusedBits > 7 || length < 1)
182 [NSException raise: MYBERParserException format: @"Bogus bit-string"];
183 return [[[MYBitString alloc] initWithBits: readDataOrDie(input, length-1)
184 count: 8*(length-1) - unusedBits] autorelease];
186 case 4: // octetstring
187 return readDataOrDie(input, length);
189 requireLength(length,0);
190 return [NSNull null];
192 return [[[MYOID alloc] initWithBEREncoding: readDataOrDie(input, length)] autorelease];
193 case 12: // UTF8String
194 return readStringOrDie(input,length,NSUTF8StringEncoding);
195 case 18: // numeric string
196 case 19: // printable string:
197 case 22: // IA5 string:
198 case 20: // T61 string:
200 NSString *string = readStringOrDie(input,length,NSASCIIStringEncoding);
204 break; // if decoding fails, fall back to generic MYASN1Object
206 case 23: // UTC time:
207 case 24: // Generalized time:
208 return parseDate(readStringOrDie(input,length,NSASCIIStringEncoding), header.tag);
214 // Generic case -- create and return a MYASN1Object:
215 NSData *value = readDataOrDie(input, length);
216 id result = [[[defaultClass alloc] initWithTag: header.tag
217 ofClass: header.tagClass
218 constructed: header.isConstructed
219 value: value] autorelease];
220 if( defaultClass == [MYASN1Object class])
221 Warn(@"parseBER: Returning default %@", result);
226 static void exceptionToError (NSException *x, NSError **outError) {
227 if ($equal(x.name, MYBERParserException)) {
229 *outError = MYError(1,MYASN1ErrorDomain, @"%@", x.reason);
236 id MYBERParse (NSData *ber, NSError **outError) {
238 InputData input = {ber.bytes, ber.length};
239 return parseBER(&input);
240 }@catch (NSException *x) {
241 exceptionToError(x,outError);
247 size_t MYBERGetLength (NSData *ber, NSError **outError) {
249 InputData input = {ber.bytes, ber.length};
251 return readHeader(&input,&header);
252 }@catch (NSException *x) {
253 exceptionToError(x,outError);
258 const void* MYBERGetContents (NSData *ber, NSError **outError) {
260 InputData input = {ber.bytes, ber.length};
262 readHeader(&input,&header);
263 return input.nextChar;
264 }@catch (NSException *x) {
265 exceptionToError(x,outError);
273 #pragma mark TEST CASES:
276 #define $data(BYTES...) ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
279 CAssertEqual(MYBERParse($data(0x05, 0x00), nil),
281 CAssertEqual(MYBERParse($data(0x01, 0x01, 0xFF), nil),
283 CAssertEqual(MYBERParse($data(0x01, 0x01, 0x00), nil),
287 CAssertEqual(MYBERParse($data(0x02, 0x01, 0x00), nil),
289 CAssertEqual(MYBERParse($data(0x02, 0x01, 0x48), nil),
291 CAssertEqual(MYBERParse($data(0x02, 0x01, 0x80), nil),
293 CAssertEqual(MYBERParse($data(0x02, 0x02, 0x00, 0x80), nil),
295 CAssertEqual(MYBERParse($data(0x02, 0x02, 0x30,0x39), nil),
297 CAssertEqual(MYBERParse($data(0x02, 0x02, 0xCF, 0xC7), nil),
299 CAssertEqual(MYBERParse($data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15), nil),
301 CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
302 $object(-123456789));
303 CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
304 $object(-123456789));
307 CAssertEqual(MYBERParse($data(0x04, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
308 [@"hello" dataUsingEncoding: NSASCIIStringEncoding]);
309 CAssertEqual(MYBERParse($data(0x04, 0x00), nil),
311 CAssertEqual(MYBERParse($data(0x0C, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
315 CAssertEqual(MYBERParse($data(0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF), nil),
316 $array($object(72), $true));
317 CAssertEqual(MYBERParse($data(0x30, 0x10,
318 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF,
319 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF), nil),
320 $array( $array($object(72), $true), $array($object(72), $true)));
324 #import "MYCertificate.h"
325 #import "MYPublicKey.h"
327 TestCase(ParseCert) {
328 NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];
329 NSError *error = nil;
330 id parsed = MYBERParse(cert,&error);
333 NSString *dump = [MYASN1Object dump: parsed];
335 Log(@"Parsed selfsigned.cer:\n%@", dump);
337 MYCertificate *myCert = [[MYCertificate alloc] initWithCertificateData: cert];
339 id parsedPubKey = MYBERParse(myCert.publicKey.keyData, NULL);
340 Log(@"Parsed public key:\n%@", [MYASN1Object dump: parsedPubKey]);
342 cert = [NSData dataWithContentsOfFile: @"../../Tests/iphonedev.cer"];
343 parsed = MYBERParse(cert,&error);
346 dump = [MYASN1Object dump: parsed];