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