MYCertificate.m
author Jens Alfke <jens@mooseyard.com>
Wed Jun 10 09:02:18 2009 -0700 (2009-06-10)
changeset 25 38c3c3923e1f
parent 21 2c300b15b381
child 26 d9c2a06d4e4e
permissions -rw-r--r--
Changed the X.509 version number in generated certs from 1 to 3, so that SecCertificateCreateFromData on iPhone will accept them. :-/
snej@0
     1
//
snej@0
     2
//  MYCertificate.m
snej@0
     3
//  MYCrypto
snej@0
     4
//
snej@0
     5
//  Created by Jens Alfke on 3/26/09.
snej@0
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
snej@0
     7
//
snej@0
     8
snej@0
     9
#import "MYCertificate.h"
snej@0
    10
#import "MYCrypto_Private.h"
jens@16
    11
#import "MYIdentity.h"
snej@8
    12
#import "MYDigest.h"
jens@21
    13
#import "MYCertificateInfo.h"
snej@8
    14
#import "MYErrorUtils.h"
snej@0
    15
snej@0
    16
snej@0
    17
@implementation MYCertificate
snej@0
    18
snej@0
    19
snej@0
    20
/** Creates a MYCertificate object for an existing Keychain certificate reference. */
snej@0
    21
- (id) initWithCertificateRef: (SecCertificateRef)certificateRef {
snej@0
    22
    self = [super initWithKeychainItemRef: (SecKeychainItemRef)certificateRef];
snej@0
    23
    if (self) {
snej@0
    24
        _certificateRef = certificateRef;     // superclass has already CFRetained it
snej@0
    25
    }
snej@0
    26
    return self;
snej@0
    27
}
snej@0
    28
snej@8
    29
+ (MYCertificate*) certificateWithCertificateRef: (SecCertificateRef)certificateRef {
snej@8
    30
    return [[[self alloc] initWithCertificateRef: certificateRef] autorelease];
snej@8
    31
}
snej@8
    32
snej@0
    33
/** Creates a MYCertificate object from exported key data, but does not add it to any keychain. */
snej@0
    34
- (id) initWithCertificateData: (NSData*)data
jens@24
    35
#if !MYCRYPTO_USE_IPHONE_API
snej@0
    36
                          type: (CSSM_CERT_TYPE) type
snej@0
    37
                      encoding: (CSSM_CERT_ENCODING) encoding
jens@24
    38
#endif
snej@0
    39
{
snej@0
    40
    Assert(data);
jens@24
    41
    SecCertificateRef certificateRef = NULL;
jens@24
    42
#if MYCRYPTO_USE_IPHONE_API
jens@24
    43
    certificateRef = SecCertificateCreateWithData(NULL, (CFDataRef)data);
jens@24
    44
#else
snej@0
    45
    CSSM_DATA cssmData = {.Data=(void*)data.bytes, .Length=data.length};
snej@0
    46
    if (!check(SecCertificateCreateFromData(&cssmData, type, encoding, &certificateRef),
jens@24
    47
               @"SecCertificateCreateFromData"))
jens@24
    48
        certificateRef = NULL;
jens@24
    49
#endif
jens@24
    50
    if (!certificateRef) {
snej@0
    51
        [self release];
snej@0
    52
        return nil;
snej@0
    53
    }
snej@0
    54
    self = [self initWithCertificateRef: certificateRef];
snej@0
    55
    CFRelease(certificateRef);
jens@24
    56
    
jens@24
    57
    // If the cert is self-signed, verify its signature. Apple's frameworks don't do this,
jens@24
    58
    // even the SecTrust API; if the signature doesn't verify, they just assume it could be
jens@24
    59
    // signed by a different cert. Seems like a bad decision to me, so I'll add the check:
jens@24
    60
    MYCertificateInfo *info = self.info;
jens@24
    61
    if (info.isRoot) {
jens@24
    62
        Log(@"Verifying self-signed certificate %@ ...", self);
jens@24
    63
        if (![info verifySignatureWithKey: self.publicKey]) {
jens@24
    64
            Log(@"Self-signed cert failed signature verification (%@)", self);
jens@24
    65
            [self release];
jens@24
    66
            return nil;
jens@24
    67
        }
jens@24
    68
    }
jens@24
    69
    
snej@0
    70
    return self;
snej@0
    71
}
snej@0
    72
jens@24
    73
#if !MYCRYPTO_USE_IPHONE_API
snej@0
    74
- (id) initWithCertificateData: (NSData*)data {
snej@0
    75
    return [self initWithCertificateData: data 
snej@0
    76
                                    type: CSSM_CERT_X_509v3 
snej@0
    77
                                encoding: CSSM_CERT_ENCODING_BER];
snej@0
    78
}
jens@24
    79
