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