MYCertificateInfo.m
author Jens Alfke <jens@mooseyard.com>
Fri Aug 07 11:24:53 2009 -0700 (2009-08-07)
changeset 28 54b373aa65ab
parent 25 38c3c3923e1f
permissions -rw-r--r--
Fixed iPhone OS build. (issue 3)
jens@17
     1
//
jens@21
     2
//  MYCertificateInfo.m
jens@17
     3
//  MYCrypto
jens@17
     4
//
jens@17
     5
//  Created by Jens Alfke on 6/2/09.
jens@17
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
jens@17
     7
//
jens@17
     8
jens@17
     9
// References:
jens@26
    10
// <http://tools.ietf.org/html/rfc3280> "RFC 3280: Internet X.509 Certificate Profile"
jens@20
    11
// <http://www.columbia.edu/~ariel/ssleay/layman.html> "Layman's Guide To ASN.1/BER/DER"
jens@20
    12
// <http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt> "X.509 Style Guide"
jens@20
    13
// <http://en.wikipedia.org/wiki/X.509> Wikipedia article on X.509
jens@17
    14
jens@17
    15
jens@21
    16
#import "MYCertificateInfo.h"
jens@21
    17
#import "MYCrypto.h"
jens@17
    18
#import "MYASN1Object.h"
jens@17
    19
#import "MYOID.h"
jens@17
    20
#import "MYBERParser.h"
jens@17
    21
#import "MYDEREncoder.h"
jens@17
    22
#import "MYErrorUtils.h"
jens@17
    23
jens@17
    24
jens@25
    25
#define kDefaultExpirationTime (60.0 * 60.0 * 24.0 * 365.0)     /* that's 1 year */
jens@19
    26
jens@25
    27
/*  X.509 version number to generate. Even though my code doesn't (yet) add any of the post-v1
jens@25
    28
    metadata, it's necessary to write v3 or the resulting certs won't be accepted on some platforms,
jens@25
    29
    notably iPhone OS.
jens@25
    30
    "This field is used mainly for marketing purposes to claim that software is X.509v3 compliant 
jens@25
    31
    (even when it isn't)." --Peter Gutmann */
jens@25
    32
#define kCertRequestVersionNumber 3
jens@19
    33
jens@25
    34
jens@25
    35
/* "Safe" NSArray accessor -- returns nil if out of range. */
jens@17
    36
static id $atIf(NSArray *array, NSUInteger index) {
jens@17
    37
    return index < array.count ?[array objectAtIndex: index] :nil;
jens@17
    38
}
jens@17
    39
jens@17
    40
jens@20
    41
@interface MYCertificateName ()
jens@20
    42
- (id) _initWithComponents: (NSArray*)components;
jens@20
    43
@end
jens@20
    44
jens@21
    45
@interface MYCertificateInfo ()
jens@21
    46
@property (retain) NSArray *_root;
jens@21
    47
@end
jens@21
    48
jens@20
    49
jens@20
    50
#pragma mark -
jens@21
    51
@implementation MYCertificateInfo
jens@17
    52
jens@17
    53
jens@19
    54
static MYOID *kRSAAlgorithmID, *kRSAWithSHA1AlgorithmID, *kCommonNameOID,
jens@25
    55
             *kGivenNameOID, *kSurnameOID, *kDescriptionOID, *kEmailOID;
jens@17
    56
jens@17
    57
jens@17
    58
+ (void) initialize {
jens@19
    59
    if (!kEmailOID) {
jens@19
    60
        kRSAAlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 1,}
jens@19
    61
                                                      count: 7];
jens@19
    62
        kRSAWithSHA1AlgorithmID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 1, 5}
jens@19
    63
                                                              count: 7];
jens@19
    64
        kCommonNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 3}
jens@19
    65
                                                     count: 4];
jens@19
    66
        kGivenNameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 42}
jens@19
    67
                                                    count: 4];
jens@19
    68
        kSurnameOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 4}
jens@19
    69
                                                  count: 4];
jens@19
    70
        kDescriptionOID = [[MYOID alloc] initWithComponents: (UInt32[]){2, 5, 4, 13}
jens@26
    71
                                                count: 4];
jens@19
    72
        kEmailOID = [[MYOID alloc] initWithComponents: (UInt32[]){1, 2, 840, 113549, 1, 9, 1}
jens@19
    73
                                                count: 7];
jens@17
    74
    }
jens@21
    75
}
jens@21
    76
jens@21
    77
jens@21
    78
