Whew! MYParsedCertificate can now generate certs from scratch. Also added improvements and fixes to the BER/DER codecs.
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;
22 @property BOOL _forcePrintableStrings;
26 @implementation MYDEREncoder
29 - (id) initWithRootObject: (id)rootObject
33 _rootObject = [rootObject retain];
38 + (NSData*) encodeRootObject: (id)rootObject error: (NSError**)outError {
39 MYDEREncoder *encoder = [[self alloc] initWithRootObject: rootObject];
40 NSData *output = [encoder.output copy];
41 if (outError) *outError = [[encoder.error retain] autorelease];
43 return [output autorelease];
48 [_rootObject release];
54 - (id) copyWithZone: (NSZone*)zone {
55 MYDEREncoder *copy = [[[self class] alloc] init];
56 copy->_forcePrintableStrings = _forcePrintableStrings;
61 static unsigned sizeOfUnsignedInt (UInt64 n) {
68 static unsigned encodeUnsignedInt (UInt64 n, UInt8 buf[], BOOL padHighBit) {
69 unsigned size = MAX(1U, sizeOfUnsignedInt(n));
70 UInt64 bigEndian = NSSwapHostLongLongToBig(n);
71 const UInt8* src = (UInt8*)&bigEndian + (8-size);
73 if (padHighBit && (*src & 0x80)) {
77 memcpy(dst, src, size);
81 static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
83 return encodeUnsignedInt(n, buf, YES);
85 unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
86 UInt64 bigEndian = NSSwapHostLongLongToBig(n);
87 const UInt8* src = (UInt8*)&bigEndian + (8-size);
93 memcpy(dst, src, size);
99 - (void) _writeTag: (unsigned)tag
100 class: (unsigned)tagClass
101 constructed: (BOOL) constructed
102 length: (size_t)length
106 unsigned isConstructed :1;
107 unsigned tagClass :2;
109 unsigned isLengthLong :1;
110 UInt8 extraLength[9];
112 size_t headerSize = 2;
115 header.isConstructed = constructed;
116 header.tagClass = tagClass;
118 header.isLengthLong = NO;
119 header.length = length;
121 header.isLengthLong = YES;
122 header.length = encodeUnsignedInt(length, header.extraLength, NO);
123 headerSize += header.length;
125 [_output appendBytes: &header length: headerSize];
128 - (void) _writeTag: (unsigned)tag
129 class: (unsigned)tagClass
130 constructed: (BOOL) constructed
131 bytes: (const void*)bytes
132 length: (size_t)length
134 [self _writeTag: tag class: tagClass constructed: constructed length: length];
135 [_output appendBytes: bytes length: length];
138 - (void) _writeTag: (unsigned)tag
139 class: (unsigned)tagClass
140 constructed: (BOOL) constructed
144 [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
148 - (void) _encodeNumber: (NSNumber*)number {
149 // Special-case detection of booleans by pointer equality, because otherwise they appear
150 // identical to 0 and 1:
151 if (number==$true || number==$false) {
152 UInt8 value = number==$true ?0xFF :0x00;
153 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
157 const char *type = number.objCType;
158 if (strlen(type) == 1) {
165 { // Signed integers:
167 size_t size = encodeSignedInt(number.longLongValue, buf);
168 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
176 { // Unsigned integers:
178 size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
179 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
184 UInt8 value = number.boolValue ?0xFF :0x00;
185 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
190 [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
194 - (void) _encodeString: (NSString*)string {
195 static NSMutableCharacterSet *kNotPrintableCharSet;
196 if (!kNotPrintableCharSet) {
197 kNotPrintableCharSet = [[NSMutableCharacterSet characterSetWithCharactersInString: @" '()+,-./:=?"] retain];
198 [kNotPrintableCharSet formUnionWithCharacterSet: [NSCharacterSet alphanumericCharacterSet]];
199 [kNotPrintableCharSet invert];
201 NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
203 unsigned tag = 19; // printablestring (a silly arbitrary subset of ASCII defined by ASN.1)
204 if (!_forcePrintableStrings && [string rangeOfCharacterFromSet: kNotPrintableCharSet].length > 0)
205 tag = 20; // IA5string (full 7-bit ASCII)
206 [self _writeTag: tag class: 0 constructed: NO data: data];
208 // fall back to UTF-8:
209 [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
214 - (void) _encodeBitString: (MYBitString*)bitString {
215 NSUInteger bitCount = bitString.bitCount;
216 [self _writeTag: 3 class: 0 constructed: NO length: 1 + (bitCount/8)];
217 UInt8 unused = (8 - (bitCount % 8)) % 8;
218 [_output appendBytes: &unused length: 1];
219 [_output appendBytes: bitString.bits.bytes length: bitCount/8];
222 - (void) _encodeDate: (NSDate*)date {
223 NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
224 Log(@"Encoded %@ as '%@'",date,dateStr);//TEMP
225 [self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
229 - (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass {
230 MYDEREncoder *subEncoder = [self copy];
231 for (id object in collection)
232 [subEncoder _encode: object];
233 [self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output];
234 [subEncoder release];
238 - (void) _encode: (id)object {
240 _output = [[NSMutableData alloc] initWithCapacity: 1024];
241 if ([object isKindOfClass: [NSNumber class]]) {
242 [self _encodeNumber: object];
243 } else if ([object isKindOfClass: [NSData class]]) {
244 [self _writeTag: 4 class: 0 constructed: NO data: object];
245 } else if ([object isKindOfClass: [MYBitString class]]) {
246 [self _encodeBitString: object];
247 } else if ([object isKindOfClass: [NSString class]]) {
248 [self _encodeString: object];
249 } else if ([object isKindOfClass: [NSDate class]]) {
250 [self _encodeDate: object];
251 } else if ([object isKindOfClass: [NSNull class]]) {
252 [self _writeTag: 5 class: 0 constructed: NO bytes: NULL length: 0];
253 } else if ([object isKindOfClass: [NSArray class]]) {
254 [self _encodeCollection: object tag: 16 class: 0];
255 } else if ([object isKindOfClass: [NSSet class]]) {
256 [self _encodeCollection: object tag: 17 class: 0];
257 } else if ([object isKindOfClass: [MYOID class]]) {
258 [self _writeTag: 6 class: 0 constructed: NO data: [object DEREncoding]];
259 } else if ([object isKindOfClass: [MYASN1Object class]]) {
260 MYASN1Object *asn = object;
262 [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
264 [self _writeTag: asn.tag
266 constructed: asn.constructed
269 [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
275 if (!_output && !_error) {
277 [self _encode: _rootObject];
278 }@catch (NSException *x) {
279 if ($equal(x.name, MYDEREncoderException)) {
280 self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
289 @synthesize error=_error, _forcePrintableStrings;
296 #define $data(BYTES...) ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
298 TestCase(DEREncoder) {
299 CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
301 CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
302 $data(0x01, 0x01, 0xFF));
303 CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
304 $data(0x01, 0x01, 0x00));
307 CAssertEqual([MYDEREncoder encodeRootObject: $object(0) error: nil],
308 $data(0x02, 0x01, 0x00));
309 CAssertEqual([MYDEREncoder encodeRootObject: $object(1) error: nil],
310 $data(0x02, 0x01, 0x01));
311 CAssertEqual([MYDEREncoder encodeRootObject: $object(-1) error: nil],
312 $data(0x02, 0x01, 0xFF));
313 CAssertEqual([MYDEREncoder encodeRootObject: $object(72) error: nil],
314 $data(0x02, 0x01, 0x48));
315 CAssertEqual([MYDEREncoder encodeRootObject: $object(-128) error: nil],
316 $data(0x02, 0x01, 0x80));
317 CAssertEqual([MYDEREncoder encodeRootObject: $object(128) error: nil],
318 $data(0x02, 0x02, 0x00, 0x80));
319 CAssertEqual([MYDEREncoder encodeRootObject: $object(255) error: nil],
320 $data(0x02, 0x02, 0x00, 0xFF));
321 CAssertEqual([MYDEREncoder encodeRootObject: $object(-256) error: nil],
322 $data(0x02, 0x02, 0xFF, 0x00));
323 CAssertEqual([MYDEREncoder encodeRootObject: $object(12345) error: nil],
324 $data(0x02, 0x02, 0x30,0x39));
325 CAssertEqual([MYDEREncoder encodeRootObject: $object(-12345) error: nil],
326 $data(0x02, 0x02, 0xCF, 0xC7));
327 CAssertEqual([MYDEREncoder encodeRootObject: $object(123456789) error: nil],
328 $data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15));
329 CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
330 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
331 CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
332 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
335 CAssertEqual([MYDEREncoder encodeRootObject: @"hello" error: nil],
336 $data(0x13, 0x05, 'h', 'e', 'l', 'l', 'o'));
337 CAssertEqual([MYDEREncoder encodeRootObject: @"thérè" error: nil],
338 $data(0x0C, 0x07, 't', 'h', 0xC3, 0xA9, 'r', 0xC3, 0xA8));
341 CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
343 $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
346 CAssertEqual([MYDEREncoder encodeRootObject: $array($object(72), $true) error: nil],
347 $data(0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
348 CAssertEqual([MYDEREncoder encodeRootObject: $array( $array($object(72), $true),
349 $array($object(72), $true))
352 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF,
353 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
357 TestCase(EncodeCert) {
358 NSError *error = nil;
359 NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"]; //TEMP
360 id certObjects = MYBERParse(cert,&error);
362 Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]);
363 MYDEREncoder *encoder = [[MYDEREncoder alloc] initWithRootObject: certObjects];
364 encoder._forcePrintableStrings = YES; // hack for compatibility with the way CDSA writes ASN.1
365 NSData *encoded = encoder.output;
367 id reDecoded = MYBERParse(encoded, &error);
369 Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
370 [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
371 CAssertEqual(encoded,cert);