jens@16
|
1 |
//
|
jens@16
|
2 |
// MYBERParser.m
|
jens@16
|
3 |
// MYCrypto
|
jens@16
|
4 |
//
|
jens@16
|
5 |
// Created by Jens Alfke on 6/2/09.
|
jens@16
|
6 |
// Copyright 2009 Jens Alfke. All rights reserved.
|
jens@16
|
7 |
//
|
jens@16
|
8 |
|
jens@20
|
9 |
// Reference:
|
jens@20
|
10 |
// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
|
jens@20
|
11 |
|
jens@16
|
12 |
#import "MYBERParser.h"
|
jens@16
|
13 |
#import "MYASN1Object.h"
|
jens@16
|
14 |
#import "MYOID.h"
|
jens@16
|
15 |
#import "MYErrorUtils.h"
|
jens@16
|
16 |
#import "CollectionUtils.h"
|
jens@16
|
17 |
#import "Test.h"
|
jens@16
|
18 |
|
jens@16
|
19 |
|
jens@16
|
20 |
#define MYBERParserException @"MYBERParserException"
|
jens@16
|
21 |
|
jens@16
|
22 |
|
jens@16
|
23 |
|
jens@16
|
24 |
typedef struct {
|
jens@17
|
25 |
unsigned tag :5;
|
jens@17
|
26 |
unsigned isConstructed :1;
|
jens@17
|
27 |
unsigned tagClass :2;
|
jens@17
|
28 |
unsigned length :7;
|
jens@17
|
29 |
unsigned isLengthLong :1;
|
jens@17
|
30 |
} BERHeader;
|
jens@17
|
31 |
|
jens@17
|
32 |
typedef struct {
|
jens@16
|
33 |
const uint8_t *nextChar;
|
jens@16
|
34 |
size_t length;
|
jens@16
|
35 |
} InputData;
|
jens@16
|
36 |
|
jens@16
|
37 |
|
jens@16
|
38 |
static void requireLength (size_t length, size_t expectedLength) {
|
jens@16
|
39 |
if (length != expectedLength)
|
jens@16
|
40 |
[NSException raise: MYBERParserException format: @"Unexpected value length"];
|
jens@16
|
41 |
}
|
jens@16
|
42 |
|
jens@16
|
43 |
|
jens@16
|
44 |
static const void* readOrDie (InputData *input, size_t len) {
|
jens@16
|
45 |
if (len > input->length)
|
jens@16
|
46 |
[NSException raise: MYBERParserException format: @"Unexpected EOF on input"];
|
jens@16
|
47 |
const void *bytes = input->nextChar;
|
jens@16
|
48 |
input->nextChar += len;
|
jens@16
|
49 |
input->length -= len;
|
jens@16
|
50 |
return bytes;
|
jens@16
|
51 |
}
|
jens@16
|
52 |
|
jens@16
|
53 |
|
jens@16
|
54 |
static NSData* readDataOrDie(InputData *input, size_t length) {
|
jens@16
|
55 |
return [NSMutableData dataWithBytes: readOrDie(input,length) length: length];
|
jens@16
|
56 |
}
|
jens@16
|
57 |
|
jens@16
|
58 |
|
jens@16
|
59 |
static NSString* readStringOrDie(InputData *input, size_t length, NSStringEncoding encoding) {
|
jens@16
|
60 |
NSString *str = [[NSString alloc] initWithBytes: readOrDie(input,length)
|
jens@16
|
61 |
length: length
|
jens@16
|
62 |
encoding: encoding];
|
jens@16
|
63 |
if (!str)
|
jens@16
|
64 |
[NSException raise: MYBERParserException format: @"Unparseable string"];
|
jens@16
|
65 |
return [str autorelease];
|
jens@16
|
66 |
}
|
jens@16
|
67 |
|
jens@16
|
68 |
|
jens@16
|
69 |
static uint32_t readBigEndianUnsignedInteger (InputData *input, size_t length) {
|
jens@16
|
70 |
if (length == 0 || length > 4)
|
jens@16
|
71 |
[NSException raise: MYBERParserException format: @"Invalid integer length"];
|
jens@16
|
72 |
uint32_t result = 0;
|
jens@16
|
73 |
memcpy(((uint8_t*)&result)+(4-length), readOrDie(input, length), length);
|
jens@16
|
74 |
return result;
|
jens@16
|
75 |
}
|
jens@16
|
76 |
|
jens@16
|
77 |
static int32_t readBigEndianSignedInteger (InputData *input, size_t length) {
|
jens@16
|
78 |
int32_t result = (int32_t) readBigEndianUnsignedInteger(input,length);
|
jens@16
|
79 |
uint8_t *dst = ((uint8_t*)&result)+(4-length);
|
jens@16
|
80 |
if (*dst & 0x80) { // sign-extend negative value
|
jens@16
|
81 |
while (--dst >= (uint8_t*)&result)
|
jens@16
|
82 |
*dst = 0xFF;
|
jens@16
|
83 |
}
|
jens@16
|
84 |
return result;
|
jens@16
|
85 |
}
|
jens@16
|
86 |
|
jens@16
|
87 |
|
jens@16
|
88 |
NSDateFormatter* MYBERGeneralizedTimeFormatter() {
|
jens@16
|
89 |
static NSDateFormatter *sFmt;
|
jens@16
|
90 |
if (!sFmt) {
|
jens@16
|
91 |
sFmt = [[NSDateFormatter alloc] init];
|
jens@16
|
92 |
sFmt.dateFormat = @"yyyyMMddHHmmss'Z'";
|
jens@16
|
93 |
sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
|
jens@16
|
94 |
}
|
jens@16
|
95 |
return sFmt;
|
jens@16
|
96 |
}
|
jens@16
|
97 |
|
jens@16
|
98 |
NSDateFormatter* MYBERUTCTimeFormatter() {
|
jens@16
|
99 |
static NSDateFormatter *sFmt;
|
jens@16
|
100 |
if (!sFmt) {
|
jens@16
|
101 |
sFmt = [[NSDateFormatter alloc] init];
|
jens@16
|
102 |
sFmt.dateFormat = @"yyMMddHHmmss'Z'";
|
jens@16
|
103 |
sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
|
jens@16
|
104 |
}
|
jens@16
|
105 |
return sFmt;
|
jens@16
|
106 |
}
|
jens@16
|
107 |
|
jens@16
|
108 |
static NSDate* parseDate (NSString *dateStr, unsigned tag) {
|
jens@20
|
109 |
//FIX: There are more date formats possible; need to try them all. (see "Layman's Guide", 5.17)
|
jens@16
|
110 |
NSDateFormatter *fmt = (tag==23 ?MYBERUTCTimeFormatter() :MYBERGeneralizedTimeFormatter());
|
jens@16
|
111 |
NSDate *date = [fmt dateFromString: dateStr];
|
jens@16
|
112 |
if (!date)
|
jens@16
|
113 |
[NSException raise: MYBERParserException format: @"Unparseable date '%@'", dateStr];
|
jens@16
|
114 |
return date;
|
jens@16
|
115 |
}
|
jens@16
|
116 |
|
jens@16
|
117 |
|
jens@17
|
118 |
static size_t readHeader(InputData *input, BERHeader *header) {
|
jens@17
|
119 |
memcpy(header, readOrDie(input,2), 2);
|
jens@17
|
120 |
if (header->tag == 0x1F)
|
jens@17
|
121 |
[NSException raise: MYBERParserException format: @"Long tags not supported"];
|
jens@17
|
122 |
if (!header->isLengthLong)
|
jens@17
|
123 |
return header->length;
|
jens@17
|
124 |
else {
|
jens@17
|
125 |
if (header->length == 0)
|
jens@17
|
126 |
[NSException raise: MYBERParserException format: @"Indefinite length not supported"];
|
jens@17
|
127 |
return NSSwapBigIntToHost(readBigEndianUnsignedInteger(input,header->length));
|
jens@17
|
128 |
}
|
jens@17
|
129 |
}
|
jens@17
|
130 |
|
jens@17
|
131 |
|
jens@16
|
132 |
static id parseBER(InputData *input) {
|
jens@17
|
133 |
BERHeader header;
|
jens@17
|
134 |
size_t length = readHeader(input,&header);
|
jens@16
|
135 |
|
jens@16
|
136 |
Class defaultClass = [MYASN1Object class];
|
jens@16
|
137 |
|
jens@16
|
138 |
// Tag values can be found in <Security/x509defs.h>. I'm not using them here because that
|
jens@16
|
139 |
// header does not exist on iPhone!
|
jens@16
|
140 |
|
jens@16
|
141 |
if (header.isConstructed) {
|
jens@16
|
142 |
// Constructed:
|
jens@16
|
143 |
NSMutableArray *items = $marray();
|
jens@16
|
144 |
InputData subInput = {input->nextChar, length};
|
jens@16
|
145 |
while (subInput.length > 0) {
|
jens@16
|
146 |
[items addObject: parseBER(&subInput)];
|
jens@16
|
147 |
}
|
jens@16
|
148 |
input->nextChar += length;
|
jens@16
|
149 |
input->length -= length;
|
jens@16
|
150 |
|
jens@16
|
151 |
switch (header.tag) {
|
jens@16
|
152 |
case 16: // sequence
|
jens@16
|
153 |
return items;
|
jens@16
|
154 |
case 17: // set
|
jens@16
|
155 |
return [NSSet setWithArray: items];
|
jens@16
|
156 |
default:
|
jens@16
|
157 |
return [[[MYASN1Object alloc] initWithTag: header.tag
|
jens@16
|
158 |
ofClass: header.tagClass
|
jens@16
|
159 |
components: items] autorelease];
|
jens@16
|
160 |
}
|
jens@16
|
161 |
} else {
|
jens@16
|
162 |
// Primitive:
|
jens@16
|
163 |
switch (header.tag) {
|
jens@16
|
164 |
case 1: { // boolean
|
jens@16
|
165 |
requireLength(length,1);
|
jens@16
|
166 |
return *(const uint8_t*)readOrDie(input, 1) ?$true :$false;
|
jens@16
|
167 |
}
|
jens@16
|
168 |
case 2: // integer
|
jens@16
|
169 |
case 10: // enum
|
jens@16
|
170 |
{
|
jens@16
|
171 |
if (length <= 4) {
|
jens@16
|
172 |
int32_t value = NSSwapBigIntToHost(readBigEndianSignedInteger(input,length));
|
jens@16
|
173 |
return [NSNumber numberWithInteger: value];
|
jens@16
|
174 |
} else {
|
jens@16
|
175 |
// Big integer!
|
jens@16
|
176 |
defaultClass = [MYASN1BigInteger class];
|
jens@16
|
177 |
break;
|
jens@16
|
178 |
}
|
jens@16
|
179 |
}
|
jens@16
|
180 |
case 3: // bitstring
|
jens@16
|
181 |
{
|
jens@16
|
182 |
UInt8 unusedBits = *(const UInt8*) readOrDie(input, 1);
|
jens@16
|
183 |
if (unusedBits)
|
jens@16
|
184 |
Log(@"Bit-string has %u unused bits", (unsigned)unusedBits);
|
jens@16
|
185 |
if (unusedBits > 7 || length < 1)
|
jens@16
|
186 |
[NSException raise: MYBERParserException format: @"Bogus bit-string"];
|
jens@16
|
187 |
return [[[MYBitString alloc] initWithBits: readDataOrDie(input, length-1)
|
jens@16
|
188 |
count: 8*(length-1) - unusedBits] autorelease];
|
jens@16
|
189 |
}
|
jens@16
|
190 |
case 4: // octetstring
|
jens@16
|
191 |
return readDataOrDie(input, length);
|
jens@16
|
192 |
case 5: // null
|
jens@16
|
193 |
requireLength(length,0);
|
jens@16
|
194 |
return [NSNull null];
|
jens@16
|
195 |
case 6: // OID
|
jens@16
|
196 |
return [[[MYOID alloc] initWithBEREncoding: readDataOrDie(input, length)] autorelease];
|
jens@16
|
197 |
case 12: // UTF8String
|
jens@16
|
198 |
return readStringOrDie(input,length,NSUTF8StringEncoding);
|
jens@16
|
199 |
case 18: // numeric string
|
jens@16
|
200 |
case 19: // printable string:
|
jens@19
|
201 |
case 22: // IA5 string:
|
jens@19
|
202 |
case 20: // T61 string:
|
jens@19
|
203 |
{
|
jens@19
|
204 |
NSString *string = readStringOrDie(input,length,NSASCIIStringEncoding);
|
jens@19
|
205 |
if (string)
|
jens@19
|
206 |
return string;
|
jens@19
|
207 |
else
|
jens@19
|
208 |
break; // if decoding fails, fall back to generic MYASN1Object
|
jens@19
|
209 |
}
|
jens@16
|
210 |
case 23: // UTC time:
|
jens@16
|
211 |
case 24: // Generalized time:
|
jens@16
|
212 |
return parseDate(readStringOrDie(input,length,NSASCIIStringEncoding), header.tag);
|
jens@16
|
213 |
default:
|
jens@16
|
214 |
break;
|
jens@16
|
215 |
}
|
jens@16
|
216 |
}
|
jens@16
|
217 |
|
jens@16
|
218 |
// Generic case -- create and return a MYASN1Object:
|
jens@16
|
219 |
NSData *value = readDataOrDie(input, length);
|
jens@16
|
220 |
id result = [[[defaultClass alloc] initWithTag: header.tag
|
jens@16
|
221 |
ofClass: header.tagClass
|
jens@16
|
222 |
constructed: header.isConstructed
|
jens@16
|
223 |
value: value] autorelease];
|
jens@16
|
224 |
if( defaultClass == [MYASN1Object class])
|
jens@16
|
225 |
Warn(@"parseBER: Returning default %@", result);
|
jens@16
|
226 |
return result;
|
jens@16
|
227 |
}
|
jens@17
|
228 |
|
jens@17
|
229 |
|
jens@17
|
230 |
static void exceptionToError (NSException *x, NSError **outError) {
|
jens@17
|
231 |
if ($equal(x.name, MYBERParserException)) {
|
jens@17
|
232 |
if (outError)
|
jens@17
|
233 |
*outError = MYError(1,MYASN1ErrorDomain, @"%@", x.reason);
|
jens@17
|
234 |
} else {
|
jens@17
|
235 |
@throw(x);
|
jens@17
|
236 |
}
|
jens@17
|
237 |
}
|
jens@16
|
238 |
|
jens@16
|
239 |
|
jens@16
|
240 |
id MYBERParse (NSData *ber, NSError **outError) {
|
jens@16
|
241 |
@try{
|
jens@16
|
242 |
InputData input = {ber.bytes, ber.length};
|
jens@16
|
243 |
return parseBER(&input);
|
jens@16
|
244 |
}@catch (NSException *x) {
|
jens@17
|
245 |
exceptionToError(x,outError);
|
jens@16
|
246 |
}
|
jens@16
|
247 |
return nil;
|
jens@16
|
248 |
}
|
jens@16
|
249 |
|
jens@16
|
250 |
|
jens@17
|
251 |
size_t MYBERGetLength (NSData *ber, NSError **outError) {
|
jens@17
|
252 |
@try{
|
jens@17
|
253 |
InputData input = {ber.bytes, ber.length};
|
jens@17
|
254 |
BERHeader header;
|
jens@17
|
255 |
return readHeader(&input,&header);
|
jens@17
|
256 |
}@catch (NSException *x) {
|
jens@17
|
257 |
exceptionToError(x,outError);
|
jens@17
|
258 |
}
|
jens@17
|
259 |
return 0;
|
jens@17
|
260 |
}
|
jens@17
|
261 |
|
jens@17
|
262 |
const void* MYBERGetContents (NSData *ber, NSError **outError) {
|
jens@17
|
263 |
@try{
|
jens@17
|
264 |
InputData input = {ber.bytes, ber.length};
|
jens@17
|
265 |
BERHeader header;
|
jens@17
|
266 |
readHeader(&input,&header);
|
jens@17
|
267 |
return input.nextChar;
|
jens@17
|
268 |
}@catch (NSException *x) {
|
jens@17
|
269 |
exceptionToError(x,outError);
|
jens@17
|
270 |
}
|
jens@17
|
271 |
return NULL;
|
jens@17
|
272 |
}
|
jens@17
|
273 |
|
jens@16
|
274 |
|
jens@16
|
275 |
|
jens@16
|
276 |
#pragma mark -
|
jens@16
|
277 |
#pragma mark TEST CASES:
|
jens@16
|
278 |
|
jens@16
|
279 |
|
jens@16
|
280 |
#define $data(BYTES...) ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
|
jens@16
|
281 |
|
jens@16
|
282 |
TestCase(ParseBER) {
|
jens@16
|
283 |
CAssertEqual(MYBERParse($data(0x05, 0x00), nil),
|
jens@16
|
284 |
[NSNull null]);
|
jens@16
|
285 |
CAssertEqual(MYBERParse($data(0x01, 0x01, 0xFF), nil),
|
jens@16
|
286 |
$true);
|
jens@16
|
287 |
CAssertEqual(MYBERParse($data(0x01, 0x01, 0x00), nil),
|
jens@16
|
288 |
$false);
|
jens@16
|
289 |
|
jens@16
|
290 |
// integers:
|
jens@16
|
291 |
CAssertEqual(MYBERParse($data(0x02, 0x01, 0x00), nil),
|
jens@16
|
292 |
$object(0));
|
jens@16
|
293 |
CAssertEqual(MYBERParse($data(0x02, 0x01, 0x48), nil),
|
jens@16
|
294 |
$object(72));
|
jens@16
|
295 |
CAssertEqual(MYBERParse($data(0x02, 0x01, 0x80), nil),
|
jens@16
|
296 |
$object(-128));
|
jens@16
|
297 |
CAssertEqual(MYBERParse($data(0x02, 0x02, 0x00, 0x80), nil),
|
jens@16
|
298 |
$object(128));
|
jens@16
|
299 |
CAssertEqual(MYBERParse($data(0x02, 0x02, 0x30,0x39), nil),
|
jens@16
|
300 |
$object(12345));
|
jens@16
|
301 |
CAssertEqual(MYBERParse($data(0x02, 0x02, 0xCF, 0xC7), nil),
|
jens@16
|
302 |
$object(-12345));
|
jens@16
|
303 |
CAssertEqual(MYBERParse($data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15), nil),
|
jens@16
|
304 |
$object(123456789));
|
jens@16
|
305 |
CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
|
jens@16
|
306 |
$object(-123456789));
|
jens@16
|
307 |
CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
|
jens@16
|
308 |
$object(-123456789));
|
jens@16
|
309 |
|
jens@16
|
310 |
// octet strings:
|
jens@16
|
311 |
CAssertEqual(MYBERParse($data(0x04, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
|
jens@16
|
312 |
[@"hello" dataUsingEncoding: NSASCIIStringEncoding]);
|
jens@16
|
313 |
CAssertEqual(MYBERParse($data(0x04, 0x00), nil),
|
jens@16
|
314 |
[NSData data]);
|
jens@16
|
315 |
CAssertEqual(MYBERParse($data(0x0C, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
|
jens@16
|
316 |
@"hello");
|
jens@16
|
317 |
|
jens@16
|
318 |
// sequences:
|
jens@16
|
319 |
CAssertEqual(MYBERParse($data(0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF), nil),
|
jens@16
|
320 |
$array($object(72), $true));
|
jens@16
|
321 |
CAssertEqual(MYBERParse($data(0x30, 0x10,
|
jens@16
|
322 |
0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF,
|
jens@16
|
323 |
0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF), nil),
|
jens@16
|
324 |
$array( $array($object(72), $true), $array($object(72), $true)));
|
jens@16
|
325 |
}
|
jens@16
|
326 |
|
jens@16
|
327 |
|
jens@17
|
328 |
#import "MYCertificate.h"
|
jens@17
|
329 |
#import "MYPublicKey.h"
|
jens@17
|
330 |
|
jens@16
|
331 |
TestCase(ParseCert) {
|
jens@16
|
332 |
NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];
|
jens@16
|
333 |
NSError *error = nil;
|
jens@16
|
334 |
id parsed = MYBERParse(cert,&error);
|
jens@16
|
335 |
CAssert(parsed);
|
jens@16
|
336 |
CAssertNil(error);
|
jens@16
|
337 |
NSString *dump = [MYASN1Object dump: parsed];
|
jens@16
|
338 |
CAssert(dump);
|
jens@17
|
339 |
Log(@"Parsed selfsigned.cer:\n%@", dump);
|
jens@17
|
340 |
|
jens@17
|
341 |
MYCertificate *myCert = [[MYCertificate alloc] initWithCertificateData: cert];
|
jens@17
|
342 |
CAssert(myCert);
|
jens@19
|
343 |
id parsedPubKey = MYBERParse(myCert.publicKey.keyData, NULL);
|
jens@17
|
344 |
Log(@"Parsed public key:\n%@", [MYASN1Object dump: parsedPubKey]);
|
jens@16
|
345 |
|
jens@16
|
346 |
cert = [NSData dataWithContentsOfFile: @"../../Tests/iphonedev.cer"];
|
jens@16
|
347 |
parsed = MYBERParse(cert,&error);
|
jens@16
|
348 |
CAssert(parsed);
|
jens@16
|
349 |
CAssertNil(error);
|
jens@16
|
350 |
dump = [MYASN1Object dump: parsed];
|
jens@16
|
351 |
CAssert(dump);
|
jens@16
|
352 |
}
|