MYBERParser.m
author Jens Alfke <jens@mooseyard.com>
Fri Jun 05 08:57:18 2009 -0700 (2009-06-05)
changeset 20 df9da0f6b358
parent 19 f6c91b9da05b
child 21 2c300b15b381
permissions -rw-r--r--
Factored out the name accessors of MYParsedCertificate into a new class MYCertificateName, so that both subject and issuer can be accessed. A bit of other cleanup too.
jens@16
     1
//
jens@16
     2
//  MYBERParser.m
jens@16
     3
//  MYCrypto
jens@16
     4
//
jens@16
     5
//  Created by Jens Alfke on 6/2/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 "MYBERParser.h"
jens@16
    13
#import "MYASN1Object.h"
jens@16
    14
#import "MYOID.h"
jens@16
    15
#import "MYErrorUtils.h"
jens@16
    16
#import "CollectionUtils.h"
jens@16
    17
#import "Test.h"
jens@16
    18
jens@16
    19
jens@16
    20
#define MYBERParserException @"MYBERParserException"
jens@16
    21
jens@16
    22
jens@16
    23
jens@16
    24
typedef struct {
jens@17
    25
    unsigned tag            :5;
jens@17
    26
    unsigned isConstructed  :1;
jens@17
    27
    unsigned tagClass       :2;
jens@17
    28
    unsigned length         :7;
jens@17
    29
    unsigned isLengthLong   :1;
jens@17
    30
} BERHeader;
jens@17
    31
jens@17
    32
typedef struct {
jens@16
    33
    const uint8_t *nextChar;
jens@16
    34
    size_t length;
jens@16
    35
} InputData;
jens@16
    36
jens@16
    37
jens@16
    38
static void requireLength (size_t length, size_t expectedLength) {
jens@16
    39
    if (length != expectedLength)
jens@16
    40
        [NSException raise: MYBERParserException format: @"Unexpected value length"];
jens@16
    41
}
jens@16
    42
jens@16
    43
jens@16
    44
static const void* readOrDie (InputData *input, size_t len) {
jens@16
    45
    if (len > input->length)
jens@16
    46
        [NSException raise: MYBERParserException format: @"Unexpected EOF on input"];
jens@16
    47
    const void *bytes = input->nextChar;
jens@16
    48
    input->nextChar += len;
jens@16
    49
    input->length -= len;
jens@16
    50
    return bytes;
jens@16
    51
}
jens@16
    52
jens@16
    53
jens@16
    54
static NSData* readDataOrDie(InputData *input, size_t length) {
jens@16
    55
    return [NSMutableData dataWithBytes: readOrDie(input,length) length: length];
jens@16
    56
}
jens@16
    57
jens@16
    58
jens@16
    59
static NSString* readStringOrDie(InputData *input, size_t length, NSStringEncoding encoding) {
jens@16
    60
    NSString *str = [[NSString alloc] initWithBytes: readOrDie(input,length) 
jens@16
    61
                                             length: length
jens@16
    62
                                           encoding: encoding];
jens@16
    63
    if (!str)
jens@16
    64
        [NSException raise: MYBERParserException format: @"Unparseable string"];
jens@16
    65
    return [str autorelease];
jens@16
    66
}    
jens@16
    67
jens@16
    68
jens@16
    69
static uint32_t readBigEndianUnsignedInteger (InputData *input, size_t length) {
jens@16
    70
    if (length == 0 || length > 4)
jens@16
    71
        [NSException raise: MYBERParserException format: @"Invalid integer length"];
jens@16
    72
    uint32_t result = 0;
jens@16
    73
    memcpy(((uint8_t*)&result)+(4-length), readOrDie(input, length), length);
jens@16
    74
    return result;
jens@16
    75
}
jens@16
    76
jens@16
    77
static int32_t readBigEndianSignedInteger (InputData *input, size_t length) {
jens@16
    78
    int32_t result = (int32_t) readBigEndianUnsignedInteger(input,length);
jens@16
    79
    uint8_t *dst = ((uint8_t*)&result)+(4-length);
jens@16
    80
    if (*dst & 0x80) { // sign-extend negative value
jens@16
    81
        while (--dst >= (uint8_t*)&result)
jens@16
    82
            *dst = 0xFF;
jens@16
    83
    }
jens@16
    84
    return result;
jens@16
    85
}
jens@16
    86
jens@16
    87
jens@16
    88
NSDateFormatter* MYBERGeneralizedTimeFormatter() {
jens@16
    89
    static NSDateFormatter *sFmt;
jens@16
    90
    if (!sFmt) {
jens@16
    91
        sFmt = [[NSDateFormatter alloc] init];
jens@16
    92
        sFmt.dateFormat = @"yyyyMMddHHmmss'Z'";
jens@16
    93
        sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
jens@16
    94
    }
jens@16
    95
    return sFmt;
jens@16
    96
}
jens@16
    97