- (id) initWithRoot: (NSArray*)root
jens@21
    79
{
jens@21
    80
    self = [super init];
jens@21
    81
    if (self != nil) {
jens@21
    82
        _root = [root retain];
jens@21
    83
    }
jens@21
    84
    return self;
jens@17
    85
}
jens@17
    86
jens@17
    87
+ (NSString*) validate: (id)root {
jens@17
    88
    NSArray *top = $castIf(NSArray,root);
jens@17
    89
    if (top.count < 3)
jens@17
    90
        return @"Too few top-level components";
jens@17
    91
    NSArray *info = $castIf(NSArray, [top objectAtIndex: 0]);
jens@17
    92
    if (info.count < 7)
jens@17
    93
        return @"Too few identity components";
jens@17
    94
    MYASN1Object *version = $castIf(MYASN1Object, [info objectAtIndex: 0]);
jens@17
    95
    if (!version || version.tag != 0)
jens@17
    96
        return @"Missing or invalid version";
jens@17
    97
    NSArray *versionComps = $castIf(NSArray, version.components);
jens@17
    98
    if (!versionComps || versionComps.count != 1)
jens@17
    99
        return @"Invalid version";
jens@17
   100
    NSNumber *versionNum = $castIf(NSNumber, [versionComps objectAtIndex: 0]);
jens@17
   101
    if (!versionNum || versionNum.intValue < 0 || versionNum.intValue > 2)
jens@17
   102
        return @"Unrecognized version number";
jens@17
   103
    return nil;
jens@17
   104
}
jens@17
   105
jens@17
   106
jens@17
   107
- (id) initWithCertificateData: (NSData*)data error: (NSError**)outError;
jens@17
   108
{
jens@21
   109
    if (outError) *outError = nil;
jens@21
   110
    id root = MYBERParse(data,outError);
jens@21
   111
    NSString *errorMsg = [[self class] validate: root];
jens@21
   112
    if (errorMsg) {
jens@21
   113
        if (outError && !*outError)
jens@21
   114
            *outError = MYError(2, MYASN1ErrorDomain, @"Invalid certificate: %@", errorMsg);
jens@21
   115
        [self release];
jens@21
   116
        return nil;
jens@17
   117
    }
jens@21
   118
jens@24
   119
    self = [self initWithRoot: root];
jens@24
   120
    if (self) {
jens@24
   121
        _data = [data copy];
jens@24
   122
    }
jens@24
   123
    return self;
jens@17
   124
}
jens@17
   125
jens@17
   126
- (void) dealloc
jens@17
   127
{
jens@17
   128
    [_root release];
jens@24
   129
    [_data release];
jens@17
   130
    [super dealloc];
jens@17
   131
}
jens@17
   132
jens@21
   133
- (BOOL) isEqual: (id)object {
jens@21
   134
    return [object isKindOfClass: [MYCertificateInfo class]]
jens@21
   135
        && [_root isEqual: ((MYCertificateInfo*)object)->_root];
jens@21
   136
}
jens@17
   137
jens@19
   138
- (NSArray*) _info       {return $castIf(NSArray,$atIf(_root,0));}
jens@17
   139
jens@19
   140
- (NSArray*) _validDates {return $castIf(NSArray, [self._info objectAtIndex: 4]);}
jens@17
   141
jens@21
   142
@synthesize _root;
jens@19
   143
jens@19
   144
jens@19
   145
- (NSDate*) validFrom       {return $castIf(NSDate, $atIf(self._validDates, 0));}
jens@19
   146
- (NSDate*) validTo         {return $castIf(NSDate, $atIf(self._validDates, 1));}
jens@20
   147
jens@20
   148
- (MYCertificateName*) subject {
jens@20
   149
    return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 5]] autorelease];
jens@20
   150
}
jens@20
   151
jens@20
   152
- (MYCertificateName*) issuer {
jens@20
   153
    return [[[MYCertificateName alloc] _initWithComponents: [self._info objectAtIndex: 3]] autorelease];
jens@20
   154
}
jens@19
   155
jens@19
   156
- (BOOL) isSigned           {return [_root count] >= 3;}
jens@19
   157
jens@19
   158
- (BOOL) isRoot {
jens@19
   159
    id issuer = $atIf(self._info,3);
jens@19
   160
    return $equal(issuer, $atIf(self._info,5)) || $equal(issuer, $array());
jens@19
   161
}
jens@19
   162
jens@19
   163
jens@26
   164
