snej@0: // snej@0: // MYCertificate.m snej@0: // MYCrypto snej@0: // snej@0: // Created by Jens Alfke on 3/26/09. snej@0: // Copyright 2009 Jens Alfke. All rights reserved. snej@0: // snej@0: snej@0: #import "MYCertificate.h" snej@0: #import "MYCrypto_Private.h" jens@16: #import "MYIdentity.h" snej@8: #import "MYDigest.h" jens@21: #import "MYCertificateInfo.h" snej@8: #import "MYErrorUtils.h" snej@0: snej@0: snej@0: @implementation MYCertificate snej@0: snej@0: snej@0: /** Creates a MYCertificate object for an existing Keychain certificate reference. */ snej@0: - (id) initWithCertificateRef: (SecCertificateRef)certificateRef { snej@0: self = [super initWithKeychainItemRef: (SecKeychainItemRef)certificateRef]; snej@0: if (self) { snej@0: _certificateRef = certificateRef; // superclass has already CFRetained it snej@0: } snej@0: return self; snej@0: } snej@0: snej@8: + (MYCertificate*) certificateWithCertificateRef: (SecCertificateRef)certificateRef { snej@8: return [[[self alloc] initWithCertificateRef: certificateRef] autorelease]; snej@8: } snej@8: snej@0: /** Creates a MYCertificate object from exported key data, but does not add it to any keychain. */ snej@0: - (id) initWithCertificateData: (NSData*)data jens@24: #if !MYCRYPTO_USE_IPHONE_API snej@0: type: (CSSM_CERT_TYPE) type snej@0: encoding: (CSSM_CERT_ENCODING) encoding jens@24: #endif snej@0: { snej@0: Assert(data); jens@24: SecCertificateRef certificateRef = NULL; jens@24: #if MYCRYPTO_USE_IPHONE_API jens@24: certificateRef = SecCertificateCreateWithData(NULL, (CFDataRef)data); jens@24: #else snej@0: CSSM_DATA cssmData = {.Data=(void*)data.bytes, .Length=data.length}; snej@0: if (!check(SecCertificateCreateFromData(&cssmData, type, encoding, &certificateRef), jens@24: @"SecCertificateCreateFromData")) jens@24: certificateRef = NULL; jens@24: #endif jens@24: if (!certificateRef) { snej@0: [self release]; snej@0: return nil; snej@0: } snej@0: self = [self initWithCertificateRef: certificateRef]; snej@0: CFRelease(certificateRef); jens@24: jens@24: // If the cert is self-signed, verify its signature. Apple's frameworks don't do this, jens@24: // even the SecTrust API; if the signature doesn't verify, they just assume it could be jens@24: // signed by a different cert. Seems like a bad decision to me, so I'll add the check: jens@24: MYCertificateInfo *info = self.info; jens@24: if (info.isRoot) { jens@24: Log(@"Verifying self-signed certificate %@ ...", self); jens@24: if (![info verifySignatureWithKey: self.publicKey]) { jens@24: Log(@"Self-signed cert failed signature verification (%@)", self); jens@24: [self release]; jens@24: return nil; jens@24: } jens@24: } jens@24: snej@0: return self; snej@0: } snej@0: jens@24: #if !MYCRYPTO_USE_IPHONE_API snej@0: - (id) initWithCertificateData: (NSData*)data { snej@0: return [self initWithCertificateData: data snej@0: type: CSSM_CERT_X_509v3 snej@0: encoding: CSSM_CERT_ENCODING_BER]; snej@0: } jens@24: #endif jens@21: - (void) dealloc jens@21: { jens@21: [_info release]; jens@21: [super dealloc]; jens@21: } jens@21: jens@21: snej@8: snej@8: - (NSString*) description { snej@8: return $sprintf(@"%@[%@ %@/%p]", snej@8: [self class], snej@8: self.commonName, snej@8: self.certificateData.my_SHA1Digest.abbreviatedHexString, snej@8: _certificateRef); snej@8: } snej@8: snej@8: snej@8: - (BOOL)isEqualToCertificate:(MYCertificate*)cert { snej@8: return [self isEqual: cert] || [self.certificateData isEqual: cert.certificateData]; snej@8: } snej@8: snej@8: jens@24: #if !TARGET_OS_IPHONE snej@0: + (MYCertificate*) preferredCertificateForName: (NSString*)name { snej@0: SecCertificateRef certRef = NULL; snej@0: if (!check(SecCertificateCopyPreference((CFStringRef)name, 0, &certRef), snej@0: @"SecCertificateCopyPreference")) snej@0: return nil; snej@0: return [[[MYCertificate alloc] initWithCertificateRef: certRef] autorelease]; snej@0: } snej@0: snej@0: - (BOOL) setPreferredCertificateForName: (NSString*)name { snej@0: return check(SecCertificateSetPreference(_certificateRef, (CFStringRef)name, 0, NULL), snej@0: @"SecCertificateSetPreference"); snej@0: } jens@24: #endif TARGET_OS_IPHONE snej@0: snej@8: snej@0: @synthesize certificateRef=_certificateRef; snej@0: snej@0: - (NSData*) certificateData { jens@24: #if MYCRYPTO_USE_IPHONE_API jens@24: CFDataRef data = SecCertificateCopyData(_certificateRef); jens@24: return data ?[(id)CFMakeCollectable(data) autorelease] :nil; jens@24: #else snej@0: CSSM_DATA cssmData; snej@0: if (!check(SecCertificateGetData(_certificateRef, &cssmData), snej@0: @"SecCertificateGetData")) snej@0: return nil; snej@0: return [NSData dataWithBytes: cssmData.Data length: cssmData.Length]; jens@24: #endif snej@0: } snej@0: snej@0: - (MYPublicKey*) publicKey { snej@0: SecKeyRef keyRef = NULL; jens@24: #if MYCRYPTO_USE_IPHONE_API jens@24: SecTrustRef trust = NULL; jens@24: SecPolicyRef policy = SecPolicyCreateBasicX509(); jens@24: OSStatus err = SecTrustCreateWithCertificates((CFArrayRef)$array((id)_certificateRef), jens@24: policy, jens@24: &trust); jens@24: CFRelease(policy); jens@24: if (!check(err,@"SecTrustCreateWithCertificates")) jens@24: return nil; jens@24: SecTrustResultType result; jens@24: if (!check(SecTrustEvaluate(trust, &result), @"SecTrustEvaluate")) { jens@24: CFRelease(trust); jens@24: return nil; jens@24: } jens@24: keyRef = SecTrustCopyPublicKey(trust); jens@24: CFRelease(trust); jens@24: #else snej@0: if (!check(SecCertificateCopyPublicKey(_certificateRef, &keyRef), snej@0: @"SecCertificateCopyPublicKey") || !keyRef) snej@0: return nil; jens@24: #endif jens@24: if (!keyRef) jens@24: return nil; snej@0: MYPublicKey *key = [[[MYPublicKey alloc] initWithKeyRef: keyRef] autorelease]; snej@0: CFRelease(keyRef); snej@0: return key; snej@0: } snej@0: jens@16: - (MYIdentity*) identity { jens@16: return [[[MYIdentity alloc] initWithCertificateRef: _certificateRef] autorelease]; jens@16: } jens@16: jens@21: - (MYCertificateInfo*) info { jens@21: if (!_info) { jens@21: NSError *error; jens@21: _info = [[MYCertificateInfo alloc] initWithCertificateData: self.certificateData jens@21: error: &error]; jens@21: if (!_info) jens@21: Warn(@"Couldn't parse certificate %@: %@", self, error); jens@21: } jens@21: return _info; jens@21: } jens@21: snej@0: - (NSString*) commonName { snej@0: CFStringRef name = NULL; jens@24: #if MYCRYPTO_USE_IPHONE_API jens@24: name = SecCertificateCopySubjectSummary(_certificateRef); jens@24: #else snej@0: if (!check(SecCertificateCopyCommonName(_certificateRef, &name), jens@24: @"SecCertificateCopyCommonName")) snej@0: return nil; jens@24: #endif jens@24: return name ?[NSMakeCollectable(name) autorelease] :nil; snej@0: } snej@0: snej@0: - (NSArray*) emailAddresses { jens@24: #if MYCRYPTO_USE_IPHONE_API jens@24: NSString *email = self.info.subject.emailAddress; jens@24: return email ?$array(email) :nil; jens@24: #else snej@0: CFArrayRef addrs = NULL; snej@0: if (!check(SecCertificateCopyEmailAddresses(_certificateRef, &addrs), snej@0: @"SecCertificateCopyEmailAddresses") || !addrs) snej@0: return nil; snej@0: return [(id)CFMakeCollectable(addrs) autorelease]; jens@24: #endif snej@0: } snej@0: snej@0: snej@8: #pragma mark - snej@8: #pragma mark TRUST/POLICY STUFF: snej@8: snej@8: jens@24: - (SecTrustResultType) evaluateTrustWithPolicy: (SecPolicyRef)policy { jens@24: SecTrustRef trust; jens@24: if (!check(SecTrustCreateWithCertificates((CFArrayRef)$array((id)_certificateRef), policy, &trust), jens@24: @"SecTrustCreateWithCertificates")) jens@24: return kSecTrustResultOtherError; jens@24: SecTrustResultType result; jens@24: if (!check(SecTrustEvaluate(trust, &result), @"SecTrustEvaluate")) jens@24: result = kSecTrustResultOtherError; jens@24: jens@24: #if 0 jens@24: // This is just to log details: jens@24: CSSM_TP_APPLE_EVIDENCE_INFO *status; jens@24: CFArrayRef certChain; jens@24: if (check(SecTrustGetResult(trust, &result, &certChain, &status), @"SecTrustGetResult")) { jens@24: Log(@"evaluateTrust: result=%@, bits=%X, certChain=%@", MYTrustResultDescribe(result),status->StatusBits, certChain); jens@24: for (unsigned i=0; iNumStatusCodes; i++) jens@24: Log(@" #%i: %X", i, status->StatusCodes[i]); jens@24: CFRelease(certChain); jens@24: } jens@24: #endif jens@24: jens@24: CFRelease(trust); jens@24: return result; jens@24: } jens@24: jens@24: - (SecTrustResultType) evaluateTrust { jens@24: return [self evaluateTrustWithPolicy: [[self class] X509Policy]]; jens@24: } jens@24: jens@24: jens@24: #if !MYCRYPTO_USE_IPHONE_API snej@8: + (SecPolicyRef) policyForOID: (CSSM_OID) policyOID { snej@8: SecPolicySearchRef search; snej@8: if (!check(SecPolicySearchCreate(CSSM_CERT_X_509v3, &policyOID, NULL, &search), snej@8: @"SecPolicySearchCreate")) snej@8: return nil; snej@8: SecPolicyRef policy = NULL; snej@8: if (!check(SecPolicySearchCopyNext(search, &policy), @"SecPolicySearchCopyNext")) snej@8: policy = NULL; snej@8: CFRelease(search); snej@8: return policy; snej@8: } jens@24: #endif snej@8: snej@8: + (SecPolicyRef) X509Policy { snej@8: static SecPolicyRef sX509Policy = NULL; jens@24: if (!sX509Policy) { jens@24: #if MYCRYPTO_USE_IPHONE_API jens@24: sX509Policy = SecPolicyCreateBasicX509(); jens@24: #else snej@8: sX509Policy = [self policyForOID: CSSMOID_APPLE_X509_BASIC]; jens@24: #endif jens@24: } snej@8: return sX509Policy; snej@8: } snej@8: snej@8: + (SecPolicyRef) SSLPolicy { snej@8: static SecPolicyRef sSSLPolicy = NULL; jens@24: if (!sSSLPolicy) { jens@24: #if MYCRYPTO_USE_IPHONE_API jens@24: sSSLPolicy = SecPolicyCreateSSL(NO,NULL); jens@24: #else snej@8: sSSLPolicy = [self policyForOID: CSSMOID_APPLE_TP_SSL]; jens@24: #endif jens@24: } snej@8: return sSSLPolicy; snej@8: } snej@8: jens@24: #if !TARGET_OS_IPHONE snej@8: + (SecPolicyRef) SMIMEPolicy { snej@8: static SecPolicyRef sSMIMEPolicy = NULL; jens@24: if (!sSMIMEPolicy) { snej@8: sSMIMEPolicy = [self policyForOID: CSSMOID_APPLE_TP_SMIME]; jens@24: } snej@8: return sSMIMEPolicy; snej@8: } snej@8: snej@8: - (CSSM_CERT_TYPE) certificateType { snej@8: CSSM_CERT_TYPE type = CSSM_CERT_UNKNOWN; snej@8: if (!check(SecCertificateGetType(_certificateRef, &type), @"SecCertificateGetType")) snej@8: type = CSSM_CERT_UNKNOWN; snej@8: return type; snej@8: } snej@8: snej@8: - (NSArray*) trustSettings { snej@8: CFArrayRef settings = NULL; snej@8: OSStatus err = SecTrustSettingsCopyTrustSettings(_certificateRef, kSecTrustSettingsDomainUser, snej@8: &settings); snej@8: if (err == errSecItemNotFound || !check(err,@"SecTrustSettingsCopyTrustSettings") || !settings) snej@8: return nil; snej@8: return [(id)CFMakeCollectable(settings) autorelease]; snej@8: } snej@8: snej@8: snej@8: - (BOOL) setUserTrust: (SecTrustUserSetting)trustSetting snej@8: { snej@8: if (trustSetting == kSecTrustResultProceed) { snej@8: return check(SecTrustSettingsSetTrustSettings(_certificateRef, snej@8: kSecTrustSettingsDomainUser, nil), snej@8: @"SecTrustSettingsSetTrustSettings"); snej@8: } else if (trustSetting == kSecTrustResultDeny) { snej@8: OSStatus err = SecTrustSettingsRemoveTrustSettings(_certificateRef, snej@8: kSecTrustSettingsDomainUser); snej@8: return err == errSecItemNotFound || check(err, @"SecTrustSettingsRemoveTrustSettings"); snej@8: } else snej@8: return paramErr; snej@8: } jens@24: #endif snej@8: snej@8: snej@0: @end snej@0: snej@0: snej@8: NSString* MYTrustResultDescribe( SecTrustResultType result ) { snej@8: static NSString* const kTrustResultNames[kSecTrustResultOtherError+1] = { snej@8: @"Invalid", snej@8: @"Proceed", snej@8: @"Confirm", snej@8: @"Deny", snej@8: @"Unspecified", snej@8: @"RecoverableTrustFailure", snej@8: @"FatalTrustFailure", snej@8: @"OtherError" snej@8: }; snej@8: if (result>=0 && result <=kSecTrustResultOtherError) snej@8: return kTrustResultNames[result]; snej@8: else snej@8: return $sprintf(@"(Unknown trust result %i)", result); snej@8: } snej@8: snej@8: jens@24: #if !TARGET_OS_IPHONE jens@24: NSString* MYPolicyGetName( SecPolicyRef policy ) { jens@24: if (!policy) jens@24: return @"(null)"; jens@24: CSSM_OID oid = {}; jens@24: SecPolicyGetOID(policy, &oid); jens@24: return $sprintf(@"SecPolicy[%@]", OIDAsString(oid)); jens@24: } jens@24: snej@8: NSString* MYTrustDescribe( SecTrustRef trust ) { snej@8: SecTrustResultType result; snej@8: CFArrayRef certChain=NULL; snej@8: CSSM_TP_APPLE_EVIDENCE_INFO* statusChain; snej@8: OSStatus err = SecTrustGetResult(trust, &result, &certChain, &statusChain); snej@8: NSString *desc; snej@8: if (err) snej@8: desc = $sprintf(@"SecTrust[%p, err=%@]", trust, MYErrorName(NSOSStatusErrorDomain, err)); snej@8: else snej@8: desc = $sprintf(@"SecTrust[%@, %u in chain]", snej@8: MYTrustResultDescribe(result), snej@8: CFArrayGetCount(certChain)); snej@8: if (certChain) CFRelease(certChain); snej@8: return desc; snej@8: } snej@8: snej@8: snej@12: // Taken from Keychain.framework snej@12: NSString* OIDAsString(const CSSM_OID oid) { snej@12: if ((NULL == oid.Data) || (0 >= oid.Length)) { snej@12: return nil; snej@12: } else { snej@12: NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)]; snej@12: unsigned int i; snej@12: snej@12: for (i = 0; i < oid.Length; ++i) { snej@12: [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]]; snej@12: } snej@12: snej@12: return result; snej@12: } snej@12: } jens@24: #endif snej@12: snej@12: jens@24: #if !TARGET_OS_IPHONE snej@8: TestCase(Trust) { snej@8: Log(@"X.509 policy = %@", MYPolicyGetName([MYCertificate X509Policy])); snej@8: Log(@" SSL policy = %@", MYPolicyGetName([MYCertificate SSLPolicy])); snej@8: Log(@"SMIME policy = %@", MYPolicyGetName([MYCertificate SMIMEPolicy])); snej@8: for (MYCertificate *cert in [[MYKeychain defaultKeychain] enumerateCertificates]) { snej@8: NSArray *settings = cert.trustSettings; snej@8: if (settings) snej@8: Log(@"---- %@ = %@", cert, settings); snej@8: } snej@8: } jens@24: #endif snej@14: snej@14: snej@14: snej@14: /* snej@14: Copyright (c) 2009, Jens Alfke . All rights reserved. snej@14: snej@14: Redistribution and use in source and binary forms, with or without modification, are permitted snej@14: provided that the following conditions are met: snej@14: snej@14: * Redistributions of source code must retain the above copyright notice, this list of conditions snej@14: and the following disclaimer. snej@14: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions snej@14: and the following disclaimer in the documentation and/or other materials provided with the snej@14: distribution. snej@14: snej@14: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR snej@14: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND snej@14: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- snej@14: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES snej@14: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR snej@14: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN snej@14: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF snej@14: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. snej@14: */