jens@16
    98
NSDateFormatter* MYBERUTCTimeFormatter() {
jens@16
    99
    static NSDateFormatter *sFmt;
jens@16
   100
    if (!sFmt) {
jens@16
   101
        sFmt = [[NSDateFormatter alloc] init];
jens@16
   102
        sFmt.dateFormat = @"yyMMddHHmmss'Z'";
jens@16
   103
        sFmt.timeZone = [NSTimeZone timeZoneWithName: @"GMT"];
jens@16
   104
    }
jens@16
   105
    return sFmt;
jens@16
   106
}
jens@16
   107
jens@16
   108
static NSDate* parseDate (NSString *dateStr, unsigned tag) {
jens@20
   109
    //FIX: There are more date formats possible; need to try them all. (see "Layman's Guide", 5.17)
jens@16
   110
    NSDateFormatter *fmt = (tag==23 ?MYBERUTCTimeFormatter() :MYBERGeneralizedTimeFormatter());
jens@16
   111
    NSDate *date = [fmt dateFromString: dateStr];
jens@16
   112
    if (!date)
jens@16
   113
        [NSException raise: MYBERParserException format: @"Unparseable date '%@'", dateStr];
jens@16
   114
    return date;
jens@16
   115
}
jens@16
   116
jens@16
   117
jens@17
   118
static size_t readHeader(InputData *input, BERHeader *header) {
jens@17
   119
    memcpy(header, readOrDie(input,2), 2);
jens@17
   120
    if (header->tag == 0x1F)
jens@17
   121
        [NSException raise: MYBERParserException format: @"Long tags not supported"];
jens@17
   122
    if (!header->isLengthLong)
jens@17
   123
        return header->length;
jens@17
   124
    else {
jens@17
   125
        if (header->length == 0)
jens@17
   126
            [NSException raise: MYBERParserException format: @"Indefinite length not supported"];
jens@17
   127
        return NSSwapBigIntToHost(readBigEndianUnsignedInteger(input,header->length));
jens@17
   128
    }
jens@17
   129
}
jens@17
   130
jens@17
   131
jens@16
   132