#endif
jens@21
    80
- (void) dealloc
jens@21
    81
{
jens@21
    82
    [_info release];
jens@21
    83
    [super dealloc];
jens@21
    84
}
jens@21
    85
jens@21
    86
snej@8
    87
snej@8
    88
- (NSString*) description {
snej@8
    89
    return $sprintf(@"%@[%@ %@/%p]", 
snej@8
    90
                    [self class],
snej@8
    91
                    self.commonName,
snej@8
    92
                    self.certificateData.my_SHA1Digest.abbreviatedHexString,
snej@8
    93
                    _certificateRef);
snej@8
    94
}
snej@8
    95
snej@8
    96
snej@8
    97
- (BOOL)isEqualToCertificate:(MYCertificate*)cert {
snej@8
    98
    return [self isEqual: cert] || [self.certificateData isEqual: cert.certificateData];
snej@8
    99
}
snej@8
   100
snej@8
   101
jens@24
   102
#if !TARGET_OS_IPHONE
snej@0
   103
+ (MYCertificate*) preferredCertificateForName: (NSString*)name {
snej@0
   104
    SecCertificateRef certRef = NULL;
snej@0
   105
    if (!check(SecCertificateCopyPreference((CFStringRef)name, 0, &certRef),
snej@0
   106
               @"SecCertificateCopyPreference"))
snej@0
   107
        return nil;
snej@0
   108
    return [[[MYCertificate alloc] initWithCertificateRef: certRef] autorelease];
snej@0
   109
}
snej@0
   110
snej@0
   111
- (BOOL) setPreferredCertificateForName: (NSString*)name {
snej@0
   112
    return check(SecCertificateSetPreference(_certificateRef, (CFStringRef)name, 0, NULL),
snej@0
   113
                 @"SecCertificateSetPreference");
snej@0
   114
}
jens@24
   115
#endif TARGET_OS_IPHONE
snej@0
   116
snej@8
   117
snej@0
   118
@synthesize certificateRef=_certificateRef;
snej@0
   119
snej@0
   120
- (NSData*) certificateData {
jens@24
   121
#if MYCRYPTO_USE_IPHONE_API
jens@24
   122
    CFDataRef data = SecCertificateCopyData(_certificateRef);
jens@24
   123
    return data ?[(id)CFMakeCollectable(data) autorelease] :nil;
jens@24
   124
#else
snej@0
   125
    CSSM_DATA cssmData;
snej@0
   126
    if (!check(SecCertificateGetData(_certificateRef, &cssmData),
snej@0
   127
               @"SecCertificateGetData"))
snej@0
   128
        return nil;
snej@0
   129
    return [NSData dataWithBytes: cssmData.Data length: cssmData.Length];
jens@24
   130
#endif
snej@0
   131
}
snej@0
   132
snej@0
   133
- (MYPublicKey*) publicKey {
snej@0
   134
    SecKeyRef keyRef = NULL;
jens@24
   135
#if MYCRYPTO_USE_IPHONE_API
jens@24
   136
    SecTrustRef trust = NULL;
jens@24
   137
    SecPolicyRef policy = SecPolicyCreateBasicX509();
jens@24
   138
    OSStatus err = SecTrustCreateWithCertificates((CFArrayRef)$array((id)_certificateRef),
jens@24
   139
                                                  policy,
jens@24
   140
                                                  &trust);
jens@24
   141
    CFRelease(policy);
jens@24
   142
    if (!check(err,@"SecTrustCreateWithCertificates"))
jens@24
   143
        return nil;
jens@24
   144
    SecTrustResultType result;
jens@24
   145
    if (!check(SecTrustEvaluate(trust, &result), @"SecTrustEvaluate")) {
jens@24
   146
        CFRelease(trust);
jens@24
   147
        return nil;
jens@24
   148
    }
jens@24
   149
    keyRef = SecTrustCopyPublicKey(trust);
jens@24
   150
    CFRelease(trust);
jens@24
   151
#else
snej@0
   152
    if (!check(SecCertificateCopyPublicKey(_certificateRef, &keyRef),
snej@0
   153
               @"SecCertificateCopyPublicKey") || !keyRef)
snej@0
   154
        return nil;
jens@24
   155
#endif
jens@24
   156
    if (!keyRef)
jens@24
   157
        return nil;
snej@0
   158
    MYPublicKey *key = [[[MYPublicKey alloc] initWithKeyRef: keyRef] autorelease];
snej@0
   159
    CFRelease(keyRef);
snej@0
   160
    return key;
snej@0
   161
}
snej@0
   162
