* More work on iPhone compatibility.
* Restored the signature-verification code to MYCertInfo, which I'd removed earlier. I now need it to verify self-signed certs, since the Security framework won't do it for me.
* Merged MYCertificate-iPhone.m into MYCertificate.m since there's more shared code now.
5 // Created by Jens Alfke on 5/29/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 "MYDEREncoder.h"
13 #import "MYASN1Object.h"
14 #import "MYBERParser.h"
16 #import "MYErrorUtils.h"
19 #define MYDEREncoderException @"MYDEREncoderException"
22 @interface MYDEREncoder ()
23 - (void) _encode: (id)object;
24 @property (retain) NSError *error;
26 /* Forces use of PrintableString tag for ASCII strings that contain characters not valid
27 for that encoding (notably '@'). Provided to get byte-for-byte compatibility with certs
28 generated by CDSA, for test cases that check this. */
29 @property BOOL _forcePrintableStrings;
33 @implementation MYDEREncoder
36 - (id) initWithRootObject: (id)rootObject
40 _rootObject = [rootObject retain];
45 + (NSData*) encodeRootObject: (id)rootObject error: (NSError**)outError {
46 MYDEREncoder *encoder = [[self alloc] initWithRootObject: rootObject];
47 NSData *output = [encoder.output copy];
48 if (outError) *outError = [[encoder.error retain] autorelease];
50 return [output autorelease];
55 [_rootObject release];
61 - (id) copyWithZone: (NSZone*)zone {
62 MYDEREncoder *copy = [[[self class] alloc] init];
63 copy->_forcePrintableStrings = _forcePrintableStrings;
68 static unsigned sizeOfUnsignedInt (UInt64 n) {
75 static unsigned encodeUnsignedInt (UInt64 n, UInt8 buf[], BOOL padHighBit) {
76 unsigned size = MAX(1U, sizeOfUnsignedInt(n));
77 UInt64 bigEndian = NSSwapHostLongLongToBig(n);
78 const UInt8* src = (UInt8*)&bigEndian + (8-size);
80 if (padHighBit && (*src & 0x80)) {
84 memcpy(dst, src, size);
88 static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
90 return encodeUnsignedInt(n, buf, YES);
92 unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
93 UInt64 bigEndian = NSSwapHostLongLongToBig(n);
94 const UInt8* src = (UInt8*)&bigEndian + (8-size);
100 memcpy(dst, src, size);
106 - (void) _writeTag: (unsigned)tag
107 class: (unsigned)tagClass
108 constructed: (BOOL) constructed
109 length: (size_t)length
113 unsigned isConstructed :1;
114 unsigned tagClass :2;
116 unsigned isLengthLong :1;
117 UInt8 extraLength[9];
119 size_t headerSize = 2;
122 header.isConstructed = constructed;
123 header.tagClass = tagClass;
125 header.isLengthLong = NO;
126 header.length = length;
128 header.isLengthLong = YES;
129 header.length = encodeUnsignedInt(length, header.extraLength, NO);
130 headerSize += header.length;
132 [_output appendBytes: &header length: headerSize];
135 - (void) _writeTag: (unsigned)tag
136 class: (unsigned)tagClass
137 constructed: (BOOL) constructed
138 bytes: (const void*)bytes
139 length: (size_t)length
141 [self _writeTag: tag class: tagClass constructed: constructed length: length];
142 [_output appendBytes: bytes length: length];
145 - (void) _writeTag: (unsigned)tag
146 class: (unsigned)tagClass
147 constructed: (BOOL) constructed
151 [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
155 - (void) _encodeNumber: (NSNumber*)number {
156 // Special-case detection of booleans by pointer equality, because otherwise they appear
157 // identical to 0 and 1:
158 if (number==$true || number==$false) {
159 UInt8 value = number==$true ?0xFF :0x00;
160 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
164 const char *type = number.objCType;
165 if (strlen(type) == 1) {
172 { // Signed integers:
174 size_t size = encodeSignedInt(number.longLongValue, buf);
175 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
183 { // Unsigned integers:
185 size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
186 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
191 UInt8 value = number.boolValue ?0xFF :0x00;
192 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
197 [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
201 - (void) _encodeString: (NSString*)string {
202 static NSMutableCharacterSet *kNotPrintableCharSet;
203 if (!kNotPrintableCharSet) {
204 kNotPrintableCharSet = [[NSMutableCharacterSet characterSetWithCharactersInString: @" '()+,-./:=?"] retain];
205 [kNotPrintableCharSet formUnionWithCharacterSet: [NSCharacterSet alphanumericCharacterSet]];
206 [kNotPrintableCharSet invert];
208 NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
210 unsigned tag = 19; // printablestring (a silly arbitrary subset of ASCII defined by ASN.1)
211 if (!_forcePrintableStrings && [string rangeOfCharacterFromSet: kNotPrintableCharSet].length > 0)
212 tag = 20; // IA5string (full 7-bit ASCII)
213 [self _writeTag: tag class: 0 constructed: NO data: data];
215 // fall back to UTF-8:
216 [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
221 - (void) _encodeBitString: (MYBitString*)bitString {
222 NSUInteger bitCount = bitString.bitCount;
223 [self _writeTag: 3 class: 0 constructed: NO length: 1 + (bitCount/8)];
224 UInt8 unused = (8 - (bitCount % 8)) % 8;
225 [_output appendBytes: &unused length: 1];
226 [_output appendBytes: bitString.bits.bytes length: bitCount/8];
229 - (void) _encodeDate: (NSDate*)date {
230 NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
231 [self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
235 - (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass {
236 MYDEREncoder *subEncoder = [self copy];
237 for (id object in collection)
238 [subEncoder _encode: object];
239 [self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output];
240 [subEncoder release];
244 - (void) _encode: (id)object {
246 _output = [[NSMutableData alloc] initWithCapacity: 1024];
247 if ([object isKindOfClass: [NSNumber class]]) {
248 [self _encodeNumber: object];
249 } else if ([object isKindOfClass: [NSData class]]) {
250 [self _writeTag: 4 class: 0 constructed: NO data: object];
251 } else if ([object isKindOfClass: [MYBitString class]]) {
252 [self _encodeBitString: object];
253 } else if ([object isKindOfClass: [NSString class]]) {
254 [self _encodeString: object];
255 } else if ([object isKindOfClass: [NSDate class]]) {
256 [self _encodeDate: object];
257 } else if ([object isKindOfClass: [NSNull class]]) {
258 [self _writeTag: 5 class: 0 constructed: NO bytes: NULL length: 0];
259 } else if ([object isKindOfClass: [NSArray class]]) {
260 [self _encodeCollection: object tag: 16 class: 0];
261 } else if ([object isKindOfClass: [NSSet class]]) {
262 [self _encodeCollection: object tag: 17 class: 0];
263 } else if ([object isKindOfClass: [MYOID class]]) {
264 [self _writeTag: 6 class: 0 constructed: NO data: [object DEREncoding]];
265 } else if ([object isKindOfClass: [MYASN1Object class]]) {
266 MYASN1Object *asn = object;
268 [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
270 [self _writeTag: asn.tag
272 constructed: asn.constructed
275 [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
281 if (!_output && !_error) {
283 [self _encode: _rootObject];
284 }@catch (NSException *x) {
285 if ($equal(x.name, MYDEREncoderException)) {
286 self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
295 @synthesize error=_error, _forcePrintableStrings;
302 #define $data(BYTES...) ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
304 TestCase(DEREncoder) {
305 CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
307 CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
308 $data(0x01, 0x01, 0xFF));
309 CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
310 $data(0x01, 0x01, 0x00));
313 CAssertEqual([MYDEREncoder encodeRootObject: $object(0) error: nil],
314 $data(0x02, 0x01, 0x00));
315 CAssertEqual([MYDEREncoder encodeRootObject: $object(1) error: nil],
316 $data(0x02, 0x01, 0x01));
317 CAssertEqual([MYDEREncoder encodeRootObject: $object(-1) error: nil],
318 $data(0x02, 0x01, 0xFF));
319 CAssertEqual([MYDEREncoder encodeRootObject: $object(72) error: nil],
320 $data(0x02, 0x01, 0x48));
321 CAssertEqual([MYDEREncoder encodeRootObject: $object(-128) error: nil],
322 $data(0x02, 0x01, 0x80));
323 CAssertEqual([MYDEREncoder encodeRootObject: $object(128) error: nil],
324 $data(0x02, 0x02, 0x00, 0x80));
325 CAssertEqual([MYDEREncoder encodeRootObject: $object(255) error: nil],
326 $data(0x02, 0x02, 0x00, 0xFF));
327 CAssertEqual([MYDEREncoder encodeRootObject: $object(-256) error: nil],
328 $data(0x02, 0x02, 0xFF, 0x00));
329 CAssertEqual([MYDEREncoder encodeRootObject: $object(12345) error: nil],
330 $data(0x02, 0x02, 0x30,0x39));
331 CAssertEqual([MYDEREncoder encodeRootObject: $object(-12345) error: nil],
332 $data(0x02, 0x02, 0xCF, 0xC7));
333 CAssertEqual([MYDEREncoder encodeRootObject: $object(123456789) error: nil],
334 $data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15));
335 CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
336 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
337 CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
338 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
341 CAssertEqual([MYDEREncoder encodeRootObject: @"hello" error: nil],
342 $data(0x13, 0x05, 'h', 'e', 'l', 'l', 'o'));
343 CAssertEqual([MYDEREncoder encodeRootObject: @"thérè" error: nil],
344 $data(0x0C, 0x07, 't', 'h', 0xC3, 0xA9, 'r', 0xC3, 0xA8));
347 CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
349 $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
352 CAssertEqual([MYDEREncoder encodeRootObject: $array($object(72), $true) error: nil],
353 $data(0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
354 CAssertEqual([MYDEREncoder encodeRootObject: $array( $array($object(72), $true),
355 $array($object(72), $true))
358 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF,
359 0x30, 0x06, 0x02, 0x01, 0x48, 0x01, 0x01, 0xFF));
363 TestCase(EncodeCert) {
364 NSError *error = nil;
365 NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];
366 id certObjects = MYBERParse(cert,&error);
368 Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]);
369 MYDEREncoder *encoder = [[MYDEREncoder alloc] initWithRootObject: certObjects];
370 encoder._forcePrintableStrings = YES; // hack for compatibility with the way CDSA writes ASN.1
371 NSData *encoded = encoder.output;
373 id reDecoded = MYBERParse(encoded, &error);
375 Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
376 [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
377 CAssertEqual(encoded,cert);
383 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
385 Redistribution and use in source and binary forms, with or without modification, are permitted
386 provided that the following conditions are met:
388 * Redistributions of source code must retain the above copyright notice, this list of conditions
389 and the following disclaimer.
390 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
391 and the following disclaimer in the documentation and/or other materials provided with the
394 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
395 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
396 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
397 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
398 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
399 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
400 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
401 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.