static id parseBER(InputData *input) {
jens@17
   133
    BERHeader header;
jens@17
   134
    size_t length = readHeader(input,&header);
jens@16
   135
    
jens@16
   136
    Class defaultClass = [MYASN1Object class];
jens@16
   137
    
jens@16
   138
    // Tag values can be found in <Security/x509defs.h>. I'm not using them here because that
jens@16
   139
    // header does not exist on iPhone!
jens@16
   140
    
jens@16
   141
    if (header.isConstructed) {
jens@16
   142
        // Constructed:
jens@16
   143
        NSMutableArray *items = $marray();
jens@16
   144
        InputData subInput = {input->nextChar, length};
jens@16
   145
        while (subInput.length > 0) {
jens@16
   146
            [items addObject: parseBER(&subInput)];
jens@16
   147
        }
jens@16
   148
        input->nextChar += length;
jens@16
   149
        input->length -= length;
jens@16
   150
jens@16
   151
        switch (header.tag) {
jens@16
   152
            case 16: // sequence
jens@16
   153
                return items;
jens@16
   154
            case 17: // set
jens@16
   155
                return [NSSet setWithArray: items];
jens@16
   156
            default:
jens@16
   157
                return [[[MYASN1Object alloc] initWithTag: header.tag
jens@16
   158
                                                  ofClass: header.tagClass
jens@16
   159
                                               components: items] autorelease];
jens@16
   160
        }
jens@16
   161
    } else {
jens@16
   162
        // Primitive:
jens@16
   163
        switch (header.tag) {
jens@16
   164
            case 1: { // boolean
jens@16
   165
                requireLength(length,1);
jens@16
   166
                return *(const uint8_t*)readOrDie(input, 1) ?$true :$false;
jens@16
   167
            }
jens@16
   168
            case 2: // integer
jens@16
   169
            case 10: // enum
jens@16
   170
            {
jens@16
   171
                if (length <= 4) {
jens@16
   172
                    int32_t value = NSSwapBigIntToHost(readBigEndianSignedInteger(input,length));
jens@16
   173
                    return [NSNumber numberWithInteger: value];
jens@16
   174
                } else {
jens@16
   175
                    // Big integer!
jens@16
   176
                    defaultClass = [MYASN1BigInteger class];
jens@16
   177
                    break;
jens@16
   178
                }
jens@16
   179
            }
jens@16
   180
            case 3: // bitstring
jens@16
   181
            {
jens@16
   182
                UInt8 unusedBits = *(const UInt8*) readOrDie(input, 1);
jens@16
   183
                if (unusedBits)
jens@16
   184
                    Log(@"Bit-string has %u unused bits", (unsigned)unusedBits);
jens@16
   185
                if (unusedBits > 7 || length < 1)
jens@16
   186
                    [NSException raise: MYBERParserException format: @"Bogus bit-string"];
jens@16
   187
                return [[[MYBitString alloc] initWithBits: readDataOrDie(input, length-1)
jens@16
   188
                                                    count: 8*(length-1) - unusedBits] autorelease];
jens@16
   189
            }
jens@16
   190
            case 4: // octetstring
jens@16
   191
                return readDataOrDie(input, length);
jens@16
   192
            case 5: // null
jens@16
   193
                requireLength(length,0);
jens@16
   194
                return [NSNull null];
jens@16
   195
            case 6: // OID
jens@16
   196
                return [[[MYOID alloc] initWithBEREncoding: readDataOrDie(input, length)] autorelease];
jens@16
   197
            case 12: // UTF8String
jens@16
   198
                return readStringOrDie(input,length,NSUTF8StringEncoding);
jens@16
   199
            case 18: // numeric string
jens@16
   200
            case 19: // printable string:
jens@19
   201
            case 22: // IA5 string:
jens@19
   202
            case 20: // T61 string:
jens@19
   203
            {
jens@19
   204
                NSString *string = readStringOrDie(input,length,NSASCIIStringEncoding);
jens@19
   205
                if (string)
jens@19
   206
                    return string;
jens@19
   207
                else
jens@19
   208
                    break;  // if decoding fails, fall back to generic MYASN1Object
jens@19
   209
            }
jens@16
   210
            case 23: // UTC time:
jens@16
   211
            case 24: // Generalized time:
jens@16
   212
                return parseDate(readStringOrDie(input,length,NSASCIIStringEncoding), header.tag);
jens@16
   213
            default:
jens@16
   214
                break;
jens@16
   215
        }
jens@16
   216
    }
jens@16
   217
jens@16
   218
    // Generic case -- create and return a MYASN1Object:
jens@16
   219
    NSData *value = readDataOrDie(input, length);
jens@16
   220
    id result = [[[defaultClass alloc] initWithTag: header.tag
jens@16
   221
                                           ofClass: header.tagClass 
jens@16
   222
                                       constructed: header.isConstructed
jens@16
   223
                                             value: value] autorelease];
jens@16
   224
    if( defaultClass == [MYASN1Object class])
jens@16
   225
        Warn(@"parseBER: Returning default %@", result);
jens@16
   226
    return result;
jens@16
   227
}
jens@17
   228
    
jens@17
   229
    
jens@17
   230
static void exceptionToError (NSException *x, NSError **outError) {
jens@17
   231
    if ($equal(x.name, MYBERParserException)) {
jens@17
   232
        if (outError)
jens@17
   233
            *outError = MYError(1,MYASN1ErrorDomain, @"%@", x.reason);
jens@17
   234
    } else {
jens@17
   235
        @throw(x);
jens@17
   236
    }
jens@17
   237
}
jens@16
   238
jens@16
   239
jens@16
   240
id MYBERParse (NSData *ber, NSError **outError) {
jens@16
   241
    @try{
jens@16
   242
        InputData input = {ber.bytes, ber.length};
jens@16
   243
        return parseBER(&input);
jens@16
   244
    }@catch (NSException *x) {
jens@17
   245
        exceptionToError(x,outError);
jens@16
   246
    }
jens@16
   247
    return nil;
jens@16
   248
}
jens@16
   249
jens@16
   250
jens@17
   251
size_t MYBERGetLength (NSData *ber, NSError **outError) {
jens@17
   252
    @try{
jens@17
   253
        InputData input = {ber.bytes, ber.length};
jens@17
   254
        BERHeader header;
jens@17
   255
        return readHeader(&input,&header);
jens@17
   256
    }@catch (NSException *x) {
jens@17
   257
        exceptionToError(x,outError);
jens@17
   258
    }
jens@17
   259
    return 0;
jens@17
   260
}
jens@17
   261
jens@17
   262
const void* MYBERGetContents (NSData *ber, NSError **outError) {
jens@17
   263
    @try{
jens@17
   264
        InputData input = {ber.bytes, ber.length};
jens@17
   265
        BERHeader header;
jens@17
   266
        readHeader(&input,&header);
jens@17
   267
        return input.nextChar;
jens@17
   268
    }@catch (NSException *x) {
jens@17
   269
        exceptionToError(x,outError);
jens@17
   270
    }
jens@17
   271
    return NULL;
jens@17
   272
}
jens@17
   273
