Index: net/base/cert_database_mac.cc |
=================================================================== |
--- net/base/cert_database_mac.cc (revision 49024) |
+++ net/base/cert_database_mac.cc (working copy) |
@@ -7,11 +7,163 @@ |
#include <Security/Security.h> |
#include "base/logging.h" |
+#include "base/scoped_cftyperef.h" |
#include "net/base/net_errors.h" |
#include "net/base/x509_certificate.h" |
namespace net { |
+namespace { |
+ |
+const void* CertRetainProc(CFAllocatorRef allocator, const void* value) { |
+ return CFRetain(value); |
+} |
+ |
+void CertReleaseProc(CFAllocatorRef allocator, const void* value) { |
+ CFRelease(value); |
+} |
+ |
+// Compare two certificates for equality. This is used as part of a |
+// CFArrayCallbacks structure, as the CFTypeRef returned by |
+// SecTrustGetResult may not point to the same location as was supplied |
+// to ImportIntermediates(), even when they are equivalent. This ensures |
+// the two certificates are properly compared. On OS X 10.6+, CFEqual can |
+// be safely used, as it will make a proper comparison between the two. |
+Boolean CertEqualityProc(const void* value1, const void* value2) { |
+ CFTypeID cert_type = SecCertificateGetTypeID(); |
+ if (CFGetTypeID(value1) != cert_type || CFGetTypeID(value2) != cert_type) |
+ return CFEqual(value1, value2); |
+ return X509Certificate::IsSameOSCert( |
+ reinterpret_cast<SecCertificateRef>(value1), |
+ reinterpret_cast<SecCertificateRef>(value2)); |
+} |
+ |
+// From an arbitrary, unordered list of certificate, attempt to import every |
+// certificate that: |
+// 1) Is a CA |
+// 2) Results in a valid certificate chain |
+void ImportIntermediates( |
+ const X509Certificate::OSCertHandles& intermediates) { |
+ OSStatus rv = noErr; |
+ CFArrayCallbacks array_cb = { 0, CertRetainProc, CertReleaseProc, |
+ CFCopyDescription, CertEqualityProc }; |
+ |
+ // See the "AppleX509XP Trust Policies" reference for documentation, but |
+ // this guarantees that the trust evaluation includes making sure the leaf |
+ // is a valid CA certificate. |
+ CSSM_APPLE_TP_ACTION_DATA tp_action; |
+ tp_action.Version = CSSM_APPLE_TP_ACTION_VERSION; |
+ tp_action.ActionFlags = CSSM_TP_ACTION_LEAF_IS_CA; |
+ |
+ scoped_cftyperef<CFDataRef> tp_action_data(CFDataCreateWithBytesNoCopy( |
+ kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&tp_action), |
+ sizeof(tp_action), kCFAllocatorDefault)); |
+ if (tp_action_data == NULL) |
+ return; |
+ |
+ // Create a temporary array so that it can be manipulated at will, and |
+ // because Sec* functions expect CFArrays. |
+ scoped_cftyperef<CFMutableArrayRef> certs_array(CFArrayCreateMutable( |
+ kCFAllocatorDefault, 0, &array_cb)); |
+ for (X509Certificate::OSCertHandles::const_iterator it = |
+ intermediates.begin(); it != intermediates.end(); ++it) { |
+ CFArrayAppendValue(certs_array, *it); |
+ } |
+ |
+ // Create a basic X.509 policy, which will ensure that each constructed |
+ // chain matches the minimal criteria for well-formedness. |
+ scoped_cftyperef<SecPolicySearchRef> policy_search_ref; |
+ SecPolicySearchRef temp_search_ref = NULL; |
+ rv = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, |
+ NULL, &temp_search_ref); |
+ if (rv != noErr || temp_search_ref == NULL) |
+ return; |
+ policy_search_ref.reset(temp_search_ref); |
+ |
+ scoped_cftyperef<SecPolicyRef> policy_ref; |
+ SecPolicyRef temp_policy_ref = NULL; |
+ rv = SecPolicySearchCopyNext(policy_search_ref, &temp_policy_ref); |
+ if (rv != noErr || temp_trust_ref == NULL) |
+ return; |
+ policy_ref.reset(temp_policy_ref); |
+ |
+ // Enumerate all the certificates, building a chain for each certificate. |
+ // If the constructed chain is trusted, and solely comprised of CA |
+ // certificates, add each certificate that was in |intermediates| into the |
+ // keychain. |
+ CFIndex certs_count = CFArrayGetCount(certs_array); |
+ size_t offset = 0; |
+ for (CFIndex i = 0; i < certs_count; ++i, ++offset) { |
+ if (i > 0 && certs_count > 1) { |
+ // Rotate the array so that the last item becomes the first, and |
+ // everything else is shifted forward by 1. This is because |
+ // SecTrustCreateWithCertificates begins validation against the |
+ // certificate at index 0. |
+ for (CFIndex j = 0; j < certs_count - 1; ++j) { |
+ CFArrayExchangeValuesAtIndices(certs_array, certs_count - 1 - j, |
+ certs_count - 2 - j); |
+ } |
+ } |
+ |
+ SecTrustRef temp_trust_ref = NULL; |
+ rv = SecTrustCreateWithCertificates(certs_array, policy_ref, |
+ &temp_trust_ref); |
+ if (rv != noErr) |
+ continue; |
+ scoped_cftyperef<SecTrustRef> trust_ref(temp_trust_ref); |
+ temp_trust_ref = NULL; |
+ |
+ rv = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT, |
+ tp_action_data); |
+ if (rv != noErr) |
+ continue; |
+ |
+ SecTrustResultType result = kSecTrustResultInvalid; |
+ rv = SecTrustEvalute(trust_ref, &result); |
+ // Allowing kSecTrustResultConfirm, as it will be honored when a chain is |
+ // built using this certificate that ends in the certificate that was |
+ // flagged as such. However, kSecTrustResultDeny is honored here, to |
+ // match behaviour of NSS/Windows implementations of rejecting chains the |
+ // user has explicitly rejected. |
+ if (rv != noErr || !(result == kSecTrustResultProceed || |
+ result == kSecTrustResultUnspecified || |
+ result == kSecTrustResultConfirm)) { |
+ if (rv != noErr) { |
+ DLOG(WARNING) << rv << " Unable to get certificate chain for " |
+ << "intermediate " << offset; |
+ } else { |
+ DLOG(WARNING) << "Certificate chain for intermediate " << offset |
+ << " is not trusted. " << result; |
+ } |
+ continue; |
+ } |
+ |
+ CFArrayRef temp_chain = NULL; |
+ rv = SecTrustGetResult(trust_ref, &result, &temp_chain, NULL); |
+ if (rv != noErr || temp_chain == NULL) |
+ continue; |
+ |
+ scoped_cftyperef<CFArrayRef> chain(temp_chain); |
+ temp_chain = NULL; |
+ |
+ for (CFIndex j = 0; j < CFArrayGetCount(chain); ++j) { |
+ SecCertificateRef chain_cert = CFArrayGetValueAtIndex(chain, j); |
+ CFIndex orig_index = CFArrayGetFirstIndexOfValue(certs_array, |
+ CFRangeMake(0, 0), |
+ chain_cert); |
+ if (orig_index == -1) |
+ continue; |
+ // Not checking the result code, because it doesn't matter too much if |
+ // the certificate cannot be imported. |
+ rv = SecCertificateAddToKeychain(chain_cert, NULL); |
+ if (rv != noErr && rv != errSecDuplicateItem) { |
+ DLOG(WARNING) << rv << " Unable to import intermediate " |
+ << offset; |
+ } |
+ } |
+ } |
+} // namespace |
+ |
CertDatabase::CertDatabase() { |
} |
@@ -20,8 +172,6 @@ |
return ERR_CERT_INVALID; |
if (cert->HasExpired()) |
return ERR_CERT_DATE_INVALID; |
- if (!cert->SupportsSSLClientAuth()) |
- return ERR_CERT_INVALID; |
// Verify the Keychain already has the corresponding private key: |
SecIdentityRef identity = NULL; |
@@ -42,15 +192,14 @@ |
int CertDatabase::AddUserCert(X509Certificate* cert) { |
OSStatus err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL); |
- switch (err) { |
- case noErr: |
- case errSecDuplicateItem: |
- return OK; |
- default: |
- LOG(ERROR) << "CertDatabase failed to add cert to keychain: " << err; |
- // TODO(snej): Map the error code more intelligently. |
- return ERR_ADD_USER_CERT_FAILED; |
+ if (err != noErr && err != errSecDuplicateItem) { |
+ LOG(ERROR) << "CertDatabase failed to add cert to keychain: " << err; |
+ // TODO(snej): Map the error code more intelligently. |
+ return ERR_ADD_USER_CERT_FAILED; |
} |
+ |
+ ImportIntermediates(cert->GetIntermediateCertificates()); |
+ return OK; |
} |
} // namespace net |