jens@16
   163
- (MYIdentity*) identity {
jens@16
   164
    return [[[MYIdentity alloc] initWithCertificateRef: _certificateRef] autorelease];
jens@16
   165
}
jens@16
   166
jens@21
   167
- (MYCertificateInfo*) info {
jens@21
   168
    if (!_info) {
jens@21
   169
        NSError *error;
jens@21
   170
        _info = [[MYCertificateInfo alloc] initWithCertificateData: self.certificateData
jens@21
   171
                                                             error: &error];
jens@21
   172
        if (!_info)
jens@21
   173
            Warn(@"Couldn't parse certificate %@: %@", self, error);
jens@21
   174
    }
jens@21
   175
    return _info;
jens@21
   176
}
jens@21
   177
snej@0
   178
- (NSString*) commonName {
snej@0
   179
    CFStringRef name = NULL;
jens@24
   180
#if MYCRYPTO_USE_IPHONE_API
jens@24
   181
    name = SecCertificateCopySubjectSummary(_certificateRef);
jens@24
   182
#else
snej@0
   183
    if (!check(SecCertificateCopyCommonName(_certificateRef, &name),
jens@24
   184
               @"SecCertificateCopyCommonName"))
snej@0
   185
        return nil;
jens@24
   186
#endif
jens@24
   187
    return name ?[NSMakeCollectable(name) autorelease] :nil;
snej@0
   188
}
snej@0
   189
snej@0
   190
- (NSArray*) emailAddresses {
jens@24
   191
#if MYCRYPTO_USE_IPHONE_API
jens@24
   192
    NSString *email = self.info.subject.emailAddress;
jens@24
   193
    return email ?$array(email) :nil;
jens@24
   194
#else
snej@0
   195
    CFArrayRef addrs = NULL;
snej@0
   196
    if (!check(SecCertificateCopyEmailAddresses(_certificateRef, &addrs),
snej@0
   197
               @"SecCertificateCopyEmailAddresses") || !addrs)
snej@0
   198
        return nil;
snej@0
   199
    return [(id)CFMakeCollectable(addrs) autorelease];
jens@24
   200
#endif
snej@0
   201
}
snej@0
   202
snej@0
   203
snej@8
   204
#pragma mark -
snej@8
   205
#pragma mark TRUST/POLICY STUFF:
snej@8
   206
snej@8
   207
jens@24
   208
- (SecTrustResultType) evaluateTrustWithPolicy: (SecPolicyRef)policy {
jens@24
   209
    SecTrustRef trust;
jens@24
   210
    if (!check(SecTrustCreateWithCertificates((CFArrayRef)$array((id)_certificateRef), policy, &trust), 
jens@24
   211
               @"SecTrustCreateWithCertificates"))
jens@24
   212
        return kSecTrustResultOtherError;
jens@24
   213
    SecTrustResultType result;
jens@24
   214
    if (!check(SecTrustEvaluate(trust, &result), @"SecTrustEvaluate"))
jens@24
   215
        result = kSecTrustResultOtherError;
jens@24
   216
    
jens@24
   217
#if 0
jens@24
   218
    // This is just to log details:
jens@24
   219
    CSSM_TP_APPLE_EVIDENCE_INFO *status;
jens@24
   220
    CFArrayRef certChain;
jens@24
   221
    if (check(SecTrustGetResult(trust, &result, &certChain, &status), @"SecTrustGetResult")) {
jens@24
   222
        Log(@"evaluateTrust: result=%@, bits=%X, certChain=%@", MYTrustResultDescribe(result),status->StatusBits, certChain);
jens@24
   223
        for (unsigned i=0; i<status->NumStatusCodes; i++)
jens@24
   224
            Log(@"    #%i: %X", i, status->StatusCodes[i]);
jens@24
   225
        CFRelease(certChain);
jens@24
   226
    }
jens@24
   227
#endif
jens@24
   228
    
jens@24
   229
    CFRelease(trust);
jens@24
   230
    return result;
jens@24
   231
}
jens@24
   232
jens@24
   233
- (SecTrustResultType) evaluateTrust {
jens@24
   234
    return [self evaluateTrustWithPolicy: [[self class] X509Policy]];
jens@24
   235
}
jens@24
   236
jens@24
   237
jens@24
   238
#if !MYCRYPTO_USE_IPHONE_API
snej@8
   239