jens@16
   274
jens@16
   275
jens@16
   276
#pragma mark -
jens@16
   277
#pragma mark TEST CASES:
jens@16
   278
jens@16
   279
jens@16
   280
#define $data(BYTES...)    ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
jens@16
   281
jens@16
   282
TestCase(ParseBER) {
jens@16
   283
    CAssertEqual(MYBERParse($data(0x05, 0x00), nil),
jens@16
   284
                 [NSNull null]);
jens@16
   285
    CAssertEqual(MYBERParse($data(0x01, 0x01, 0xFF), nil),
jens@16
   286
                 $true);
jens@16
   287
    CAssertEqual(MYBERParse($data(0x01, 0x01, 0x00), nil),
jens@16
   288
                 $false);
jens@16
   289
    
jens@16
   290
    // integers:
jens@16
   291
    CAssertEqual(MYBERParse($data(0x02, 0x01, 0x00), nil),
jens@16
   292
                 $object(0));
jens@16
   293
    CAssertEqual(MYBERParse($data(0x02, 0x01, 0x48), nil),
jens@16
   294
                 $object(72));
jens@16
   295
    CAssertEqual(MYBERParse($data(0x02, 0x01, 0x80), nil),
jens@16
   296
                 $object(-128));
jens@16
   297
    CAssertEqual(MYBERParse($data(0x02, 0x02, 0x00, 0x80), nil),
jens@16
   298
                 $object(128));
jens@16
   299
    CAssertEqual(MYBERParse($data(0x02, 0x02, 0x30,0x39), nil),
jens@16
   300
                 $object(12345));
jens@16
   301
    CAssertEqual(MYBERParse($data(0x02, 0x02, 0xCF, 0xC7), nil),
jens@16
   302
                 $object(-12345));
jens@16
   303
    CAssertEqual(MYBERParse($data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15), nil),
jens@16
   304
                 $object(123456789));
jens@16
   305
    CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
jens@16
   306
                 $object(-123456789));
jens@16
   307
    CAssertEqual(MYBERParse($data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB), nil),
jens@16
   308
                 $object(-123456789));
jens@16
   309
    
jens@16
   310
    // octet strings:
jens@16
   311
    CAssertEqual(MYBERParse($data(0x04, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
jens@16
   312
                 [@"hello" dataUsingEncoding: NSASCIIStringEncoding]);
jens@16
   313
    CAssertEqual(MYBERParse($data(0x04, 0x00), nil),
jens@16
   314
                 [NSData data]);
jens@16
   315
    CAssertEqual(MYBERParse($data(0x0C, 0x05, 'h', 'e', 'l', 'l', 'o'), nil),
jens@16
   316
                 @"hello");
jens@16
   317
    
jens@16
   318
    // sequences:
jens@16
   319
    CAssertEqual(MYBERParse($data(0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF), nil),
jens@16
   320
                 $array($object(72), $true));
jens@16
   321
    CAssertEqual(MYBERParse($data(0x30, 0x10,  
jens@16
   322
                                  0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF,
jens@16
   323
                                  0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF), nil),
jens@16
   324
                 $array( $array($object(72), $true), $array($object(72), $true)));
jens@16
   325
}
jens@16
   326
jens@16
   327
jens@17
   328
#import "MYCertificate.h"
jens@17
   329
#import "MYPublicKey.h"
jens@17
   330
jens@16
   331
TestCase(ParseCert) {
jens@16
   332
    NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];
jens@16
   333
    NSError *error = nil;
jens@16
   334
    id parsed = MYBERParse(cert,&error);
jens@16
   335
    CAssert(parsed);
jens@16
   336
    CAssertNil(error);
jens@16
   337
    NSString *dump = [MYASN1Object dump: parsed];
jens@16
   338
    CAssert(dump);
jens@17
   339
    Log(@"Parsed selfsigned.cer:\n%@", dump);
jens@17
   340
    
jens@17
   341
    MYCertificate *myCert = [[MYCertificate alloc] initWithCertificateData: cert];
jens@17
   342
    CAssert(myCert);
jens@19
   343
    id parsedPubKey = MYBERParse(myCert.publicKey.keyData, NULL);
jens@17
   344
    Log(@"Parsed public key:\n%@", [MYASN1Object dump: parsedPubKey]);
jens@16
   345
jens@16
   346
    cert = [NSData dataWithContentsOfFile: @"../../Tests/iphonedev.cer"];
jens@16
   347
    parsed = MYBERParse(cert,&error);
jens@16
   348
    CAssert(parsed);
jens@16
   349
    CAssertNil(error);
jens@16
   350
    dump = [MYASN1Object dump: parsed];
jens@16
   351
    CAssert(dump);
jens@16
   352
}