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