- (NSData*) subjectPublicKeyData {
jens@19
   165
    NSArray *keyInfo = $cast(NSArray, $atIf(self._info, 6));
jens@17
   166
    MYOID *keyAlgorithmID = $castIf(MYOID, $atIf($castIf(NSArray,$atIf(keyInfo,0)), 0));
jens@17
   167
    if (!$equal(keyAlgorithmID, kRSAAlgorithmID))
jens@17
   168
        return nil;
jens@26
   169
    return $cast(MYBitString, $atIf(keyInfo, 1)).bits;
jens@26
   170
}
jens@26
   171
jens@26
   172
- (MYPublicKey*) subjectPublicKey {
jens@26
   173
    NSData *keyData = self.subjectPublicKeyData;
jens@17
   174
    if (!keyData) return nil;
jens@26
   175
    return [[[MYPublicKey alloc] initWithKeyData: keyData] autorelease];
jens@17
   176
}
jens@17
   177
jens@24
   178
- (NSData*) signedData {
jens@24
   179
    if (!_data)
jens@24
   180
        return nil;
jens@24
   181
    // The root object is a sequence; we want to extract the 1st object of that sequence.
jens@24
   182
    const UInt8 *certStart = _data.bytes;
jens@24
   183
    const UInt8 *start = MYBERGetContents(_data, nil);
jens@24
   184
    if (!start) return nil;
jens@24
   185
    size_t length = MYBERGetLength([NSData dataWithBytesNoCopy: (void*)start
jens@24
   186
                                                        length: _data.length - (start-certStart)
jens@24
   187
                                                  freeWhenDone: NO],
jens@24
   188
                                   NULL);
jens@24
   189
    if (length==0)
jens@24
   190
        return nil;
jens@24
   191
    return [NSData dataWithBytes: start length: (start + length - certStart)];
jens@24
   192
}
jens@24
   193
jens@24
   194
- (MYOID*) signatureAlgorithmID {
jens@24
   195
    return $castIf(MYOID, $atIf($castIf(NSArray,$atIf(_root,1)), 0));
jens@24
   196
}
jens@24
   197
jens@24
   198
- (NSData*) signature {
jens@24
   199
    id signature = $atIf(_root,2);
jens@24
   200
    if ([signature isKindOfClass: [MYBitString class]])
jens@24
   201
        signature = [signature bits];
jens@24
   202
    return $castIf(NSData,signature);
jens@24
   203
}
jens@24
   204
jens@24
   205
- (BOOL) verifySignatureWithKey: (MYPublicKey*)issuerPublicKey {
jens@24
   206
    if (!$equal(self.signatureAlgorithmID, kRSAWithSHA1AlgorithmID))
jens@24
   207
        return NO;
jens@24
   208
    NSData *signedData = self.signedData;
jens@24
   209
    NSData *signature = self.signature;
jens@24
   210
    return signedData && signature && [issuerPublicKey verifySignature: signature ofData: signedData];
jens@24
   211
}
jens@24
   212
jens@24
   213
jens@21
   214
@end
jens@17
   215
jens@17
   216
jens@17
   217
jens@17
   218
jens@19
   219
#pragma mark -
jens@21
   220
@implementation MYCertificateRequest
jens@19
   221
jens@21
   222
- (id) initWithPublicKey: (MYPublicKey*)publicKey {
jens@21
   223
    Assert(publicKey);
jens@21
   224
    id empty = [NSNull null];
jens@25
   225
    id version = [[MYASN1Object alloc] initWithTag: 0 
jens@25
   226
                                           ofClass: 2
jens@25
   227
                                        components: $array($object(kCertRequestVersionNumber - 1))];
jens@21
   228
    NSArray *root = $array( $marray(version,
jens@21
   229
                                    empty,       // serial #
jens@21
   230
                                    $array(kRSAAlgorithmID),
jens@21
   231
                                    $marray(),
jens@21
   232
                                    $marray(empty, empty),
jens@21
   233
                                    $marray(),
jens@21
   234
                                    $array( $array(kRSAAlgorithmID, empty),
jens@21
   235
                                           [MYBitString bitStringWithData: publicKey.keyData] ) ) );
jens@21
   236
    self = [super initWithRoot: root];
jens@21
   237
    [version release];
jens@23
   238
    if (self) {
jens@23
   239
        _publicKey = publicKey.retain;
jens@23
   240
    }
jens@19
   241
    return self;
jens@19
   242
}
jens@23
   243
    
jens@23
   244