+ (SecPolicyRef) policyForOID: (CSSM_OID) policyOID {
snej@8
   240
    SecPolicySearchRef search;
snej@8
   241
    if (!check(SecPolicySearchCreate(CSSM_CERT_X_509v3, &policyOID, NULL, &search),
snej@8
   242
           @"SecPolicySearchCreate"))
snej@8
   243
        return nil;
snej@8
   244
    SecPolicyRef policy = NULL;
snej@8
   245
    if (!check(SecPolicySearchCopyNext(search, &policy), @"SecPolicySearchCopyNext"))
snej@8
   246
        policy = NULL;
snej@8
   247
    CFRelease(search);
snej@8
   248
    return policy;
snej@8
   249
}
jens@24
   250
#endif
snej@8
   251
snej@8
   252
+ (SecPolicyRef) X509Policy {
snej@8
   253
    static SecPolicyRef sX509Policy = NULL;
jens@24
   254
    if (!sX509Policy) {
jens@24
   255
#if MYCRYPTO_USE_IPHONE_API
jens@24
   256
        sX509Policy = SecPolicyCreateBasicX509();
jens@24
   257
#else
snej@8
   258
        sX509Policy = [self policyForOID: CSSMOID_APPLE_X509_BASIC];
jens@24
   259
#endif
jens@24
   260
    }
snej@8
   261
    return sX509Policy;
snej@8
   262
}
snej@8
   263
snej@8
   264
+ (SecPolicyRef) SSLPolicy {
snej@8
   265
    static SecPolicyRef sSSLPolicy = NULL;
jens@24
   266
    if (!sSSLPolicy) {
jens@24
   267
#if MYCRYPTO_USE_IPHONE_API
jens@24
   268
        sSSLPolicy = SecPolicyCreateSSL(NO,NULL);
jens@24
   269
#else
snej@8
   270
        sSSLPolicy = [self policyForOID: CSSMOID_APPLE_TP_SSL];
jens@24
   271
#endif
jens@24
   272
    }
snej@8
   273
    return sSSLPolicy;
snej@8
   274
}
snej@8
   275
jens@24
   276
#if !TARGET_OS_IPHONE
snej@8
   277
+ (SecPolicyRef) SMIMEPolicy {
snej@8
   278
    static SecPolicyRef sSMIMEPolicy = NULL;
jens@24
   279
    if (!sSMIMEPolicy) {
snej@8
   280
        sSMIMEPolicy = [self policyForOID: CSSMOID_APPLE_TP_SMIME];
jens@24
   281
    }
snej@8
   282
    return sSMIMEPolicy;
snej@8
   283
}
snej@8
   284
snej@8
   285
- (CSSM_CERT_TYPE) certificateType {
snej@8
   286
    CSSM_CERT_TYPE type = CSSM_CERT_UNKNOWN;
snej@8
   287
    if (!check(SecCertificateGetType(_certificateRef, &type), @"SecCertificateGetType"))
snej@8
   288
        type = CSSM_CERT_UNKNOWN;
snej@8
   289
    return type;
snej@8
   290
}
snej@8
   291
snej@8
   292
- (NSArray*) trustSettings {
snej@8
   293
    CFArrayRef settings = NULL;
snej@8
   294
    OSStatus err = SecTrustSettingsCopyTrustSettings(_certificateRef, kSecTrustSettingsDomainUser, 
snej@8
   295
                                                     &settings);
snej@8
   296
    if (err == errSecItemNotFound || !check(err,@"SecTrustSettingsCopyTrustSettings") || !settings)
snej@8
   297
        return nil;
snej@8
   298
    return [(id)CFMakeCollectable(settings) autorelease];
snej@8
   299
}
snej@8
   300
        
snej@8
   301
snej@8
   302
- (BOOL) setUserTrust: (SecTrustUserSetting)trustSetting
snej@8
   303
{
snej@8
   304
    if (trustSetting == kSecTrustResultProceed) {
snej@8
   305
        return check(SecTrustSettingsSetTrustSettings(_certificateRef, 
snej@8
   306
                                                      kSecTrustSettingsDomainUser, nil),
snej@8
   307
                     @"SecTrustSettingsSetTrustSettings");
snej@8
   308
    } else if (trustSetting == kSecTrustResultDeny) {
snej@8
   309
        OSStatus err = SecTrustSettingsRemoveTrustSettings(_certificateRef, 
snej@8
   310
                                                           kSecTrustSettingsDomainUser);
snej@8
   311
        return err == errSecItemNotFound || check(err, @"SecTrustSettingsRemoveTrustSettings");
snej@8
   312
    } else
snej@8
   313
        return paramErr;
snej@8
   314
}
jens@24
   315
#endif
snej@8
   316
snej@8
   317
snej@0
   318
@end
snej@0
   319
snej@0
   320
snej@8
   321
