MYBERParser.m
author Jens Alfke <jens@mooseyard.com>
Tue Jul 21 10:13:08 2009 -0700 (2009-07-21)
changeset 27 d0aadddb9c64
parent 20 df9da0f6b358
permissions -rw-r--r--
MYCertificate now checks validity of self-signed certs loaded from the keychain (because the Security framework doesn't validate self-signed certs.)
     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 }
   353 
   354 
   355 
   356 /*
   357  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   358  
   359  Redistribution and use in source and binary forms, with or without modification, are permitted
   360  provided that the following conditions are met:
   361  
   362  * Redistributions of source code must retain the above copyright notice, this list of conditions
   363  and the following disclaimer.
   364  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   365  and the following disclaimer in the documentation and/or other materials provided with the
   366  distribution.
   367  
   368  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   369  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   370  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   371  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   372  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   373   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   374  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   375  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   376  */