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