Changed the X.509 version number in generated certs from 1 to 3, so that SecCertificateCreateFromData on iPhone will accept them. :-/
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];
357 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
359 Redistribution and use in source and binary forms, with or without modification, are permitted
360 provided that the following conditions are met:
362 * Redistributions of source code must retain the above copyright notice, this list of conditions
363 and the following disclaimer.
364 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
365 and the following disclaimer in the documentation and/or other materials provided with the
368 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
369 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
370 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
371 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
372 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
373 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
374 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
375 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.