MYDEREncoder.m
author Jens Alfke <jens@mooseyard.com>
Tue Jun 02 13:16:28 2009 -0700 (2009-06-02)
changeset 16 c409dbc4f068
child 17 90a70925562b
permissions -rw-r--r--
* Added ASN.1 / BER / DER utilities, to be used in generating and parsing X.509 certs.
* Added Keychain user-interaction-allowed setter. Added doc comments to MYSymmetricKey.
     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 @end
    23 
    24 
    25 @implementation MYDEREncoder
    26 
    27 
    28 - (id) initWithRootObject: (id)rootObject
    29 {
    30     self = [super init];
    31     if (self != nil) {
    32         _rootObject = [rootObject retain];
    33     }
    34     return self;
    35 }
    36 
    37 + (NSData*) encodeRootObject: (id)rootObject error: (NSError**)outError {
    38     MYDEREncoder *encoder = [[self alloc] initWithRootObject: rootObject];
    39     NSData *output = [encoder.output copy];
    40     if (outError) *outError = [[encoder.error retain] autorelease];
    41     [encoder release];
    42     return [output autorelease];
    43 }
    44 
    45 - (void) dealloc
    46 {
    47     [_rootObject release];
    48     [_output release];
    49     [super dealloc];
    50 }
    51 
    52 
    53 
    54 static unsigned sizeOfUnsignedInt (UInt64 n) {
    55     unsigned bytes = 0;
    56     for (; n; n >>= 8)
    57         bytes++;
    58     return bytes;
    59 }
    60 
    61 static unsigned encodeUnsignedInt (UInt64 n, UInt8 buf[], BOOL padHighBit) {
    62     unsigned size = MAX(1U, sizeOfUnsignedInt(n));
    63     UInt64 bigEndian = NSSwapHostLongLongToBig(n);
    64     const UInt8* src = (UInt8*)&bigEndian + (8-size);
    65     UInt8 *dst = &buf[0];
    66     if (padHighBit && (*src & 0x80)) {
    67         *dst++ = 0;
    68         size++;
    69     }
    70     memcpy(dst, src, size);
    71     return size;
    72 }
    73 
    74 static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
    75     if (n >= 0)
    76         return encodeUnsignedInt(n, buf, YES);
    77     else {
    78         unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
    79         UInt64 bigEndian = NSSwapHostLongLongToBig(n);
    80         const UInt8* src = (UInt8*)&bigEndian + (8-size);
    81         UInt8 *dst = &buf[0];
    82         if (!(*src & 0x80)) {
    83             *dst++ = 0xFF;
    84             size++;
    85         }
    86         memcpy(dst, src, size);
    87         return size;
    88     }
    89 }
    90 
    91 
    92 - (void) _writeTag: (unsigned)tag
    93              class: (unsigned)tagClass
    94        constructed: (BOOL) constructed
    95             length: (size_t)length 
    96 {
    97     struct {
    98         unsigned tag            :5;
    99         unsigned isConstructed  :1;
   100         unsigned tagClass       :2;
   101         unsigned length         :7;
   102         unsigned isLengthLong   :1;
   103         UInt8    extraLength[9];
   104     } header;
   105     size_t headerSize = 2;
   106     
   107     header.tag = tag;
   108     header.isConstructed = constructed;
   109     header.tagClass = tagClass;
   110     if (length < 128) {
   111         header.isLengthLong = NO;
   112         header.length = length;
   113     } else {
   114         header.isLengthLong = YES;
   115         header.length = encodeUnsignedInt(length, header.extraLength, NO);
   116         headerSize += header.length;
   117     }
   118     [_output appendBytes: &header length: headerSize];
   119 }
   120 
   121 - (void) _writeTag: (unsigned)tag
   122              class: (unsigned)tagClass
   123        constructed: (BOOL) constructed
   124              bytes: (const void*)bytes 
   125             length: (size_t)length 
   126 {
   127     [self _writeTag: tag class: tagClass constructed: constructed length: length];
   128     [_output appendBytes: bytes length: length];
   129 }
   130 
   131 - (void) _writeTag: (unsigned)tag
   132              class: (unsigned)tagClass
   133        constructed: (BOOL) constructed
   134               data: (NSData*)data 
   135 {
   136     Assert(data);
   137     [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
   138 }
   139 
   140 
   141 - (void) _encodeNumber: (NSNumber*)number {
   142     // Special-case detection of booleans by pointer equality, because otherwise they appear
   143     // identical to 0 and 1:
   144     if (number==$true || number==$false) {
   145         UInt8 value = number==$true ?0xFF :0x00;
   146         [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
   147         return;
   148     }
   149     
   150     const char *type = number.objCType;
   151     if (strlen(type) == 1) {
   152         switch(type[0]) {
   153             case 'c':
   154             case 'i':
   155             case 's':
   156             case 'l':
   157             case 'q':
   158             {   // Signed integers:
   159                 UInt8 buf[9];
   160                 size_t size = encodeSignedInt(number.longLongValue, buf);
   161                 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   162                 return;
   163             }
   164             case 'C':
   165             case 'I':
   166             case 'S':
   167             case 'L':
   168             case 'Q':
   169             {   // Unsigned integers:
   170                 UInt8 buf[9];
   171                 size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
   172                 [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   173                 return;
   174             }
   175             case 'B':
   176             {   // bool
   177                 UInt8 value = number.boolValue ?0xFF :0x00;
   178                 [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
   179                 return;
   180             }
   181         }
   182     }
   183     [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
   184 }
   185 
   186 
   187 - (void) _encodeString: (NSString*)string {
   188     NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
   189     if (data)
   190         [self _writeTag: 19 class: 0 constructed: NO data: data];
   191     else
   192         [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
   193 }
   194 
   195 
   196 - (void) _encodeBitString: (MYBitString*)bitString {
   197     NSUInteger bitCount = bitString.bitCount;
   198     [self _writeTag: 3 class: 0 constructed: NO length: 1 + (bitCount/8)];
   199     UInt8 unused = (8 - (bitCount % 8)) % 8;
   200     [_output appendBytes: &unused length: 1];
   201     [_output appendBytes: bitString.bits.bytes length: bitCount/8];
   202 }
   203 
   204 - (void) _encodeDate: (NSDate*)date {
   205     NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
   206     Log(@"Encoded %@ as '%@'",date,dateStr);//TEMP
   207     [self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
   208 }
   209 
   210 
   211 - (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass {
   212     MYDEREncoder *subEncoder = [[[self class] alloc] init];
   213     for (id object in collection)
   214         [subEncoder _encode: object];
   215     [self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output];
   216     [subEncoder release];
   217 }
   218 
   219 
   220 - (void) _encode: (id)object {
   221     if (!_output)
   222         _output = [[NSMutableData alloc] initWithCapacity: 1024];
   223     if ([object isKindOfClass: [NSNumber class]]) {
   224         [self _encodeNumber: object];
   225     } else if ([object isKindOfClass: [NSData class]]) {
   226         [self _writeTag: 4 class: 0 constructed: NO data: object];
   227     } else if ([object isKindOfClass: [MYBitString class]]) {
   228         [self _encodeBitString: object];
   229     } else if ([object isKindOfClass: [NSString class]]) {
   230         [self _encodeString: object];
   231     } else if ([object isKindOfClass: [NSDate class]]) {
   232         [self _encodeDate: object];
   233     } else if ([object isKindOfClass: [NSNull class]]) {
   234         [self _writeTag: 5 class: 0 constructed: NO bytes: NULL length: 0];
   235     } else if ([object isKindOfClass: [NSArray class]]) {
   236         [self _encodeCollection: object tag: 16 class: 0];
   237     } else if ([object isKindOfClass: [NSSet class]]) {
   238         [self _encodeCollection: object tag: 17 class: 0];
   239     } else if ([object isKindOfClass: [MYOID class]]) {
   240         [self _writeTag: 6 class: 0 constructed: NO data: [object DEREncoding]];
   241     } else if ([object isKindOfClass: [MYASN1Object class]]) {
   242         MYASN1Object *asn = object;
   243         if (asn.components)
   244             [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
   245         else
   246             [self _writeTag: asn.tag 
   247                       class: asn.tagClass
   248                 constructed: asn.constructed
   249                        data: asn.value];
   250     } else {
   251         [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
   252     }
   253 }
   254 
   255 
   256 - (NSData*) output {
   257     if (!_output && !_error) {
   258         @try{
   259             [self _encode: _rootObject];
   260         }@catch (NSException *x) {
   261             if ($equal(x.name, MYDEREncoderException)) {
   262                 self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
   263                 return nil;
   264             } else
   265                 @throw(x);
   266         }
   267     }
   268     return _output;
   269 }
   270 
   271 @synthesize error=_error;
   272 
   273 
   274 @end
   275 
   276 
   277 
   278 #define $data(BYTES...)    ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
   279 
   280 TestCase(DEREncoder) {
   281     CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
   282                  $data(0x05, 0x00));
   283     CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
   284                  $data(0x01, 0x01, 0xFF));
   285     CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
   286                  $data(0x01, 0x01, 0x00));
   287 
   288     // Integers:
   289     CAssertEqual([MYDEREncoder encodeRootObject: $object(0) error: nil],
   290                  $data(0x02, 0x01, 0x00));
   291     CAssertEqual([MYDEREncoder encodeRootObject: $object(1) error: nil],
   292                  $data(0x02, 0x01, 0x01));
   293     CAssertEqual([MYDEREncoder encodeRootObject: $object(-1) error: nil],
   294                  $data(0x02, 0x01, 0xFF));
   295     CAssertEqual([MYDEREncoder encodeRootObject: $object(72) error: nil],
   296                   $data(0x02, 0x01, 0x48));
   297     CAssertEqual([MYDEREncoder encodeRootObject: $object(-128) error: nil],
   298                  $data(0x02, 0x01, 0x80));
   299     CAssertEqual([MYDEREncoder encodeRootObject: $object(128) error: nil],
   300                  $data(0x02, 0x02, 0x00, 0x80));
   301     CAssertEqual([MYDEREncoder encodeRootObject: $object(255) error: nil],
   302                  $data(0x02, 0x02, 0x00, 0xFF));
   303     CAssertEqual([MYDEREncoder encodeRootObject: $object(-256) error: nil],
   304                  $data(0x02, 0x02, 0xFF, 0x00));
   305     CAssertEqual([MYDEREncoder encodeRootObject: $object(12345) error: nil],
   306                  $data(0x02, 0x02, 0x30,0x39));
   307     CAssertEqual([MYDEREncoder encodeRootObject: $object(-12345) error: nil],
   308                  $data(0x02, 0x02, 0xCF, 0xC7));
   309     CAssertEqual([MYDEREncoder encodeRootObject: $object(123456789) error: nil],
   310                  $data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15));
   311     CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
   312                  $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
   313     CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
   314                  $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
   315 
   316     // Strings:
   317     CAssertEqual([MYDEREncoder encodeRootObject: @"hello" error: nil],
   318                  $data(0x13, 0x05, 'h', 'e', 'l', 'l', 'o'));
   319     CAssertEqual([MYDEREncoder encodeRootObject: @"thérè" error: nil],
   320                  $data(0x0C, 0x07, 't', 'h', 0xC3, 0xA9, 'r', 0xC3, 0xA8));
   321     
   322     // Dates:
   323     CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
   324                                           error: nil],
   325                  $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
   326 
   327     // Sequences:
   328     CAssertEqual([MYDEREncoder encodeRootObject: $array($object(72), $true) error: nil],
   329                  $data(0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
   330     CAssertEqual([MYDEREncoder encodeRootObject: $array( $array($object(72), $true), 
   331                                                          $array($object(72), $true))
   332                                           error: nil],
   333                  $data(0x30, 0x10,  
   334                        0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF,
   335                        0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
   336 }
   337 
   338 
   339 TestCase(EncodeCert) {
   340     NSError *error = nil;
   341     NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];  //TEMP
   342     id certObjects = MYBERParse(cert,&error);
   343     CAssertNil(error);
   344     Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]);
   345     NSData *encoded = [MYDEREncoder encodeRootObject: certObjects error: &error];
   346     CAssertNil(error);
   347     id reDecoded = MYBERParse(encoded, &error);
   348     CAssertNil(error);
   349     Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
   350     [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
   351     CAssertEqual(encoded,cert);
   352 }