| 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
|
|
|