MYDEREncoder.m
author Jens Alfke <jens@mooseyard.com>
Thu Jun 04 18:36:30 2009 -0700 (2009-06-04)
changeset 19 f6c91b9da05b
parent 18 a06e44b9b898
child 20 df9da0f6b358
permissions -rw-r--r--
Whew! MYParsedCertificate can now generate certs from scratch. Also added improvements and fixes to the BER/DER codecs.
     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 #import "MYDEREncoder.h"
    10 #import "MYASN1Object.h"
    11 #import "MYBERParser.h"
    12 #import "MYOID.h"
    13 #import "MYErrorUtils.h"
    14 
    15 
    16 #define MYDEREncoderException @"MYDEREncoderException"
    17 
    18 
    19 @interface MYDEREncoder ()
    20 - (void) _encode: (id)object;
    21 @property (retain) NSError *error;
    22 @property BOOL _forcePrintableStrings;
    23 @end
    24 
    25 
    26 @implementation MYDEREncoder
    27 
    28 
    29 - (id) initWithRootObject: (id)rootObject
    30 {
    31     self = [super init];
    32     if (self != nil) {
    33         _rootObject = [rootObject retain];
    34     }
    35     return self;
    36 }
    37 
    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];
    42     [encoder release];
    43     return [output autorelease];
    44 }
    45 
    46 - (void) dealloc
    47 {
    48     [_rootObject release];
    49     [_output release];
    50     [_error release];
    51     [super dealloc];
    52 }
    53 
    54 - (id) copyWithZone: (NSZone*)zone {
    55     MYDEREncoder *copy = [[[self class] alloc] init];
    56     copy->_forcePrintableStrings = _forcePrintableStrings;
    57     return copy;
    58 }
    59 
    60 
    61 static unsigned sizeOfUnsignedInt (UInt64 n) {
    62     unsigned bytes = 0;
    63     for (; n; n >>= 8)
    64         bytes++;
    65     return bytes;
    66 }
    67 
    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);
    72     UInt8 *dst = &buf[0];
    73     if (padHighBit && (*src & 0x80)) {
    74         *dst++ = 0;
    75         size++;
    76     }
    77     memcpy(dst, src, size);
    78     return size;
    79 }
    80 
    81 static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
    82     if (n >= 0)
    83         return encodeUnsignedInt(n, buf, YES);
    84     else {
    85         unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
    86         UInt64 bigEndian = NSSwapHostLongLongToBig(n);
    87         const UInt8* src = (UInt8*)&bigEndian + (8-size);
    88         UInt8 *dst = &buf[0];
    89         if (!(*src & 0x80)) {
    90             *dst++ = 0xFF;
    91             size++;
    92         }
    93         memcpy(dst, src, size);
    94         return size;
    95     }
    96 }
    97 
    98 
    99 - (void) _writeTag: (unsigned)tag
   100              class: (unsigned)tagClass
   101        constructed: (BOOL) constructed
   102             length: (size_t)length 
   103 {
   104     struct {
   105         unsigned tag            :5;
   106         unsigned isConstructed  :1;
   107         unsigned tagClass       :2;
   108         unsigned length         :7;
   109         unsigned isLengthLong   :1;
   110         UInt8    extraLength[9];
   111     } header;
   112     size_t headerSize = 2;
   113     
   114     header.tag = tag;
   115     header.isConstructed = constructed;
   116     header.tagClass = tagClass;
   117     if (length < 128) {
   118         header.isLengthLong = NO;
   119         header.length = length;
   120     } else {
   121         header.isLengthLong = YES;
   122         header.length = encodeUnsignedInt(length, header.extraLength, NO);
   123         headerSize += header.length;
   124     }
   125     [_output appendBytes: &header length: headerSize];
   126 }
   127 
   128 - (void) _writeTag: (unsigned)tag
   129              class: (unsigned)tagClass
   130        constructed: (BOOL) constructed
   131              bytes: (const void*)bytes 
   132             length: (size_t)length 
   133 {
   134     [self _writeTag: tag class: tagClass constructed: constructed length: length];
   135     [_output appendBytes: bytes length: length];
   136 }
   137 
   138 - (void) _writeTag: (unsigned)tag
   139              class: (unsigned)tagClass
   140        constructed: (BOOL) constructed
   141               data: (NSData*)data 
   142 {
   143     Assert(data);
   144     [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
   145 }
   146 
   147 
   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];
   154         return;
   155     }
   156     
   157     const char *type = number.objCType;
   158     if (strlen(type) == 1) {
   159         switch(type[0]) {
   160             case 'c':
   161             case 'i':
   162             case 's':
   163             case 'l':
   164             case 'q':
   165             {   // Signed integers:
   166                 UInt8 buf[9];
   167                 size_t size = encodeSignedInt(number.longLongValue, buf);
   168                 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   169                 return;
   170             }
   171             case 'C':
   172             case 'I':
   173             case 'S':
   174             case 'L':
   175             case 'Q':
   176             {   // Unsigned integers:
   177                 UInt8 buf[9];
   178                 size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
   179                 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   180                 return;
   181             }
   182             case 'B':
   183             {   // bool
   184                 UInt8 value = number.boolValue ?0xFF :0x00;
   185                 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
   186                 return;
   187             }
   188         }
   189     }
   190     [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
   191 }
   192 
   193 
   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];
   200     }
   201     NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
   202     if (data) {
   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];
   207     } else {
   208         // fall back to UTF-8:
   209         [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
   210     }
   211 }
   212 
   213 
   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];
   220 }
   221 
   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]];
   226 }
   227 
   228 
   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];
   235 }
   236 
   237 
   238 - (void) _encode: (id)object {
   239     if (!_output)
   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;
   261         if (asn.components)
   262             [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
   263         else
   264             [self _writeTag: asn.tag 
   265                       class: asn.tagClass
   266                 constructed: asn.constructed
   267                        data: asn.value];
   268     } else {
   269         [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
   270     }
   271 }
   272 
   273 
   274 - (NSData*) output {
   275     if (!_output && !_error) {
   276         @try{
   277             [self _encode: _rootObject];
   278         }@catch (NSException *x) {
   279             if ($equal(x.name, MYDEREncoderException)) {
   280                 self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
   281                 return nil;
   282             } else
   283                 @throw(x);
   284         }
   285     }
   286     return _output;
   287 }
   288 
   289 @synthesize error=_error, _forcePrintableStrings;
   290 
   291 
   292 @end
   293 
   294 
   295 
   296 #define $data(BYTES...)    ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
   297 
   298 TestCase(DEREncoder) {
   299     CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
   300                  $data(0x05, 0x00));
   301     CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
   302                  $data(0x01, 0x01, 0xFF));
   303     CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
   304                  $data(0x01, 0x01, 0x00));
   305 
   306     // Integers:
   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));
   333 
   334     // Strings:
   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));
   339     
   340     // Dates:
   341     CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
   342                                           error: nil],
   343                  $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
   344 
   345     // Sequences:
   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))
   350                                           error: nil],
   351                  $data(0x30, 0x10,  
   352                        0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF,
   353                        0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
   354 }
   355 
   356 
   357 TestCase(EncodeCert) {
   358     NSError *error = nil;
   359     NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];  //TEMP
   360     id certObjects = MYBERParse(cert,&error);
   361     CAssertNil(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;
   366     CAssertNil(error);
   367     id reDecoded = MYBERParse(encoded, &error);
   368     CAssertNil(error);
   369     Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
   370     [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
   371     CAssertEqual(encoded,cert);
   372 }