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