MYDEREncoder.m
author Jens Alfke <jens@mooseyard.com>
Sun Jun 07 21:53:56 2009 -0700 (2009-06-07)
changeset 23 39fec79de6e8
parent 20 df9da0f6b358
permissions -rw-r--r--
A snapshot taken during the long, agonizing crawl toward getting everything running on iPhone.
jens@16
     1
//
jens@16
     2
//  MYDEREncoder.m
jens@16
     3
//  MYCrypto
jens@16
     4
//
jens@16
     5
//  Created by Jens Alfke on 5/29/09.
jens@16
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
jens@16
     7
//
jens@16
     8
jens@20
     9
// Reference:
jens@20
    10
// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
jens@20
    11
jens@16
    12
#import "MYDEREncoder.h"
jens@16
    13
#import "MYASN1Object.h"
jens@16
    14
#import "MYBERParser.h"
jens@16
    15
#import "MYOID.h"
jens@16
    16
#import "MYErrorUtils.h"
jens@16
    17
jens@16
    18
jens@16
    19
#define MYDEREncoderException @"MYDEREncoderException"
jens@16
    20
jens@16
    21
jens@16
    22
@interface MYDEREncoder ()
jens@16
    23
- (void) _encode: (id)object;
jens@16
    24
@property (retain) NSError *error;
jens@20
    25
jens@20
    26
/* Forces use of PrintableString tag for ASCII strings that contain characters not valid
jens@20
    27
    for that encoding (notably '@'). Provided to get byte-for-byte compatibility with certs
jens@20
    28
    generated by CDSA, for test cases that check this. */
jens@19
    29
@property BOOL _forcePrintableStrings;
jens@16
    30
@end
jens@16
    31
jens@16
    32
jens@16
    33
@implementation MYDEREncoder
jens@16
    34
jens@16
    35
jens@16
    36
- (id) initWithRootObject: (id)rootObject
jens@16
    37
{
jens@16
    38
    self = [super init];
jens@16
    39
    if (self != nil) {
jens@16
    40
        _rootObject = [rootObject retain];
jens@16
    41
    }
jens@16
    42
    return self;
jens@16
    43
}
jens@16
    44
jens@16
    45
+ (NSData*) encodeRootObject: (id)rootObject error: (NSError**)outError {
jens@16
    46
    MYDEREncoder *encoder = [[self alloc] initWithRootObject: rootObject];
jens@16
    47
    NSData *output = [encoder.output copy];
jens@16
    48
    if (outError) *outError = [[encoder.error retain] autorelease];
jens@16
    49
    [encoder release];
jens@16
    50
    return [output autorelease];
jens@16
    51
}
jens@16
    52
jens@16
    53
- (void) dealloc
jens@16
    54
{
jens@16
    55
    [_rootObject release];
jens@16
    56
    [_output release];
jens@19
    57
    [_error release];
jens@16
    58
    [super dealloc];
jens@16
    59
}
jens@16
    60
jens@19
    61
- (id) copyWithZone: (NSZone*)zone {
jens@19
    62
    MYDEREncoder *copy = [[[self class] alloc] init];
jens@19
    63
    copy->_forcePrintableStrings = _forcePrintableStrings;
jens@19
    64
    return copy;
jens@19
    65
}
jens@16
    66
jens@16
    67
jens@16
    68
static unsigned sizeOfUnsignedInt (UInt64 n) {
jens@16
    69
    unsigned bytes = 0;
jens@16
    70
    for (; n; n >>= 8)
jens@16
    71
        bytes++;
jens@16
    72
    return bytes;
jens@16
    73
}
jens@16
    74
jens@16
    75
static unsigned encodeUnsignedInt (UInt64 n, UInt8 buf[], BOOL padHighBit) {
jens@16
    76
    unsigned size = MAX(1U, sizeOfUnsignedInt(n));
jens@16
    77
    UInt64 bigEndian = NSSwapHostLongLongToBig(n);
jens@16
    78
    const UInt8* src = (UInt8*)&bigEndian + (8-size);
jens@16
    79
    UInt8 *dst = &buf[0];
jens@16
    80
    if (padHighBit && (*src & 0x80)) {
jens@16
    81
        *dst++ = 0;
jens@16
    82
        size++;
jens@16
    83
    }
jens@16
    84
    memcpy(dst, src, size);
jens@16
    85
    return size;
jens@16
    86
}
jens@16
    87