- (void) dealloc
jens@23
   245
{
jens@23
   246
    [_publicKey release];
jens@23
   247
    [super dealloc];
jens@23
   248
}
jens@23
   249
jens@19
   250
jens@21
   251
- (NSDate*) validFrom       {return [super validFrom];}
jens@21
   252
- (NSDate*) validTo         {return [super validTo];}
jens@19
   253
jens@19
   254
- (void) setValidFrom: (NSDate*)validFrom {
jens@19
   255
    [(NSMutableArray*)self._validDates replaceObjectAtIndex: 0 withObject: validFrom];
jens@19
   256
}
jens@19
   257
jens@19
   258
- (void) setValidTo: (NSDate*)validTo {
jens@19
   259
    [(NSMutableArray*)self._validDates replaceObjectAtIndex: 1 withObject: validTo];
jens@19
   260
}
jens@19
   261
jens@19
   262
jens@21
   263
- (void) fillInValues {
jens@19
   264
    NSMutableArray *info = (NSMutableArray*)self._info;
jens@19
   265
    // Set serial number if there isn't one yet:
jens@19
   266
    if (!$castIf(NSNumber, [info objectAtIndex: 1])) {
jens@19
   267
        UInt64 serial = floor(CFAbsoluteTimeGetCurrent() * 1000);
jens@19
   268
        [info replaceObjectAtIndex: 1 withObject: $object(serial)];
jens@19
   269
    }
jens@19
   270
    
jens@19
   271
    // Set up valid date range if there isn't one yet:
jens@19
   272
    NSDate *validFrom = self.validFrom;
jens@19
   273
    if (!validFrom)
jens@19
   274
        validFrom = self.validFrom = [NSDate date];
jens@19
   275
    NSDate *validTo = self.validTo;
jens@19
   276
    if (!validTo)
jens@19
   277
        self.validTo = [validFrom addTimeInterval: kDefaultExpirationTime]; 
jens@21
   278
}
jens@21
   279
jens@21
   280
jens@21
   281
- (NSData*) requestData: (NSError**)outError {
jens@21
   282
    [self fillInValues];
jens@21
   283
    return [MYDEREncoder encodeRootObject: self._info error: outError];
jens@21
   284
}
jens@21
   285
jens@21
   286
jens@21
   287
- (NSData*) selfSignWithPrivateKey: (MYPrivateKey*)privateKey 
jens@21
   288
                             error: (NSError**)outError 
jens@21
   289
{
jens@21
   290
    AssertEqual(privateKey.publicKey, _publicKey);  // Keys must form a pair
jens@19
   291
    
jens@21
   292
    // Copy subject to issuer:
jens@21
   293
    NSMutableArray *info = (NSMutableArray*)self._info;
jens@21
   294
    [info replaceObjectAtIndex: 3 withObject: [info objectAtIndex: 5]];
jens@21
   295
    
jens@21
   296
    // Sign the request:
jens@21
   297
    NSData *dataToSign = [self requestData: outError];
jens@19
   298
    if (!dataToSign)
jens@21
   299
        return nil;
jens@21
   300
    MYBitString *signature = [MYBitString bitStringWithData: [privateKey signData: dataToSign]];
jens@19
   301
    
jens@21
   302
    // Generate and encode the certificate:
jens@21
   303
    NSArray *root = $array(info, 
jens@21
   304
                           $array(kRSAWithSHA1AlgorithmID, [NSNull null]),
jens@21
   305
                           signature);
jens@21
   306
    return [MYDEREncoder encodeRootObject: root error: outError];
jens@21
   307
}
jens@21
   308
jens@21
   309
jens@21
   310
- (MYIdentity*) createSelfSignedIdentityWithPrivateKey: (MYPrivateKey*)privateKey
jens@21
   311
                                                 error: (NSError**)outError
jens@21
   312
{
jens@23
   313
    Assert(privateKey.keychain!=nil);
jens@21
   314
    NSData *certData = [self selfSignWithPrivateKey: privateKey error: outError];
jens@21
   315
    if (!certData)
jens@21
   316
        return nil;
jens@21
   317
    MYCertificate *cert = [privateKey.keychain importCertificate: certData];
jens@21
   318
    Assert(cert!=nil);
jens@23
   319
    Assert(cert.keychain!=nil);
jens@23
   320
    AssertEqual(cert.publicKey.keyData, _publicKey.keyData);
jens@21
   321
    MYIdentity *identity = cert.identity;
jens@21
   322
    Assert(identity!=nil);
jens@21
   323
    return identity;
jens@19
   324
}
jens@19
   325
