MYDEREncoder.m
changeset 16 c409dbc4f068
child 17 90a70925562b
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/MYDEREncoder.m	Tue Jun 02 13:16:28 2009 -0700
     1.3 @@ -0,0 +1,352 @@
     1.4 +//
     1.5 +//  MYDEREncoder.m
     1.6 +//  MYCrypto
     1.7 +//
     1.8 +//  Created by Jens Alfke on 5/29/09.
     1.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    1.10 +//
    1.11 +
    1.12 +#import "MYDEREncoder.h"
    1.13 +#import "MYASN1Object.h"
    1.14 +#import "MYBERParser.h"
    1.15 +#import "MYOID.h"
    1.16 +#import "MYErrorUtils.h"
    1.17 +
    1.18 +
    1.19 +#define MYDEREncoderException @"MYDEREncoderException"
    1.20 +
    1.21 +
    1.22 +@interface MYDEREncoder ()
    1.23 +- (void) _encode: (id)object;
    1.24 +@property (retain) NSError *error;
    1.25 +@end
    1.26 +
    1.27 +
    1.28 +@implementation MYDEREncoder
    1.29 +
    1.30 +
    1.31 +- (id) initWithRootObject: (id)rootObject
    1.32 +{
    1.33 +    self = [super init];
    1.34 +    if (self != nil) {
    1.35 +        _rootObject = [rootObject retain];
    1.36 +    }
    1.37 +    return self;
    1.38 +}
    1.39 +
    1.40 ++ (NSData*) encodeRootObject: (id)rootObject error: (NSError**)outError {
    1.41 +    MYDEREncoder *encoder = [[self alloc] initWithRootObject: rootObject];
    1.42 +    NSData *output = [encoder.output copy];
    1.43 +    if (outError) *outError = [[encoder.error retain] autorelease];
    1.44 +    [encoder release];
    1.45 +    return [output autorelease];
    1.46 +}
    1.47 +
    1.48 +- (void) dealloc
    1.49 +{
    1.50 +    [_rootObject release];
    1.51 +    [_output release];
    1.52 +    [super dealloc];
    1.53 +}
    1.54 +
    1.55 +
    1.56 +
    1.57 +static unsigned sizeOfUnsignedInt (UInt64 n) {
    1.58 +    unsigned bytes = 0;
    1.59 +    for (; n; n >>= 8)
    1.60 +        bytes++;
    1.61 +    return bytes;
    1.62 +}
    1.63 +
    1.64 +static unsigned encodeUnsignedInt (UInt64 n, UInt8 buf[], BOOL padHighBit) {
    1.65 +    unsigned size = MAX(1U, sizeOfUnsignedInt(n));
    1.66 +    UInt64 bigEndian = NSSwapHostLongLongToBig(n);
    1.67 +    const UInt8* src = (UInt8*)&bigEndian + (8-size);
    1.68 +    UInt8 *dst = &buf[0];
    1.69 +    if (padHighBit && (*src & 0x80)) {
    1.70 +        *dst++ = 0;
    1.71 +        size++;
    1.72 +    }
    1.73 +    memcpy(dst, src, size);
    1.74 +    return size;
    1.75 +}
    1.76 +
    1.77 +static unsigned encodeSignedInt (SInt64 n, UInt8 buf[]) {
    1.78 +    if (n >= 0)
    1.79 +        return encodeUnsignedInt(n, buf, YES);
    1.80 +    else {
    1.81 +        unsigned size = MAX(1U, sizeOfUnsignedInt(~n));
    1.82 +        UInt64 bigEndian = NSSwapHostLongLongToBig(n);
    1.83 +        const UInt8* src = (UInt8*)&bigEndian + (8-size);
    1.84 +        UInt8 *dst = &buf[0];
    1.85 +        if (!(*src & 0x80)) {
    1.86 +            *dst++ = 0xFF;
    1.87 +            size++;
    1.88 +        }
    1.89 +        memcpy(dst, src, size);
    1.90 +        return size;
    1.91 +    }
    1.92 +}
    1.93 +
    1.94 +
    1.95 +- (void) _writeTag: (unsigned)tag
    1.96 +             class: (unsigned)tagClass
    1.97 +       constructed: (BOOL) constructed
    1.98 +            length: (size_t)length 
    1.99 +{
   1.100 +    struct {
   1.101 +        unsigned tag            :5;
   1.102 +        unsigned isConstructed  :1;
   1.103 +        unsigned tagClass       :2;
   1.104 +        unsigned length         :7;
   1.105 +        unsigned isLengthLong   :1;
   1.106 +        UInt8    extraLength[9];
   1.107 +    } header;
   1.108 +    size_t headerSize = 2;
   1.109 +    
   1.110 +    header.tag = tag;
   1.111 +    header.isConstructed = constructed;
   1.112 +    header.tagClass = tagClass;
   1.113 +    if (length < 128) {
   1.114 +        header.isLengthLong = NO;
   1.115 +        header.length = length;
   1.116 +    } else {
   1.117 +        header.isLengthLong = YES;
   1.118 +        header.length = encodeUnsignedInt(length, header.extraLength, NO);
   1.119 +        headerSize += header.length;
   1.120 +    }
   1.121 +    [_output appendBytes: &header length: headerSize];
   1.122 +}
   1.123 +
   1.124 +- (void) _writeTag: (unsigned)tag
   1.125 +             class: (unsigned)tagClass
   1.126 +       constructed: (BOOL) constructed
   1.127 +             bytes: (const void*)bytes 
   1.128 +            length: (size_t)length 
   1.129 +{
   1.130 +    [self _writeTag: tag class: tagClass constructed: constructed length: length];
   1.131 +    [_output appendBytes: bytes length: length];
   1.132 +}
   1.133 +
   1.134 +- (void) _writeTag: (unsigned)tag
   1.135 +             class: (unsigned)tagClass
   1.136 +       constructed: (BOOL) constructed
   1.137 +              data: (NSData*)data 
   1.138 +{
   1.139 +    Assert(data);
   1.140 +    [self _writeTag: tag class: tagClass constructed: constructed bytes: data.bytes length: data.length];
   1.141 +}
   1.142 +
   1.143 +
   1.144 +- (void) _encodeNumber: (NSNumber*)number {
   1.145 +    // Special-case detection of booleans by pointer equality, because otherwise they appear
   1.146 +    // identical to 0 and 1:
   1.147 +    if (number==$true || number==$false) {
   1.148 +        UInt8 value = number==$true ?0xFF :0x00;
   1.149 +        [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
   1.150 +        return;
   1.151 +    }
   1.152 +    
   1.153 +    const char *type = number.objCType;
   1.154 +    if (strlen(type) == 1) {
   1.155 +        switch(type[0]) {
   1.156 +            case 'c':
   1.157 +            case 'i':
   1.158 +            case 's':
   1.159 +            case 'l':
   1.160 +            case 'q':
   1.161 +            {   // Signed integers:
   1.162 +                UInt8 buf[9];
   1.163 +                size_t size = encodeSignedInt(number.longLongValue, buf);
   1.164 +                [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   1.165 +                return;
   1.166 +            }
   1.167 +            case 'C':
   1.168 +            case 'I':
   1.169 +            case 'S':
   1.170 +            case 'L':
   1.171 +            case 'Q':
   1.172 +            {   // Unsigned integers:
   1.173 +                UInt8 buf[9];
   1.174 +                size_t size = encodeUnsignedInt(number.unsignedLongLongValue, buf, YES);
   1.175 +                [self _writeTag: 2 class: 0 constructed: NO bytes: buf length: size];
   1.176 +                return;
   1.177 +            }
   1.178 +            case 'B':
   1.179 +            {   // bool
   1.180 +                UInt8 value = number.boolValue ?0xFF :0x00;
   1.181 +                [self _writeTag: 1 class: 0 constructed: NO bytes: &value length: 1];
   1.182 +                return;
   1.183 +            }
   1.184 +        }
   1.185 +    }
   1.186 +    [NSException raise: MYDEREncoderException format: @"Can't DER-encode value %@ (typecode=%s)", number,type];
   1.187 +}
   1.188 +
   1.189 +
   1.190 +- (void) _encodeString: (NSString*)string {
   1.191 +    NSData *data = [string dataUsingEncoding: NSASCIIStringEncoding];
   1.192 +    if (data)
   1.193 +        [self _writeTag: 19 class: 0 constructed: NO data: data];
   1.194 +    else
   1.195 +        [self _writeTag: 12 class: 0 constructed: NO data: [string dataUsingEncoding: NSUTF8StringEncoding]];
   1.196 +}
   1.197 +
   1.198 +
   1.199 +- (void) _encodeBitString: (MYBitString*)bitString {
   1.200 +    NSUInteger bitCount = bitString.bitCount;
   1.201 +    [self _writeTag: 3 class: 0 constructed: NO length: 1 + (bitCount/8)];
   1.202 +    UInt8 unused = (8 - (bitCount % 8)) % 8;
   1.203 +    [_output appendBytes: &unused length: 1];
   1.204 +    [_output appendBytes: bitString.bits.bytes length: bitCount/8];
   1.205 +}
   1.206 +
   1.207 +- (void) _encodeDate: (NSDate*)date {
   1.208 +    NSString *dateStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
   1.209 +    Log(@"Encoded %@ as '%@'",date,dateStr);//TEMP
   1.210 +    [self _writeTag: 24 class: 0 constructed: NO data: [dateStr dataUsingEncoding: NSASCIIStringEncoding]];
   1.211 +}
   1.212 +
   1.213 +
   1.214 +- (void) _encodeCollection: (id)collection tag: (unsigned)tag class: (unsigned)tagClass {
   1.215 +    MYDEREncoder *subEncoder = [[[self class] alloc] init];
   1.216 +    for (id object in collection)
   1.217 +        [subEncoder _encode: object];
   1.218 +    [self _writeTag: tag class: tagClass constructed: YES data: subEncoder.output];
   1.219 +    [subEncoder release];
   1.220 +}
   1.221 +
   1.222 +
   1.223 +- (void) _encode: (id)object {
   1.224 +    if (!_output)
   1.225 +        _output = [[NSMutableData alloc] initWithCapacity: 1024];
   1.226 +    if ([object isKindOfClass: [NSNumber class]]) {
   1.227 +        [self _encodeNumber: object];
   1.228 +    } else if ([object isKindOfClass: [NSData class]]) {
   1.229 +        [self _writeTag: 4 class: 0 constructed: NO data: object];
   1.230 +    } else if ([object isKindOfClass: [MYBitString class]]) {
   1.231 +        [self _encodeBitString: object];
   1.232 +    } else if ([object isKindOfClass: [NSString class]]) {
   1.233 +        [self _encodeString: object];
   1.234 +    } else if ([object isKindOfClass: [NSDate class]]) {
   1.235 +        [self _encodeDate: object];
   1.236 +    } else if ([object isKindOfClass: [NSNull class]]) {
   1.237 +        [self _writeTag: 5 class: 0 constructed: NO bytes: NULL length: 0];
   1.238 +    } else if ([object isKindOfClass: [NSArray class]]) {
   1.239 +        [self _encodeCollection: object tag: 16 class: 0];
   1.240 +    } else if ([object isKindOfClass: [NSSet class]]) {
   1.241 +        [self _encodeCollection: object tag: 17 class: 0];
   1.242 +    } else if ([object isKindOfClass: [MYOID class]]) {
   1.243 +        [self _writeTag: 6 class: 0 constructed: NO data: [object DEREncoding]];
   1.244 +    } else if ([object isKindOfClass: [MYASN1Object class]]) {
   1.245 +        MYASN1Object *asn = object;
   1.246 +        if (asn.components)
   1.247 +            [self _encodeCollection: asn.components tag: asn.tag class: asn.tagClass];
   1.248 +        else
   1.249 +            [self _writeTag: asn.tag 
   1.250 +                      class: asn.tagClass
   1.251 +                constructed: asn.constructed
   1.252 +                       data: asn.value];
   1.253 +    } else {
   1.254 +        [NSException raise: MYDEREncoderException format: @"Can't DER-encode a %@", [object class]];
   1.255 +    }
   1.256 +}
   1.257 +
   1.258 +
   1.259 +- (NSData*) output {
   1.260 +    if (!_output && !_error) {
   1.261 +        @try{
   1.262 +            [self _encode: _rootObject];
   1.263 +        }@catch (NSException *x) {
   1.264 +            if ($equal(x.name, MYDEREncoderException)) {
   1.265 +                self.error = MYError(2,MYASN1ErrorDomain, @"%@", x.reason);
   1.266 +                return nil;
   1.267 +            } else
   1.268 +                @throw(x);
   1.269 +        }
   1.270 +    }
   1.271 +    return _output;
   1.272 +}
   1.273 +
   1.274 +@synthesize error=_error;
   1.275 +
   1.276 +
   1.277 +@end
   1.278 +
   1.279 +
   1.280 +
   1.281 +#define $data(BYTES...)    ({const uint8_t bytes[] = {BYTES}; [NSData dataWithBytes: bytes length: sizeof(bytes)];})
   1.282 +
   1.283 +TestCase(DEREncoder) {
   1.284 +    CAssertEqual([MYDEREncoder encodeRootObject: [NSNull null] error: nil],
   1.285 +                 $data(0x05, 0x00));
   1.286 +    CAssertEqual([MYDEREncoder encodeRootObject: $true error: nil],
   1.287 +                 $data(0x01, 0x01, 0xFF));
   1.288 +    CAssertEqual([MYDEREncoder encodeRootObject: $false error: nil],
   1.289 +                 $data(0x01, 0x01, 0x00));
   1.290 +
   1.291 +    // Integers:
   1.292 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(0) error: nil],
   1.293 +                 $data(0x02, 0x01, 0x00));
   1.294 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(1) error: nil],
   1.295 +                 $data(0x02, 0x01, 0x01));
   1.296 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(-1) error: nil],
   1.297 +                 $data(0x02, 0x01, 0xFF));
   1.298 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(72) error: nil],
   1.299 +                  $data(0x02, 0x01, 0x48));
   1.300 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(-128) error: nil],
   1.301 +                 $data(0x02, 0x01, 0x80));
   1.302 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(128) error: nil],
   1.303 +                 $data(0x02, 0x02, 0x00, 0x80));
   1.304 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(255) error: nil],
   1.305 +                 $data(0x02, 0x02, 0x00, 0xFF));
   1.306 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(-256) error: nil],
   1.307 +                 $data(0x02, 0x02, 0xFF, 0x00));
   1.308 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(12345) error: nil],
   1.309 +                 $data(0x02, 0x02, 0x30,0x39));
   1.310 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(-12345) error: nil],
   1.311 +                 $data(0x02, 0x02, 0xCF, 0xC7));
   1.312 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(123456789) error: nil],
   1.313 +                 $data(0x02, 0x04, 0x07, 0x5B, 0xCD, 0x15));
   1.314 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
   1.315 +                 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
   1.316 +    CAssertEqual([MYDEREncoder encodeRootObject: $object(-123456789) error: nil],
   1.317 +                 $data(0x02, 0x04, 0xF8, 0xA4, 0x32, 0xEB));
   1.318 +
   1.319 +    // Strings:
   1.320 +    CAssertEqual([MYDEREncoder encodeRootObject: @"hello" error: nil],
   1.321 +                 $data(0x13, 0x05, 'h', 'e', 'l', 'l', 'o'));
   1.322 +    CAssertEqual([MYDEREncoder encodeRootObject: @"thérè" error: nil],
   1.323 +                 $data(0x0C, 0x07, 't', 'h', 0xC3, 0xA9, 'r', 0xC3, 0xA8));
   1.324 +    
   1.325 +    // Dates:
   1.326 +    CAssertEqual([MYDEREncoder encodeRootObject: [NSDate dateWithTimeIntervalSinceReferenceDate: 265336576]
   1.327 +                                          error: nil],
   1.328 +                 $data(0x18, 0x0F, '2', '0', '0', '9', '0', '5', '3', '0', '0', '0', '3', '6', '1', '6', 'Z'));
   1.329 +
   1.330 +    // Sequences:
   1.331 +    CAssertEqual([MYDEREncoder encodeRootObject: $array($object(72), $true) error: nil],
   1.332 +                 $data(0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
   1.333 +    CAssertEqual([MYDEREncoder encodeRootObject: $array( $array($object(72), $true), 
   1.334 +                                                         $array($object(72), $true))
   1.335 +                                          error: nil],
   1.336 +                 $data(0x30, 0x10,  
   1.337 +                       0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF,
   1.338 +                       0x30, 0x06,  0x02, 0x01, 0x48,  0x01, 0x01, 0xFF));
   1.339 +}
   1.340 +
   1.341 +
   1.342 +TestCase(EncodeCert) {
   1.343 +    NSError *error = nil;
   1.344 +    NSData *cert = [NSData dataWithContentsOfFile: @"../../Tests/selfsigned.cer"];  //TEMP
   1.345 +    id certObjects = MYBERParse(cert,&error);
   1.346 +    CAssertNil(error);
   1.347 +    Log(@"Decoded as:\n%@", [MYASN1Object dump: certObjects]);
   1.348 +    NSData *encoded = [MYDEREncoder encodeRootObject: certObjects error: &error];
   1.349 +    CAssertNil(error);
   1.350 +    id reDecoded = MYBERParse(encoded, &error);
   1.351 +    CAssertNil(error);
   1.352 +    Log(@"Re-decoded as:\n%@", [MYASN1Object dump: reDecoded]);
   1.353 +    [encoded writeToFile: @"../../Tests/selfsigned_reencoded.cer" atomically: YES];
   1.354 +    CAssertEqual(encoded,cert);
   1.355 +}