jens@16
    88
static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
jens@16
    89
    if (n >= 0)
jens@16
    90
        return encodeUnsignedInt(n, buf, YES);
jens@16
    91
    else {
jens@16
    92
        unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
jens@16
    93
        UInt64 bigEndian = NSSwapHostLongLongToBig(n);
jens@16
    94
        const UInt8* src = (UInt8*)&bigEndian + (8-size);
jens@16
    95
        UInt8 *dst = &buf[0];
jens@16
    96
        if (!(*src & 0x80)) {
jens@16
    97
            *dst++ = 0xFF;
jens@16
    98
            size++;
jens@16
    99
        }
jens@16
   100
        memcpy(dst, src, size);
jens@16
   101
        return size;
jens@16
   102
    }
jens@16
   103
}
jens@16
   104
jens@16
   105
jens@16
   106
- (void) _writeTag: (unsigned)tag
jens@16
   107
             class: (unsigned)tagClass
jens@16
   108
       constructed: (BOOL) constructed
jens@16
   109
            length: (size_t)length 
jens@16
   110
{
jens@16
   111
    struct {
jens@16
   112
        unsigned tag            :5;
jens@16
   113
        unsigned isConstructed  :1;
jens@16
   114
        unsigned tagClass       :2;
jens@16
   115
        unsigned length         :7;
jens@16
   116
        unsigned isLengthLong   :1;
jens@16
   117
        UInt8    extraLength[9];
jens@16
   118
    } header;
jens@16
   119
    size_t headerSize = 2;
jens@16
   120
    
jens@16
   121
    header.tag = tag;
jens@16
   122
    header.isConstructed = constructed;
jens@16
   123
    header.tagClass = tagClass;
jens@16
   124
    if (length < 128) {
jens@16
   125
        header.isLengthLong = NO;
jens@16
   126
        header.length = length;
jens@16
   127
    } else {
jens@16
   128
        header.isLengthLong = YES;
jens@16
   129
        header.length = encodeUnsignedInt(length, header.extraLength, NO);
jens@16
   130
        headerSize += header.length;
jens@16
   131
    }
jens@16
   132
    [_output appendBytes: &header length: headerSize];
jens@16
   133
}
jens@16
   134
jens@16
   135
- (void) _writeTag: (unsigned)tag
jens@16
   136
             class: (unsigned)tagClass
jens@16
   137
       constructed: (BOOL) constructed
jens@16
   138
             bytes: (const void*)bytes 
jens@16
   139
            length: (size_t)length 
jens@16
   140
{
jens@16
   141
    [self _writeTag: tag class: tagClass constructed: constructed length: length];
jens@16
   142
    [_output appendBytes: bytes length: length];
jens@16
   143
}
jens@16
   144
jens@16
   145
- (void) _writeTag: (unsigned)tag
jens@16
   146
             class: (unsigned)tagClass
jens@16
   147
       constructed: (BOOL) constructed
jens@16
   148
              data: (NSData*)data 
