* Added ASN.1 / BER / DER utilities, to be used in generating and parsing X.509 certs.
* Added Keychain user-interaction-allowed setter. Added doc comments to MYSymmetricKey.
5 // Created by Jens Alfke on 5/29/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYDEREncoder.h"
10 #import "MYASN1Object.h"
11 #import "MYBERParser.h"
13 #import "MYErrorUtils.h"
16 #define MYDEREncoderException @"MYDEREncoderException"
19 @interface MYDEREncoder ()
20 - (void) _encode: (id)object;
21 @property (retain) NSError *error;
25 @implementation MYDEREncoder
28 - (id) initWithRootObject: (id)rootObject
32 _rootObject = [rootObject retain];
37 + (NSData*) encodeRootObject: (id)rootObject error: (NSError**)outError {
38 MYDEREncoder *encoder = [[self alloc] initWithRootObject: rootObject];
39 NSData *output = [encoder.output copy];
40 if (outError) *outError = [[encoder.error retain] autorelease];
42 return [output autorelease];
47 [_rootObject release];
54 static unsigned sizeOfUnsignedInt (UInt64 n) {
61 static unsigned encodeUnsignedInt (UInt64 n, UInt8 buf[], BOOL padHighBit) {
62 unsigned size = MAX(1U, sizeOfUnsignedInt(n));
63 UInt64 bigEndian = NSSwapHostLongLongToBig(n);
64 const UInt8* src = (UInt8*)&bigEndian + (8-size);
66 if (padHighBit && (*src & 0x80)) {
70 memcpy(dst, src, size);
74 static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
76 return encodeUnsignedInt(n, buf, YES);
78 unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
79 UInt64 bigEndian = NSSwapHostLongLongToBig(n);
80 const UInt8* src = (UInt8*)&bigEndian + (8-size);
86 memcpy(dst, src, size);
92 - (void) _writeTag: (unsigned)tag
93 class: (unsigned)tagClass
94 constructed: (BOOL) constructed
95 length: (size_t)length
99 unsigned isConstructed :1;
100 unsigned tagClass :2;
102 unsigned isLengthLong :1;
103 UInt8 extraLength[9];
105 size_t headerSize = 2;
108 header.isConstructed = constructed;
109 header.tagClass = tagClass;
111 header.isLengthLong = NO;
112 header.length = length;
114 header.isLengthLong = YES;
115 header.length = encodeUnsignedInt(length, header.extraLength, NO);
116 headerSize += header.length;
118 [_output appendBytes: &header length: headerSize];
121 - (void) _writeTag: (unsigned)tag
122 class: (unsigned)tagClass
123 constructed: (BOOL) constructed
124 bytes: (const void*)bytes
125 length: (size_t)length
127 [self _writeTag: tag class: tagClass constructed: constructed length: length];
128 [_output appendBytes: bytes length: length];
131 - (void) _writeTag: (unsigned)tag
132 class: (unsigned)tagClass
133 constructed: (BOOL) constructed
137 [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
141 - (void) _encodeNumber: (NSNumber*)number {
142 // Special-case detection of booleans by pointer equality, because otherwise they appear
143 // identical to 0 and 1:
144 if (number==$true || number==$false) {
145 UInt8 value = number==$true ?0xFF :0x00;
146 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
150 const char *type = number.objCType;
151 if (strlen(type) == 1) {
158 { // Signed integers:
160 size_t size = encodeSignedInt(number.longLongValue, buf);
161 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
169 { // Unsigned integers:
171 size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
172 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
177 UInt8 value = number.boolValue ?0xFF :0x00;
178 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
183 [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
187 - (void) _encodeString: (NSString*)string {
188 NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
190 [self _writeTag: 19 class: 0 constructed: NO data: data];
192 [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
196 - (void) _encodeBitString: (MYBitString*)bitString {
197 NSUInteger bitCount = bitString.bitCount;
198 [self _writeTag: 3 class: 0 constructed: NO length: 1 + (bitCount/8)];
199 UInt8 unused = (8 - (bitCount % 8)) % 8;
200 [_output appendBytes: &unused length: 1];
201 [_output appendBytes: bitString.bits.bytes length: bitCount/8];
204 - (void) _encodeDate: (NSDate*)date {
205 NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
206 Log(@"Encoded %@ as '%@'",date,dateStr);//TEMP
207 [self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
211 - (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass {
212 MYDEREncoder *subEncoder = [[[self class] alloc] init];
213 for (id object in collection)
214 [subEncoder _encode: object];
215 [self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output];
216 [subEncoder release];
220 - (void) _encode: (id)object {
222 _output = [[NSMutableData alloc] initWithCapacity: 1024];
223 if ([object isKindOfClass: [NSNumber class]]) {
224 [self _encodeNumber: object];
225 } else if ([object isKindOfClass: [NSData class]]) {
226 [self _writeTag: 4 class: 0 constructed: NO data: object];
227 } else if ([object isKindOfClass: [MYBitString class]]) {
228 [self _encodeBitString: object];
229 } else if ([object isKindOfClass: [NSString class]]) {
230 [self _encodeString: object];
231 } else if ([object isKindOfClass: [NSDate class]]) {
232 [self _encodeDate: object];
233 } else if ([object isKindOfClass: [NSNull class]]) {
234 [self _writeTag: 5 class: 0 constructed: NO bytes: NULL length: 0];
235 } else if ([object isKindOfClass: [NSArray class]]) {
236 [self _encodeCollection: object tag: 16 class: 0];
237 } else if ([object isKindOfClass: [NSSet class]]) {
238 [self _encodeCollection: object tag: 17 class: 0];
239 } else if ([object isKindOfClass: [MYOID class]]) {
240 [self _writeTag: 6 class: 0 constructed: NO data: [object DEREncoding]];
241 } else if ([object isKindOfClass: [MYASN1Object class]]) {
242 MYASN1Object *asn = object;
244 [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
246 [self _writeTag: asn.tag
248 constructed: asn.constructed
251 [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
257 if (!_output && !_error) {
259 [self _encode: _rootObject];
260 }@catch (NSException *x) {
261 if ($equal(x.name, MYDEREncoderException)) {
262 self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
271 @synthesize error=_error;
278 #define $data(BYTES...) ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
280 TestCase(DEREncoder) {
281 CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
283 CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
284 $data(0x01, 0x01, 0xFF));
285 CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
286 $data(0x01, 0x01, 0x00));
289 CAssertEqual([MYDEREncoder encodeRootObject: $object(0) error: nil],
290 $data(0x02, 0x01, 0x00));
291 CAssertEqual([MYDEREncoder encodeRootObject: $object(1) error: nil],
292 $data(0x02, 0x01, 0x01));
293 CAssertEqual([MYDEREncoder encodeRootObject: $object(-1) error: nil],
294 $data(0x02, 0x01, 0xFF));
295 CAssertEqual([MYDEREncoder encodeRootObject: $object(72) error: nil],
296 $data(0x02, 0x01, 0x48));
297 CAssertEqual([MYDEREncoder encodeRootObject: $object(-128) error: nil],
298 $data(0x02, 0x01, 0x80));
299 CAssertEqual([MYDEREncoder encodeRootObject: $object(128) error: nil],
300 $data(0x02, 0x02, 0x00, 0x80));
301 CAssertEqual([MYDEREncoder encodeRootObject: $object(255) error: nil],
302 $data(0x02, 0x02, 0x00, 0xFF));
303 CAssertEqual([MYDEREncoder encodeRootObject: $object(-256) error: nil],
304 $data(0x02, 0x02, 0xFF, 0x00));
305 CAssertEqual([MYDEREncoder encodeRootObject: $object(12345) error: nil],
306 $data(0x02, 0x02, 0x30,0x39));
307 CAssertEqual([MYDEREncoder encodeRootObject: $object(-12345) error: nil],
308 $data(0x02, 0x02, 0xCF, 0xC7));
309 CAssertEqual([MYDEREncoder encodeRootObject: $object(123456789) error: nil],
310 $data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15));
311 CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
312 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
313 CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
314 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
317 CAssertEqual([MYDEREncoder encodeRootObject: @"hello" error: nil],
318 $data(0x13, 0x05, 'h', 'e', 'l', 'l', 'o'));
319 CAssertEqual([MYDEREncoder encodeRootObject: @"thérè" error: nil],
320 $data(0x0C, 0x07, 't', 'h', 0xC3, 0xA9, 'r', 0xC3, 0xA8));
323 CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
325 $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
328 CAssertEqual([MYDEREncoder encodeRootObject: $array($object(72), $true) error: nil],
329 $data(0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
330 CAssertEqual([MYDEREncoder encodeRootObject: $array( $array($object(72), $true),
331 $array($object(72), $true))
334 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF,
335 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
339 TestCase(EncodeCert) {
340 NSError *error = nil;
341 NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"]; //TEMP
342 id certObjects = MYBERParse(cert,&error);
344 Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]);
345 NSData *encoded = [MYDEREncoder encodeRootObject: certObjects error: &error];
347 id reDecoded = MYBERParse(encoded, &error);
349 Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
350 [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
351 CAssertEqual(encoded,cert);