* 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.