jens@16
   149
{
jens@16
   150
    Assert(data);
jens@16
   151
    [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
jens@16
   152
}
jens@16
   153
jens@16
   154
jens@16
   155
- (void) _encodeNumber: (NSNumber*)number {
jens@16
   156
    // Special-case detection of booleans by pointer equality, because otherwise they appear
jens@16
   157
    // identical to 0 and 1:
jens@16
   158
    if (number==$true || number==$false) {
jens@16
   159
        UInt8 value = number==$true ?0xFF :0x00;
jens@16
   160
        [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
jens@16
   161
        return;
jens@16
   162
    }
jens@16
   163
    
jens@16
   164
    const char *type = number.objCType;
jens@16
   165
    if (strlen(type) == 1) {
jens@16
   166
        switch(type[0]) {
jens@16
   167
            case 'c':
jens@16
   168
            case 'i':
jens@16
   169
            case 's':
jens@16
   170
            case 'l':
jens@16
   171
            case 'q':
jens@16
   172
            {   // Signed integers:
jens@16
   173
                UInt8 buf[9];
jens@16
   174
                size_t size = encodeSignedInt(number.longLongValue, buf);
jens@16
   175
                [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
jens@16
   176
                return;
jens@16
   177
            }
jens@16
   178
            case 'C':
jens@16
   179
            case 'I':
jens@16
   180
            case 'S':
jens@16
   181
            case 'L':
jens@16
   182
            case 'Q':
jens@16
   183
            {   // Unsigned integers:
jens@16
   184
                UInt8 buf[9];
jens@16
   185
                size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
jens@16
   186
                [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
jens@16
   187
                return;
jens@16
   188
            }
jens@16
   189
            case 'B':
jens@16
   190
            {   // bool
jens@16
   191
                UInt8 value = number.boolValue ?0xFF :0x00;
jens@16
   192
                [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
jens@16
   193
                return;
jens@16
   194
            }
jens@16
   195
        }
jens@16
   196
    }
jens@16
   197
    [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
jens@16
   198
}
jens@16
   199
jens@16
   200
jens@16
   201
- (void) _encodeString: (NSString*)string {
jens@19
   202
    static NSMutableCharacterSet *kNotPrintableCharSet;
jens@19
   203
    if (!kNotPrintableCharSet) {
jens@19
   204
        kNotPrintableCharSet = [[NSMutableCharacterSet characterSetWithCharactersInString: @" '()+,-./:=?"] retain];
jens@19
   205
        [kNotPrintableCharSet formUnionWithCharacterSet: [NSCharacterSet alphanumericCharacterSet]];
jens@19
   206
        [kNotPrintableCharSet invert];
jens@19
   207
    }
jens@16
   208
    NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
jens@19
   209
    if (data) {
jens@19
   210
        unsigned tag = 19; // printablestring (a silly arbitrary subset of ASCII defined by ASN.1)
jens@19
   211
        if (!_forcePrintableStrings && [string rangeOfCharacterFromSet: kNotPrintableCharSet].length > 0)
jens@19
   212
            tag = 20; // IA5string (full 7-bit ASCII)
jens@19
   213
        [self _writeTag: tag class: 0 constructed: NO data: data];
jens@19
   214
    } else {
jens@19
   215
        // fall back to UTF-8:
jens@16
   216
        [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
jens@19
   217
    }
jens@16
   218
}
jens@16
   219
jens@16
   220
jens@16
   221
- (void) _encodeBitString: (MYBitString*)bitString {
jens@16
   222
    NSUInteger bitCount = bitString.bitCount;
jens@16
   223
    [self _writeTag: 3 class: 0 constructed: NO length: 1 + (bitCount/8)];
jens@16
   224
    UInt8 unused = (8 - (bitCount % 8)) % 8;
jens@16
   225
    [_output appendBytes: &unused length: 1];
jens@16
   226
    [_output appendBytes: bitString.bits.bytes length: bitCount/8];
jens@16
   227
}
jens@16
   228
jens@16
   229
- (void) _encodeDate: (NSDate*)date {
jens@16
   230
    NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
jens@16
   231
    [self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
jens@16
   232
}
jens@16
   233
jens@16
   234
jens@16
   235
- (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass {
jens@19
   236
    MYDEREncoder *subEncoder = [self copy];
jens@16
   237
    for (id object in collection)
jens@16
   238
        [subEncoder _encode: object];
jens@16
   239
    [self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output];
jens@16
   240
    [subEncoder release];
jens@16
   241
}
jens@16
   242
jens@16
   243
jens@16
   244
- (void) _encode: (id)object {
jens@16
   245
    if (!_output)
jens@16
   246
        _output = [[NSMutableData alloc] initWithCapacity: 1024];
jens@16
   247
    if ([object isKindOfClass: [NSNumber class]]) {
jens@16
   248
        [self _encodeNumber: object];
jens@16
   249
    } else if ([object isKindOfClass: [NSData class]]) {
jens@16
   250
        [self _writeTag: 4 class: 0 constructed: NO data: object];
jens@16
   251
    } else if ([object isKindOfClass: [MYBitString class]]) {
jens@16
   252
        [self _encodeBitString: object];
jens@16
   253
    } else if ([object isKindOfClass: [NSString class]]) {
jens@16
   254
        [self _encodeString: object];
jens@16
   255
    } else if ([object isKindOfClass: [NSDate class]]) {
jens@16
   256
        [self _encodeDate: object];
jens@16
   257
    } else if ([object isKindOfClass: [NSNull class]]) {
jens@16
   258
        [self _writeTag: 5 class: 0 constructed: NO bytes: NULL length: 0];
jens@16
   259
    } else if ([object isKindOfClass: [NSArray class]]) {
jens@16
   260
        [self _encodeCollection: object tag: 16 class: 0];
jens@16
   261
    } else if ([object isKindOfClass: [NSSet class]]) {
jens@16
   262
        [self _encodeCollection: object tag: 17 class: 0];
jens@16
   263
    } else if ([object isKindOfClass: [MYOID class]]) {
jens@16
   264
        [self _writeTag: 6 class: 0 constructed: NO data: [object DEREncoding]];
jens@16
   265
    } else if ([object isKindOfClass: [MYASN1Object class]]) {
jens@16
   266
        MYASN1Object *asn = object;
jens@16
   267
        if (asn.components)
jens@16
   268
            [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
jens@16
   269
        else
jens@16
   270
            [self _writeTag: asn.tag 
jens@16
   271
                      class: asn.tagClass
jens@16
   272
                constructed: asn.constructed
jens@16
   273
                       data: asn.value];
jens@16
   274
    } else {
jens@16
   275
        [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
jens@16
   276
    }
jens@16
   277
}
jens@16
   278
jens@16
   279
jens@16
   280
- (NSData*) output {
jens@16
   281
    if (!_output && !_error) {
jens@16
   282
        @try{
jens@16
   283
            [self _encode: _rootObject];
jens@16
   284
        }@catch (NSException *x) {
jens@16
   285
            if ($equal(x.name, MYDEREncoderException)) {
jens@16
   286
                self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
jens@16
   287
                return nil;
jens@16
   288
            } else
jens@16
   289
                @throw(x);
jens@16
   290
        }
jens@16
   291
    }
jens@16
   292
    return _output;
jens@16
   293
}
jens@16
   294
jens@19
   295
@synthesize error=_error, _forcePrintableStrings;
jens@16
   296
jens@16
   297
jens@16
   298
@end
jens@16
   299
jens@16
   300
jens@16
   301
jens@16
   302
#define $data(BYTES...)    ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
jens@16
   303
jens@16
   304
TestCase(DEREncoder) {
jens@16
   305
    CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
jens@16
   306
                 $data(0x05, 0x00));
jens@16
   307
    CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
jens@16
   308
                 $data(0x01, 0x01, 0xFF));
jens@16
   309
    CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
jens@16
   310
                 $data(0x01, 0x01, 0x00));
jens@16
   311
jens@16
   312
    // Integers:
jens@16
   313
    CAssertEqual([MYDEREncoder encodeRootObject: $object(0) error: nil],
jens@16
   314
                 $data(0x02, 0x01, 0x00));
jens@16
   315
    CAssertEqual([MYDEREncoder encodeRootObject: $object(1) error: nil],
jens@16
   316
                 $data(0x02, 0x01, 0x01));
jens@16
   317
    CAssertEqual([MYDEREncoder encodeRootObject: $object(-1) error: nil],
jens@16
   318
                 $data(0x02, 0x01, 0xFF));
jens@16
   319
    CAssertEqual([MYDEREncoder encodeRootObject: $object(72) error: nil],
jens@16
   320
                  $data(0x02, 0x01, 0x48));
jens@16
   321
    CAssertEqual([MYDEREncoder encodeRootObject: $object(-128) error: nil],
jens@16
   322
                 $data(0x02, 0x01, 0x80));
jens@16
   323
    CAssertEqual([MYDEREncoder encodeRootObject: $object(128) error: nil],
jens@16
   324
                 $data(0x02, 0x02, 0x00, 0x80));
jens@16
   325
    CAssertEqual([MYDEREncoder encodeRootObject: $object(255) error: nil],
jens@16
   326
                 $data(0x02, 0x02, 0x00, 0xFF));
jens@16
   327
    CAssertEqual([MYDEREncoder encodeRootObject: $object(-256) error: nil],
jens@16
   328
                 $data(0x02, 0x02, 0xFF, 0x00));
jens@16
   329
    CAssertEqual([MYDEREncoder encodeRootObject: $object(12345) error: nil],
jens@16
   330
                 $data(0x02, 0x02, 0x30,0x39));
jens@16
   331
    CAssertEqual([MYDEREncoder encodeRootObject: $object(-12345) error: nil],
jens@16
   332
                 $data(0x02, 0x02, 0xCF, 0xC7));
jens@16
   333
    CAssertEqual([MYDEREncoder encodeRootObject: $object(123456789) error: nil],
jens@16
   334
                 $data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15));
jens@16
   335
    CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
jens@16
   336
                 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
jens@16
   337
    CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
jens@16
   338
                 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
jens@16
   339
jens@16
   340
    // Strings:
jens@16
   341
    CAssertEqual([MYDEREncoder encodeRootObject: @"hello" error: nil],
jens@16
   342
                 $data(0x13, 0x05, 'h', 'e', 'l', 'l', 'o'));
jens@16
   343
    CAssertEqual([MYDEREncoder encodeRootObject: @"thérè" error: nil],
jens@16
   344
                 $data(0x0C, 0x07, 't', 'h', 0xC3, 0xA9, 'r', 0xC3, 0xA8));
jens@16
   345
    
jens@16
   346
    // Dates:
jens@16
   347
    CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
jens@16
   348
                                          error: nil],
jens@16
   349
                 $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
jens@16
   350
jens@16
   351
    // Sequences:
jens@16
   352
    CAssertEqual([MYDEREncoder encodeRootObject: $array($object(72), $true) error: nil],
jens@16
   353
                 $data(0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
jens@16
   354
    CAssertEqual([MYDEREncoder encodeRootObject: $array( $array($object(72), $true), 
jens@16
   355
                                                         $array($object(72), $true))
jens@16
   356
                                          error: nil],
jens@16
   357
                 $data(0x30, 0x10,  
jens@16
   358
                       0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF,
jens@16
   359
                       0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
jens@16
   360
}
jens@16
   361
jens@16
   362
jens@16
   363
TestCase(EncodeCert) {
jens@16
   364
    NSError *error = nil;
jens@21
   365
    NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];
jens@16
   366
    id certObjects = MYBERParse(cert,&error);
jens@16
   367
    CAssertNil(error);
jens@16
   368
    Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]);
jens@19
   369
    MYDEREncoder *encoder = [[MYDEREncoder alloc] initWithRootObject: certObjects];
jens@19
   370
    encoder._forcePrintableStrings = YES;       // hack for compatibility with the way CDSA writes ASN.1
jens@19
   371
    NSData *encoded = encoder.output;
jens@16
   372
    CAssertNil(error);
jens@16
   373
    id reDecoded = MYBERParse(encoded, &error);
jens@16
   374
    CAssertNil(error);
jens@16
   375
    Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
jens@19
   376
    [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
jens@16
   377
    CAssertEqual(encoded,cert);
jens@16
   378
}
jens@21
   379
jens@21
   380
jens@21
   381
jens@21
   382
/*
jens@21
   383
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@21
   384
 
jens@21
   385
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@21
   386
 provided that the following conditions are met:
jens@21
   387
 
jens@21
   388
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@21
   389
 and the following disclaimer.
jens@21
   390
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@21
   391
 and the following disclaimer in the documentation and/or other materials provided with the
jens@21
   392
 distribution.
jens@21
   393
 
jens@21
   394
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@21
   395
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@21
   396
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@21
   397
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@21
   398
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@21
   399
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@21
   400
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@21
   401
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@21
   402
 */