jens@16
|
1 |
//
|
jens@16
|
2 |
// MYDEREncoder.m
|
jens@16
|
3 |
// MYCrypto
|
jens@16
|
4 |
//
|
jens@16
|
5 |
// Created by Jens Alfke on 5/29/09.
|
jens@16
|
6 |
// Copyright 2009 Jens Alfke. All rights reserved.
|
jens@16
|
7 |
//
|
jens@16
|
8 |
|
jens@16
|
9 |
#import "MYDEREncoder.h"
|
jens@16
|
10 |
#import "MYASN1Object.h"
|
jens@16
|
11 |
#import "MYBERParser.h"
|
jens@16
|
12 |
#import "MYOID.h"
|
jens@16
|
13 |
#import "MYErrorUtils.h"
|
jens@16
|
14 |
|
jens@16
|
15 |
|
jens@16
|
16 |
#define MYDEREncoderException @"MYDEREncoderException"
|
jens@16
|
17 |
|
jens@16
|
18 |
|
jens@16
|
19 |
@interface MYDEREncoder ()
|
jens@16
|
20 |
- (void) _encode: (id)object;
|
jens@16
|
21 |
@property (retain) NSError *error;
|
jens@16
|
22 |
@end
|
jens@16
|
23 |
|
jens@16
|
24 |
|
jens@16
|
25 |
@implementation MYDEREncoder
|
jens@16
|
26 |
|
jens@16
|
27 |
|
jens@16
|
28 |
- (id) initWithRootObject: (id)rootObject
|
jens@16
|
29 |
{
|
jens@16
|
30 |
self = [super init];
|
jens@16
|
31 |
if (self != nil) {
|
jens@16
|
32 |
_rootObject = [rootObject retain];
|
jens@16
|
33 |
}
|
jens@16
|
34 |
return self;
|
jens@16
|
35 |
}
|
jens@16
|
36 |
|
jens@16
|
37 |
+ (NSData*) encodeRootObject: (id)rootObject error: (NSError**)outError {
|
jens@16
|
38 |
MYDEREncoder *encoder = [[self alloc] initWithRootObject: rootObject];
|
jens@16
|
39 |
NSData *output = [encoder.output copy];
|
jens@16
|
40 |
if (outError) *outError = [[encoder.error retain] autorelease];
|
jens@16
|
41 |
[encoder release];
|
jens@16
|
42 |
return [output autorelease];
|
jens@16
|
43 |
}
|
jens@16
|
44 |
|
jens@16
|
45 |
- (void) dealloc
|
jens@16
|
46 |
{
|
jens@16
|
47 |
[_rootObject release];
|
jens@16
|
48 |
[_output release];
|
jens@16
|
49 |
[super dealloc];
|
jens@16
|
50 |
}
|
jens@16
|
51 |
|
jens@16
|
52 |
|
jens@16
|
53 |
|
jens@16
|
54 |
static unsigned sizeOfUnsignedInt (UInt64 n) {
|
jens@16
|
55 |
unsigned bytes = 0;
|
jens@16
|
56 |
for (; n; n >>= 8)
|
jens@16
|
57 |
bytes++;
|
jens@16
|
58 |
return bytes;
|
jens@16
|
59 |
}
|
jens@16
|
60 |
|
jens@16
|
61 |
static unsigned encodeUnsignedInt (UInt64 n, UInt8 buf[], BOOL padHighBit) {
|
jens@16
|
62 |
unsigned size = MAX(1U, sizeOfUnsignedInt(n));
|
jens@16
|
63 |
UInt64 bigEndian = NSSwapHostLongLongToBig(n);
|
jens@16
|
64 |
const UInt8* src = (UInt8*)&bigEndian + (8-size);
|
jens@16
|
65 |
UInt8 *dst = &buf[0];
|
jens@16
|
66 |
if (padHighBit && (*src & 0x80)) {
|
jens@16
|
67 |
*dst++ = 0;
|
jens@16
|
68 |
size++;
|
jens@16
|
69 |
}
|
jens@16
|
70 |
memcpy(dst, src, size);
|
jens@16
|
71 |
return size;
|
jens@16
|
72 |
}
|
jens@16
|
73 |
|
jens@16
|
74 |
static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
|
jens@16
|
75 |
if (n >= 0)
|
jens@16
|
76 |
return encodeUnsignedInt(n, buf, YES);
|
jens@16
|
77 |
else {
|
jens@16
|
78 |
unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
|
jens@16
|
79 |
UInt64 bigEndian = NSSwapHostLongLongToBig(n);
|
jens@16
|
80 |
const UInt8* src = (UInt8*)&bigEndian + (8-size);
|
jens@16
|
81 |
UInt8 *dst = &buf[0];
|
jens@16
|
82 |
if (!(*src & 0x80)) {
|
jens@16
|
83 |
*dst++ = 0xFF;
|
jens@16
|
84 |
size++;
|
jens@16
|
85 |
}
|
jens@16
|
86 |
memcpy(dst, src, size);
|
jens@16
|
87 |
return size;
|
jens@16
|
88 |
}
|
jens@16
|
89 |
}
|
jens@16
|
90 |
|
jens@16
|
91 |
|
jens@16
|
92 |
- (void) _writeTag: (unsigned)tag
|
jens@16
|
93 |
class: (unsigned)tagClass
|
jens@16
|
94 |
constructed: (BOOL) constructed
|
jens@16
|
95 |
length: (size_t)length
|
jens@16
|
96 |
{
|
jens@16
|
97 |
struct {
|
jens@16
|
98 |
unsigned tag :5;
|
jens@16
|
99 |
unsigned isConstructed :1;
|
jens@16
|
100 |
unsigned tagClass :2;
|
jens@16
|
101 |
unsigned length :7;
|
jens@16
|
102 |
unsigned isLengthLong :1;
|
jens@16
|
103 |
UInt8 extraLength[9];
|
jens@16
|
104 |
} header;
|
jens@16
|
105 |
size_t headerSize = 2;
|
jens@16
|
106 |
|
jens@16
|
107 |
header.tag = tag;
|
jens@16
|
108 |
header.isConstructed = constructed;
|
jens@16
|
109 |
header.tagClass = tagClass;
|
jens@16
|
110 |
if (length < 128) {
|
jens@16
|
111 |
header.isLengthLong = NO;
|
jens@16
|
112 |
header.length = length;
|
jens@16
|
113 |
} else {
|
jens@16
|
114 |
header.isLengthLong = YES;
|
jens@16
|
115 |
header.length = encodeUnsignedInt(length, header.extraLength, NO);
|
jens@16
|
116 |
headerSize += header.length;
|
jens@16
|
117 |
}
|
jens@16
|
118 |
[_output appendBytes: &header length: headerSize];
|
jens@16
|
119 |
}
|
jens@16
|
120 |
|
jens@16
|
121 |
- (void) _writeTag: (unsigned)tag
|
jens@16
|
122 |
class: (unsigned)tagClass
|
jens@16
|
123 |
constructed: (BOOL) constructed
|
jens@16
|
124 |
bytes: (const void*)bytes
|
jens@16
|
125 |
length: (size_t)length
|
jens@16
|
126 |
{
|
jens@16
|
127 |
[self _writeTag: tag class: tagClass constructed: constructed length: length];
|
jens@16
|
128 |
[_output appendBytes: bytes length: length];
|
jens@16
|
129 |
}
|
jens@16
|
130 |
|
jens@16
|
131 |
- (void) _writeTag: (unsigned)tag
|
jens@16
|
132 |
class: (unsigned)tagClass
|
jens@16
|
133 |
constructed: (BOOL) constructed
|
jens@16
|
134 |
data: (NSData*)data
|
jens@16
|
135 |
{
|
jens@16
|
136 |
Assert(data);
|
jens@16
|
137 |
[self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
|
jens@16
|
138 |
}
|
jens@16
|
139 |
|
jens@16
|
140 |
|
jens@16
|
141 |
- (void) _encodeNumber: (NSNumber*)number {
|
jens@16
|
142 |
// Special-case detection of booleans by pointer equality, because otherwise they appear
|
jens@16
|
143 |
// identical to 0 and 1:
|
jens@16
|
144 |
if (number==$true || number==$false) {
|
jens@16
|
145 |
UInt8 value = number==$true ?0xFF :0x00;
|
jens@16
|
146 |
[self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
|
jens@16
|
147 |
return;
|
jens@16
|
148 |
}
|
jens@16
|
149 |
|
jens@16
|
150 |
const char *type = number.objCType;
|
jens@16
|
151 |
if (strlen(type) == 1) {
|
jens@16
|
152 |
switch(type[0]) {
|
jens@16
|
153 |
case 'c':
|
jens@16
|
154 |
case 'i':
|
jens@16
|
155 |
case 's':
|
jens@16
|
156 |
case 'l':
|
jens@16
|
157 |
case 'q':
|
jens@16
|
158 |
{ // Signed integers:
|
jens@16
|
159 |
UInt8 buf[9];
|
jens@16
|
160 |
size_t size = encodeSignedInt(number.longLongValue, buf);
|
jens@16
|
161 |
[self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
|
jens@16
|
162 |
return;
|
jens@16
|
163 |
}
|
jens@16
|
164 |
case 'C':
|
jens@16
|
165 |
case 'I':
|
jens@16
|
166 |
case 'S':
|
jens@16
|
167 |
case 'L':
|
jens@16
|
168 |
case 'Q':
|
jens@16
|
169 |
{ // Unsigned integers:
|
jens@16
|
170 |
UInt8 buf[9];
|
jens@16
|
171 |
size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
|
jens@16
|
172 |
[self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
|
jens@16
|
173 |
return;
|
jens@16
|
174 |
}
|
jens@16
|
175 |
case 'B':
|
jens@16
|
176 |
{ // bool
|
jens@16
|
177 |
UInt8 value = number.boolValue ?0xFF :0x00;
|
jens@16
|
178 |
[self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
|
jens@16
|
179 |
return;
|
jens@16
|
180 |
}
|
jens@16
|
181 |
}
|
jens@16
|
182 |
}
|
jens@16
|
183 |
[NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
|
jens@16
|
184 |
}
|
jens@16
|
185 |
|
jens@16
|
186 |
|
jens@16
|
187 |
- (void) _encodeString: (NSString*)string {
|
jens@16
|
188 |
NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
|
jens@16
|
189 |
if (data)
|
jens@16
|
190 |
[self _writeTag: 19 class: 0 constructed: NO data: data];
|
jens@16
|
191 |
else
|
jens@16
|
192 |
[self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
|
jens@16
|
193 |
}
|
jens@16
|
194 |
|
jens@16
|
195 |
|
jens@16
|
196 |
- (void) _encodeBitString: (MYBitString*)bitString {
|
jens@16
|
197 |
NSUInteger bitCount = bitString.bitCount;
|
jens@16
|
198 |
[self _writeTag: 3 class: 0 constructed: NO length: 1 + (bitCount/8)];
|
jens@16
|
199 |
UInt8 unused = (8 - (bitCount % 8)) % 8;
|
jens@16
|
200 |
[_output appendBytes: &unused length: 1];
|
jens@16
|
201 |
[_output appendBytes: bitString.bits.bytes length: bitCount/8];
|
jens@16
|
202 |
}
|
jens@16
|
203 |
|
jens@16
|
204 |
- (void) _encodeDate: (NSDate*)date {
|
jens@16
|
205 |
NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
|
jens@16
|
206 |
Log(@"Encoded %@ as '%@'",date,dateStr);//TEMP
|
jens@16
|
207 |
[self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
|
jens@16
|
208 |
}
|
jens@16
|
209 |
|
jens@16
|
210 |
|
jens@16
|
211 |
- (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass {
|
jens@16
|
212 |
MYDEREncoder *subEncoder = [[[self class] alloc] init];
|
jens@16
|
213 |
for (id object in collection)
|
jens@16
|
214 |
[subEncoder _encode: object];
|
jens@16
|
215 |
[self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output];
|
jens@16
|
216 |
[subEncoder release];
|
jens@16
|
217 |
}
|
jens@16
|
218 |
|
jens@16
|
219 |
|
jens@16
|
220 |
- (void) _encode: (id)object {
|
jens@16
|
221 |
if (!_output)
|
jens@16
|
222 |
_output = [[NSMutableData alloc] initWithCapacity: 1024];
|
jens@16
|
223 |
if ([object isKindOfClass: [NSNumber class]]) {
|
jens@16
|
224 |
[self _encodeNumber: object];
|
jens@16
|
225 |
} else if ([object isKindOfClass: [NSData class]]) {
|
jens@16
|
226 |
[self _writeTag: 4 class: 0 constructed: NO data: object];
|
jens@16
|
227 |
} else if ([object isKindOfClass: [MYBitString class]]) {
|
jens@16
|
228 |
[self _encodeBitString: object];
|
jens@16
|
229 |
} else if ([object isKindOfClass: [NSString class]]) {
|
jens@16
|
230 |
[self _encodeString: object];
|
jens@16
|
231 |
} else if ([object isKindOfClass: [NSDate class]]) {
|
jens@16
|
232 |
[self _encodeDate: object];
|
jens@16
|
233 |
} else if ([object isKindOfClass: [NSNull class]]) {
|
jens@16
|
234 |
[self _writeTag: 5 class: 0 constructed: NO bytes: NULL length: 0];
|
jens@16
|
235 |
} else if ([object isKindOfClass: [NSArray class]]) {
|
jens@16
|
236 |
[self _encodeCollection: object tag: 16 class: 0];
|
jens@16
|
237 |
} else if ([object isKindOfClass: [NSSet class]]) {
|
jens@16
|
238 |
[self _encodeCollection: object tag: 17 class: 0];
|
jens@16
|
239 |
} else if ([object isKindOfClass: [MYOID class]]) {
|
jens@16
|
240 |
[self _writeTag: 6 class: 0 constructed: NO data: [object DEREncoding]];
|
jens@16
|
241 |
} else if ([object isKindOfClass: [MYASN1Object class]]) {
|
jens@16
|
242 |
MYASN1Object *asn = object;
|
jens@16
|
243 |
if (asn.components)
|
jens@16
|
244 |
[self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
|
jens@16
|
245 |
else
|
jens@16
|
246 |
[self _writeTag: asn.tag
|
jens@16
|
247 |
class: asn.tagClass
|
jens@16
|
248 |
constructed: asn.constructed
|
jens@16
|
249 |
data: asn.value];
|
jens@16
|
250 |
} else {
|
jens@16
|
251 |
[NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
|
jens@16
|
252 |
}
|
jens@16
|
253 |
}
|
jens@16
|
254 |
|
jens@16
|
255 |
|
jens@16
|
256 |
- (NSData*) output {
|
jens@16
|
257 |
if (!_output && !_error) {
|
jens@16
|
258 |
@try{
|
jens@16
|
259 |
[self _encode: _rootObject];
|
jens@16
|
260 |
}@catch (NSException *x) {
|
jens@16
|
261 |
if ($equal(x.name, MYDEREncoderException)) {
|
jens@16
|
262 |
self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
|
jens@16
|
263 |
return nil;
|
jens@16
|
264 |
} else
|
jens@16
|
265 |
@throw(x);
|
jens@16
|
266 |
}
|
jens@16
|
267 |
}
|
jens@16
|
268 |
return _output;
|
jens@16
|
269 |
}
|
jens@16
|
270 |
|
jens@16
|
271 |
@synthesize error=_error;
|
jens@16
|
272 |
|
jens@16
|
273 |
|
jens@16
|
274 |
@end
|
jens@16
|
275 |
|
jens@16
|
276 |
|
jens@16
|
277 |
|
jens@16
|
278 |
#define $data(BYTES...) ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
|
jens@16
|
279 |
|
jens@16
|
280 |
TestCase(DEREncoder) {
|
jens@16
|
281 |
CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
|
jens@16
|
282 |
$data(0x05, 0x00));
|
jens@16
|
283 |
CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
|
jens@16
|
284 |
$data(0x01, 0x01, 0xFF));
|
jens@16
|
285 |
CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
|
jens@16
|
286 |
$data(0x01, 0x01, 0x00));
|
jens@16
|
287 |
|
jens@16
|
288 |
// Integers:
|
jens@16
|
289 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(0) error: nil],
|
jens@16
|
290 |
$data(0x02, 0x01, 0x00));
|
jens@16
|
291 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(1) error: nil],
|
jens@16
|
292 |
$data(0x02, 0x01, 0x01));
|
jens@16
|
293 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(-1) error: nil],
|
jens@16
|
294 |
$data(0x02, 0x01, 0xFF));
|
jens@16
|
295 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(72) error: nil],
|
jens@16
|
296 |
$data(0x02, 0x01, 0x48));
|
jens@16
|
297 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(-128) error: nil],
|
jens@16
|
298 |
$data(0x02, 0x01, 0x80));
|
jens@16
|
299 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(128) error: nil],
|
jens@16
|
300 |
$data(0x02, 0x02, 0x00, 0x80));
|
jens@16
|
301 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(255) error: nil],
|
jens@16
|
302 |
$data(0x02, 0x02, 0x00, 0xFF));
|
jens@16
|
303 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(-256) error: nil],
|
jens@16
|
304 |
$data(0x02, 0x02, 0xFF, 0x00));
|
jens@16
|
305 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(12345) error: nil],
|
jens@16
|
306 |
$data(0x02, 0x02, 0x30,0x39));
|
jens@16
|
307 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(-12345) error: nil],
|
jens@16
|
308 |
$data(0x02, 0x02, 0xCF, 0xC7));
|
jens@16
|
309 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(123456789) error: nil],
|
jens@16
|
310 |
$data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15));
|
jens@16
|
311 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
|
jens@16
|
312 |
$data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
|
jens@16
|
313 |
CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
|
jens@16
|
314 |
$data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
|
jens@16
|
315 |
|
jens@16
|
316 |
// Strings:
|
jens@16
|
317 |
CAssertEqual([MYDEREncoder encodeRootObject: @"hello" error: nil],
|
jens@16
|
318 |
$data(0x13, 0x05, 'h', 'e', 'l', 'l', 'o'));
|
jens@16
|
319 |
CAssertEqual([MYDEREncoder encodeRootObject: @"thérè" error: nil],
|
jens@16
|
320 |
$data(0x0C, 0x07, 't', 'h', 0xC3, 0xA9, 'r', 0xC3, 0xA8));
|
jens@16
|
321 |
|
jens@16
|
322 |
// Dates:
|
jens@16
|
323 |
CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
|
jens@16
|
324 |
error: nil],
|
jens@16
|
325 |
$data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
|
jens@16
|
326 |
|
jens@16
|
327 |
// Sequences:
|
jens@16
|
328 |
CAssertEqual([MYDEREncoder encodeRootObject: $array($object(72), $true) error: nil],
|
jens@16
|
329 |
$data(0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
|
jens@16
|
330 |
CAssertEqual([MYDEREncoder encodeRootObject: $array( $array($object(72), $true),
|
jens@16
|
331 |
$array($object(72), $true))
|
jens@16
|
332 |
error: nil],
|
jens@16
|
333 |
$data(0x30, 0x10,
|
jens@16
|
334 |
0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF,
|
jens@16
|
335 |
0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
|
jens@16
|
336 |
}
|
jens@16
|
337 |
|
jens@16
|
338 |
|
jens@16
|
339 |
TestCase(EncodeCert) {
|
jens@16
|
340 |
NSError *error = nil;
|
jens@17
|
341 |
NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/iphonedev.cer"]; //TEMP
|
jens@16
|
342 |
id certObjects = MYBERParse(cert,&error);
|
jens@16
|
343 |
CAssertNil(error);
|
jens@16
|
344 |
Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]);
|
jens@16
|
345 |
NSData *encoded = [MYDEREncoder encodeRootObject: certObjects error: &error];
|
jens@16
|
346 |
CAssertNil(error);
|
jens@16
|
347 |
id reDecoded = MYBERParse(encoded, &error);
|
jens@16
|
348 |
CAssertNil(error);
|
jens@16
|
349 |
Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
|
jens@17
|
350 |
[encoded writeToFile: @"../../Tests/iphonedev_reencoded.cer" atomically: YES];
|
jens@16
|
351 |
CAssertEqual(encoded,cert);
|
jens@16
|
352 |
}
|