NSString* MYTrustResultDescribe( SecTrustResultType result ) {
snej@8
   322
    static NSString* const kTrustResultNames[kSecTrustResultOtherError+1] = {
snej@8
   323
        @"Invalid",
snej@8
   324
        @"Proceed",
snej@8
   325
        @"Confirm",
snej@8
   326
        @"Deny",
snej@8
   327
        @"Unspecified",
snej@8
   328
        @"RecoverableTrustFailure",
snej@8
   329
        @"FatalTrustFailure",
snej@8
   330
        @"OtherError"
snej@8
   331
    };
snej@8
   332
    if (result>=0 && result <=kSecTrustResultOtherError)
snej@8
   333
        return kTrustResultNames[result];
snej@8
   334
    else
snej@8
   335
        return $sprintf(@"(Unknown trust result %i)", result);
snej@8
   336
}
snej@8
   337
snej@8
   338
jens@24
   339
#if !TARGET_OS_IPHONE
jens@24
   340
NSString* MYPolicyGetName( SecPolicyRef policy ) {
jens@24
   341
    if (!policy)
jens@24
   342
        return @"(null)";
jens@24
   343
    CSSM_OID oid = {};
jens@24
   344
    SecPolicyGetOID(policy, &oid);
jens@24
   345
    return $sprintf(@"SecPolicy[%@]", OIDAsString(oid));
jens@24
   346
}
jens@24
   347
snej@8
   348
NSString* MYTrustDescribe( SecTrustRef trust ) {
snej@8
   349
    SecTrustResultType result;
snej@8
   350
    CFArrayRef certChain=NULL;
snej@8
   351
    CSSM_TP_APPLE_EVIDENCE_INFO* statusChain;
snej@8
   352
    OSStatus err = SecTrustGetResult(trust, &result, &certChain, &statusChain);
snej@8
   353
    NSString *desc;
snej@8
   354
    if (err)
snej@8
   355
        desc = $sprintf(@"SecTrust[%p, err=%@]", trust, MYErrorName(NSOSStatusErrorDomain, err));
snej@8
   356
    else
snej@8
   357
        desc = $sprintf(@"SecTrust[%@, %u in chain]", 
snej@8
   358
                        MYTrustResultDescribe(result),
snej@8
   359
                        CFArrayGetCount(certChain));
snej@8
   360
    if (certChain) CFRelease(certChain);
snej@8
   361
    return desc;
snej@8
   362
}
snej@8
   363
snej@8
   364
snej@12
   365
// Taken from Keychain.framework
snej@12
   366
NSString* OIDAsString(const CSSM_OID oid) {
snej@12
   367
    if ((NULL == oid.Data) || (0 >= oid.Length)) {
snej@12
   368
        return nil;
snej@12
   369
    } else {
snej@12
   370
        NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)];
snej@12
   371
        unsigned int i;
snej@12
   372
        
snej@12
   373
        for (i = 0; i < oid.Length; ++i) {
snej@12
   374
            [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]];
snej@12
   375
        }
snej@12
   376
        
snej@12
   377
        return result;
snej@12
   378
    }
snej@12
   379
}
jens@24
   380
#endif
snej@12
   381
snej@12
   382
jens@24
   383
#if !TARGET_OS_IPHONE
snej@8
   384
TestCase(Trust) {
snej@8
   385
    Log(@"X.509 policy = %@", MYPolicyGetName([MYCertificate X509Policy]));
snej@8
   386
    Log(@"  SSL policy = %@", MYPolicyGetName([MYCertificate SSLPolicy]));
snej@8
   387
    Log(@"SMIME policy = %@", MYPolicyGetName([MYCertificate SMIMEPolicy]));
snej@8
   388
    for (MYCertificate *cert in [[MYKeychain defaultKeychain] enumerateCertificates]) {
snej@8
   389
        NSArray *settings = cert.trustSettings;
snej@8
   390
        if (settings)
snej@8
   391
            Log(@"---- %@ = %@", cert, settings);
snej@8
   392
    }
snej@8
   393
}
jens@24
   394
#endif
snej@14
   395
snej@14
   396
snej@14
   397
snej@14
   398
/*
snej@14
   399
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@14
   400
 
snej@14
   401
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@14
   402
 provided that the following conditions are met:
snej@14
   403
 
snej@14
   404
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@14
   405
 and the following disclaimer.
snej@14
   406
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@14
   407
 and the following disclaimer in the documentation and/or other materials provided with the
snej@14
   408
 distribution.
snej@14
   409
 
snej@14
   410
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@14
   411
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@14
   412
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@14
   413
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@14
   414
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@14
   415
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@14
   416
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@14
   417
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@14
   418
 */