MYDEREncoder.m
author Jens Alfke <jens@mooseyard.com>
Tue Jun 09 23:58:03 2009 -0700 (2009-06-09)
changeset 24 6856e071d25a
parent 20 df9da0f6b358
permissions -rw-r--r--
* 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.
     1 //
     2 //  MYDEREncoder.m
     3 //  MYCrypto
     4 //
     5 //  Created by Jens Alfke on 5/29/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 // Reference:
    10 // <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
    11 
    12 #import "MYDEREncoder.h"
    13 #import "MYASN1Object.h"
    14 #import "MYBERParser.h"
    15 #import "MYOID.h"
    16 #import "MYErrorUtils.h"
    17 
    18 
    19 #define MYDEREncoderException @"MYDEREncoderException"
    20 
    21 
    22 @interface MYDEREncoder ()
    23 - (void) _encode: (id)object;
    24 @property (retain) NSError *error;
    25 
    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;
    30 @end
    31 
    32 
    33 @implementation MYDEREncoder
    34 
    35 
    36 - (id) initWithRootObject: (id)rootObject
    37 {
    38     self = [super init];
    39     if (self != nil) {
    40         _rootObject = [rootObject retain];
    41     }
    42     return self;
    43 }
    44 
    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];
    49     [encoder release];
    50     return [output autorelease];
    51 }
    52 
    53 - (void) dealloc
    54 {
    55     [_rootObject release];
    56     [_output release];
    57     [_error release];
    58     [super dealloc];
    59 }
    60 
    61 - (id) copyWithZone: (NSZone*)zone {
    62     MYDEREncoder *copy = [[[self class] alloc] init];
    63     copy->_forcePrintableStrings = _forcePrintableStrings;
    64     return copy;
    65 }
    66 
    67 
    68 static unsigned sizeOfUnsignedInt (UInt64 n) {
    69     unsigned bytes = 0;
    70     for (; n; n >>= 8)
    71         bytes++;
    72     return bytes;
    73 }
    74 
    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);
    79     UInt8 *dst = &buf[0];
    80     if (padHighBit && (*src & 0x80)) {
    81         *dst++ = 0;
    82         size++;
    83     }
    84     memcpy(dst, src, size);
    85     return size;
    86 }
    87 
    88 static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
    89     if (n >= 0)
    90         return encodeUnsignedInt(n, buf, YES);
    91     else {
    92         unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
    93         UInt64 bigEndian = NSSwapHostLongLongToBig(n);
    94         const UInt8* src = (UInt8*)&bigEndian + (8-size);
    95         UInt8 *dst = &buf[0];
    96         if (!(*src & 0x80)) {
    97             *dst++ = 0xFF;
    98             size++;
    99         }
   100         memcpy(dst, src, size);
   101         return size;
   102     }
   103 }
   104 
   105 
   106 - (void) _writeTag: (unsigned)tag
   107              class: (unsigned)tagClass
   108        constructed: (BOOL) constructed
   109             length: (size_t)length 
   110 {
   111     struct {
   112         unsigned tag            :5;
   113         unsigned isConstructed  :1;
   114         unsigned tagClass       :2;
   115         unsigned length         :7;
   116         unsigned isLengthLong   :1;
   117         UInt8    extraLength[9];
   118     } header;
   119     size_t headerSize = 2;
   120     
   121     header.tag = tag;
   122     header.isConstructed = constructed;
   123     header.tagClass = tagClass;
   124     if (length < 128) {
   125         header.isLengthLong = NO;
   126         header.length = length;
   127     } else {
   128         header.isLengthLong = YES;
   129         header.length = encodeUnsignedInt(length, header.extraLength, NO);
   130         headerSize += header.length;
   131     }
   132     [_output appendBytes: &header length: headerSize];
   133 }
   134 
   135 - (void) _writeTag: (unsigned)tag
   136              class: (unsigned)tagClass
   137        constructed: (BOOL) constructed
   138              bytes: (const void*)bytes 
   139             length: (size_t)length 
   140 {
   141     [self _writeTag: tag class: tagClass constructed: constructed length: length];
   142     [_output appendBytes: bytes length: length];
   143 }
   144 
   145 - (void) _writeTag: (unsigned)tag
   146              class: (unsigned)tagClass
   147        constructed: (BOOL) constructed
   148               data: (NSData*)data 
   149 {
   150     Assert(data);
   151     [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
   152 }
   153 
   154 
   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];
   161         return;
   162     }
   163     
   164     const char *type = number.objCType;
   165     if (strlen(type) == 1) {
   166         switch(type[0]) {
   167             case 'c':
   168             case 'i':
   169             case 's':
   170             case 'l':
   171             case 'q':
   172             {   // Signed integers:
   173                 UInt8 buf[9];
   174                 size_t size = encodeSignedInt(number.longLongValue, buf);
   175                 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   176                 return;
   177             }
   178             case 'C':
   179             case 'I':
   180             case 'S':
   181             case 'L':
   182             case 'Q':
   183             {   // Unsigned integers:
   184                 UInt8 buf[9];
   185                 size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
   186                 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   187                 return;
   188             }
   189             case 'B':
   190             {   // bool
   191                 UInt8 value = number.boolValue ?0xFF :0x00;
   192                 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
   193                 return;
   194             }
   195         }
   196     }
   197     [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
   198 }
   199 
   200 
   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];
   207     }
   208     NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
   209     if (data) {
   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];
   214     } else {
   215         // fall back to UTF-8:
   216         [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
   217     }
   218 }
   219 
   220 
   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];
   227 }
   228 
   229 - (void) _encodeDate: (NSDate*)date {
   230     NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
   231     [self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
   232 }
   233 
   234 
   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];
   241 }
   242 
   243 
   244 - (void) _encode: (id)object {
   245     if (!_output)
   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;
   267         if (asn.components)
   268             [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
   269         else
   270             [self _writeTag: asn.tag 
   271                       class: asn.tagClass
   272                 constructed: asn.constructed
   273                        data: asn.value];
   274     } else {
   275         [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
   276     }
   277 }
   278 
   279 
   280 - (NSData*) output {
   281     if (!_output && !_error) {
   282         @try{
   283             [self _encode: _rootObject];
   284         }@catch (NSException *x) {
   285             if ($equal(x.name, MYDEREncoderException)) {
   286                 self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
   287                 return nil;
   288             } else
   289                 @throw(x);
   290         }
   291     }
   292     return _output;
   293 }
   294 
   295 @synthesize error=_error, _forcePrintableStrings;
   296 
   297 
   298 @end
   299 
   300 
   301 
   302 #define $data(BYTES...)    ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
   303 
   304 TestCase(DEREncoder) {
   305     CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
   306                  $data(0x05, 0x00));
   307     CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
   308                  $data(0x01, 0x01, 0xFF));
   309     CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
   310                  $data(0x01, 0x01, 0x00));
   311 
   312     // Integers:
   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));
   339 
   340     // Strings:
   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));
   345     
   346     // Dates:
   347     CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
   348                                           error: nil],
   349                  $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
   350 
   351     // Sequences:
   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))
   356                                           error: nil],
   357                  $data(0x30, 0x10,  
   358                        0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF,
   359                        0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
   360 }
   361 
   362 
   363 TestCase(EncodeCert) {
   364     NSError *error = nil;
   365     NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];
   366     id certObjects = MYBERParse(cert,&error);
   367     CAssertNil(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;
   372     CAssertNil(error);
   373     id reDecoded = MYBERParse(encoded, &error);
   374     CAssertNil(error);
   375     Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
   376     [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
   377     CAssertEqual(encoded,cert);
   378 }
   379 
   380 
   381 
   382 /*
   383  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   384  
   385  Redistribution and use in source and binary forms, with or without modification, are permitted
   386  provided that the following conditions are met:
   387  
   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
   392  distribution.
   393  
   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.
   402  */