jens@19
   326
jens@17
   327
@end
jens@17
   328
jens@17
   329
jens@17
   330
jens@20
   331
#pragma mark -
jens@20
   332
@implementation MYCertificateName
jens@20
   333
jens@20
   334
- (id) _initWithComponents: (NSArray*)components
jens@20
   335
{
jens@20
   336
    self = [super init];
jens@20
   337
    if (self != nil) {
jens@20
   338
        _components = [components retain];
jens@20
   339
    }
jens@20
   340
    return self;
jens@20
   341
}
jens@20
   342
jens@20
   343
- (void) dealloc
jens@20
   344
{
jens@20
   345
    [_components release];
jens@20
   346
    [super dealloc];
jens@20
   347
}
jens@20
   348
jens@20
   349
- (BOOL) isEqual: (id)object {
jens@20
   350
    return [object isKindOfClass: [MYCertificateName class]]
jens@20
   351
        && [_components isEqual: ((MYCertificateName*)object)->_components];
jens@20
   352
}
jens@20
   353
jens@20
   354
- (NSArray*) _pairForOID: (MYOID*)oid {
jens@20
   355
    for (id nameEntry in _components) {
jens@20
   356
        for (id pair in $castIf(NSSet,nameEntry)) {
jens@20
   357
            if ([pair isKindOfClass: [NSArray class]] && [pair count] == 2) {
jens@20
   358
                if ($equal(oid, [pair objectAtIndex: 0]))
jens@20
   359
                    return pair;
jens@20
   360
            }
jens@20
   361
        }
jens@20
   362
    }
jens@20
   363
    return nil;
jens@20
   364
}
jens@20
   365
jens@20
   366
- (NSString*) stringForOID: (MYOID*)oid {
jens@20
   367
    return [[self _pairForOID: oid] objectAtIndex: 1];
jens@20
   368
}
jens@20
   369
jens@20
   370
- (void) setString: (NSString*)value forOID: (MYOID*)oid {
jens@20
   371
    NSMutableArray *pair = (NSMutableArray*) [self _pairForOID: oid];
jens@26
   372
    if (pair) {
jens@26
   373
        if (value)
jens@26
   374
            [pair replaceObjectAtIndex: 1 withObject: value];
jens@26
   375
        else
jens@26
   376
            Assert(NO,@"-setString:forOID: removing strings is unimplemented");//FIX
jens@26
   377
    } else {
jens@26
   378
        if (value)
jens@26
   379
            [(NSMutableArray*)_components addObject: [NSSet setWithObject: $marray(oid,value)]];
jens@26
   380
    }
jens@20
   381
}
jens@20
   382
jens@20
   383
- (NSString*) commonName    {return [self stringForOID: kCommonNameOID];}
jens@20
   384
- (NSString*) givenName     {return [self stringForOID: kGivenNameOID];}
jens@20
   385
- (NSString*) surname       {return [self stringForOID: kSurnameOID];}
jens@20
   386
- (NSString*) nameDescription {return [self stringForOID: kDescriptionOID];}
jens@20
   387
- (NSString*) emailAddress  {return [self stringForOID: kEmailOID];}
jens@20
   388
jens@20
   389
- (void) setCommonName: (NSString*)commonName   {[self setString: commonName forOID: kCommonNameOID];}
jens@20
   390
- (void) setGivenName: (NSString*)givenName     {[self setString: givenName forOID: kGivenNameOID];}
jens@20
   391
- (void) setSurname: (NSString*)surname         {[self setString: surname forOID: kSurnameOID];}
jens@20
   392
- (void) setNameDescription: (NSString*)desc    {[self setString: desc forOID: kDescriptionOID];}
jens@20
   393
- (void) setEmailAddress: (NSString*)email      {[self setString: email forOID: kEmailOID];}
jens@20
   394
jens@20
   395
jens@20
   396
@end
jens@20
   397
jens@20
   398
jens@21
   399
/*
jens@21
   400
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@17
   401
 
jens@21
   402
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@21
   403
 provided that the following conditions are met:
jens@21
   404
 
jens@21
   405
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@21
   406
 and the following disclaimer.
jens@21
   407
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@21
   408
 and the following disclaimer in the documentation and/or other materials provided with the
jens@21
   409
 distribution.
jens@21
   410
 
jens@21
   411
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@21
   412
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@21
   413
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@21
   414
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@21
   415
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@21
   416
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@21
   417
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@21
   418
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@21
   419
 */