OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/base/cert_database.h" | 5 #include "net/base/cert_database.h" |
6 | 6 |
7 #include <Security/Security.h> | 7 #include <Security/Security.h> |
8 | 8 |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/scoped_cftyperef.h" |
10 #include "net/base/net_errors.h" | 11 #include "net/base/net_errors.h" |
11 #include "net/base/x509_certificate.h" | 12 #include "net/base/x509_certificate.h" |
12 | 13 |
13 namespace net { | 14 namespace net { |
14 | 15 |
| 16 namespace { |
| 17 |
| 18 const void* CertRetainProc(CFAllocatorRef allocator, const void* value) { |
| 19 return CFRetain(value); |
| 20 } |
| 21 |
| 22 void CertReleaseProc(CFAllocatorRef allocator, const void* value) { |
| 23 CFRelease(value); |
| 24 } |
| 25 |
| 26 // Compare two certificates for equality. This is used as part of a |
| 27 // CFArrayCallbacks structure, as the CFTypeRef returned by |
| 28 // SecTrustGetResult may not point to the same location as was supplied |
| 29 // to ImportIntermediates(), even when they are equivalent. This ensures |
| 30 // the two certificates are properly compared. On OS X 10.6+, CFEqual can |
| 31 // be safely used, as it will make a proper comparison between the two. |
| 32 Boolean CertEqualityProc(const void* value1, const void* value2) { |
| 33 CFTypeID cert_type = SecCertificateGetTypeID(); |
| 34 if (CFGetTypeID(value1) != cert_type || CFGetTypeID(value2) != cert_type) |
| 35 return CFEqual(value1, value2); |
| 36 return X509Certificate::IsSameOSCert( |
| 37 reinterpret_cast<SecCertificateRef>(value1), |
| 38 reinterpret_cast<SecCertificateRef>(value2)); |
| 39 } |
| 40 |
| 41 // From an arbitrary, unordered list of certificate, attempt to import every |
| 42 // certificate that: |
| 43 // 1) Is a CA |
| 44 // 2) Results in a valid certificate chain |
| 45 void ImportIntermediates( |
| 46 const X509Certificate::OSCertHandles& intermediates) { |
| 47 OSStatus rv = noErr; |
| 48 CFArrayCallbacks array_cb = { 0, CertRetainProc, CertReleaseProc, |
| 49 CFCopyDescription, CertEqualityProc }; |
| 50 |
| 51 // See the "AppleX509XP Trust Policies" reference for documentation, but |
| 52 // this guarantees that the trust evaluation includes making sure the leaf |
| 53 // is a valid CA certificate. |
| 54 CSSM_APPLE_TP_ACTION_DATA tp_action; |
| 55 tp_action.Version = CSSM_APPLE_TP_ACTION_VERSION; |
| 56 tp_action.ActionFlags = CSSM_TP_ACTION_LEAF_IS_CA; |
| 57 |
| 58 scoped_cftyperef<CFDataRef> tp_action_data(CFDataCreateWithBytesNoCopy( |
| 59 kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&tp_action), |
| 60 sizeof(tp_action), kCFAllocatorDefault)); |
| 61 if (tp_action_data == NULL) |
| 62 return; |
| 63 |
| 64 // Create a temporary array so that it can be manipulated at will, and |
| 65 // because Sec* functions expect CFArrays. |
| 66 scoped_cftyperef<CFMutableArrayRef> certs_array(CFArrayCreateMutable( |
| 67 kCFAllocatorDefault, 0, &array_cb)); |
| 68 for (X509Certificate::OSCertHandles::const_iterator it = |
| 69 intermediates.begin(); it != intermediates.end(); ++it) { |
| 70 CFArrayAppendValue(certs_array, *it); |
| 71 } |
| 72 |
| 73 // Create a basic X.509 policy, which will ensure that each constructed |
| 74 // chain matches the minimal criteria for well-formedness. |
| 75 scoped_cftyperef<SecPolicySearchRef> policy_search_ref; |
| 76 SecPolicySearchRef temp_search_ref = NULL; |
| 77 rv = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, |
| 78 NULL, &temp_search_ref); |
| 79 if (rv != noErr || temp_search_ref == NULL) |
| 80 return; |
| 81 policy_search_ref.reset(temp_search_ref); |
| 82 |
| 83 scoped_cftyperef<SecPolicyRef> policy_ref; |
| 84 SecPolicyRef temp_policy_ref = NULL; |
| 85 rv = SecPolicySearchCopyNext(policy_search_ref, &temp_policy_ref); |
| 86 if (rv != noErr || temp_trust_ref == NULL) |
| 87 return; |
| 88 policy_ref.reset(temp_policy_ref); |
| 89 |
| 90 // Enumerate all the certificates, building a chain for each certificate. |
| 91 // If the constructed chain is trusted, and solely comprised of CA |
| 92 // certificates, add each certificate that was in |intermediates| into the |
| 93 // keychain. |
| 94 CFIndex certs_count = CFArrayGetCount(certs_array); |
| 95 size_t offset = 0; |
| 96 for (CFIndex i = 0; i < certs_count; ++i, ++offset) { |
| 97 if (i > 0 && certs_count > 1) { |
| 98 // Rotate the array so that the last item becomes the first, and |
| 99 // everything else is shifted forward by 1. This is because |
| 100 // SecTrustCreateWithCertificates begins validation against the |
| 101 // certificate at index 0. |
| 102 for (CFIndex j = 0; j < certs_count - 1; ++j) { |
| 103 CFArrayExchangeValuesAtIndices(certs_array, certs_count - 1 - j, |
| 104 certs_count - 2 - j); |
| 105 } |
| 106 } |
| 107 |
| 108 SecTrustRef temp_trust_ref = NULL; |
| 109 rv = SecTrustCreateWithCertificates(certs_array, policy_ref, |
| 110 &temp_trust_ref); |
| 111 if (rv != noErr) |
| 112 continue; |
| 113 scoped_cftyperef<SecTrustRef> trust_ref(temp_trust_ref); |
| 114 temp_trust_ref = NULL; |
| 115 |
| 116 rv = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT, |
| 117 tp_action_data); |
| 118 if (rv != noErr) |
| 119 continue; |
| 120 |
| 121 SecTrustResultType result = kSecTrustResultInvalid; |
| 122 rv = SecTrustEvalute(trust_ref, &result); |
| 123 // Allowing kSecTrustResultConfirm, as it will be honored when a chain is |
| 124 // built using this certificate that ends in the certificate that was |
| 125 // flagged as such. However, kSecTrustResultDeny is honored here, to |
| 126 // match behaviour of NSS/Windows implementations of rejecting chains the |
| 127 // user has explicitly rejected. |
| 128 if (rv != noErr || !(result == kSecTrustResultProceed || |
| 129 result == kSecTrustResultUnspecified || |
| 130 result == kSecTrustResultConfirm)) { |
| 131 if (rv != noErr) { |
| 132 DLOG(WARNING) << rv << " Unable to get certificate chain for " |
| 133 << "intermediate " << offset; |
| 134 } else { |
| 135 DLOG(WARNING) << "Certificate chain for intermediate " << offset |
| 136 << " is not trusted. " << result; |
| 137 } |
| 138 continue; |
| 139 } |
| 140 |
| 141 CFArrayRef temp_chain = NULL; |
| 142 rv = SecTrustGetResult(trust_ref, &result, &temp_chain, NULL); |
| 143 if (rv != noErr || temp_chain == NULL) |
| 144 continue; |
| 145 |
| 146 scoped_cftyperef<CFArrayRef> chain(temp_chain); |
| 147 temp_chain = NULL; |
| 148 |
| 149 for (CFIndex j = 0; j < CFArrayGetCount(chain); ++j) { |
| 150 SecCertificateRef chain_cert = CFArrayGetValueAtIndex(chain, j); |
| 151 CFIndex orig_index = CFArrayGetFirstIndexOfValue(certs_array, |
| 152 CFRangeMake(0, 0), |
| 153 chain_cert); |
| 154 if (orig_index == -1) |
| 155 continue; |
| 156 // Not checking the result code, because it doesn't matter too much if |
| 157 // the certificate cannot be imported. |
| 158 rv = SecCertificateAddToKeychain(chain_cert, NULL); |
| 159 if (rv != noErr && rv != errSecDuplicateItem) { |
| 160 DLOG(WARNING) << rv << " Unable to import intermediate " |
| 161 << offset; |
| 162 } |
| 163 } |
| 164 } |
| 165 } // namespace |
| 166 |
15 CertDatabase::CertDatabase() { | 167 CertDatabase::CertDatabase() { |
16 } | 168 } |
17 | 169 |
18 int CertDatabase::CheckUserCert(X509Certificate* cert) { | 170 int CertDatabase::CheckUserCert(X509Certificate* cert) { |
19 if (!cert) | 171 if (!cert) |
20 return ERR_CERT_INVALID; | 172 return ERR_CERT_INVALID; |
21 if (cert->HasExpired()) | 173 if (cert->HasExpired()) |
22 return ERR_CERT_DATE_INVALID; | 174 return ERR_CERT_DATE_INVALID; |
23 if (!cert->SupportsSSLClientAuth()) | |
24 return ERR_CERT_INVALID; | |
25 | 175 |
26 // Verify the Keychain already has the corresponding private key: | 176 // Verify the Keychain already has the corresponding private key: |
27 SecIdentityRef identity = NULL; | 177 SecIdentityRef identity = NULL; |
28 OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(), | 178 OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(), |
29 &identity); | 179 &identity); |
30 if (err == errSecItemNotFound) { | 180 if (err == errSecItemNotFound) { |
31 LOG(ERROR) << "CertDatabase couldn't find private key for user cert"; | 181 LOG(ERROR) << "CertDatabase couldn't find private key for user cert"; |
32 return ERR_NO_PRIVATE_KEY_FOR_CERT; | 182 return ERR_NO_PRIVATE_KEY_FOR_CERT; |
33 } | 183 } |
34 if (err != noErr || !identity) { | 184 if (err != noErr || !identity) { |
35 // TODO(snej): Map the error code more intelligently. | 185 // TODO(snej): Map the error code more intelligently. |
36 return ERR_CERT_INVALID; | 186 return ERR_CERT_INVALID; |
37 } | 187 } |
38 | 188 |
39 CFRelease(identity); | 189 CFRelease(identity); |
40 return OK; | 190 return OK; |
41 } | 191 } |
42 | 192 |
43 int CertDatabase::AddUserCert(X509Certificate* cert) { | 193 int CertDatabase::AddUserCert(X509Certificate* cert) { |
44 OSStatus err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL); | 194 OSStatus err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL); |
45 switch (err) { | 195 if (err != noErr && err != errSecDuplicateItem) { |
46 case noErr: | 196 LOG(ERROR) << "CertDatabase failed to add cert to keychain: " << err; |
47 case errSecDuplicateItem: | 197 // TODO(snej): Map the error code more intelligently. |
48 return OK; | 198 return ERR_ADD_USER_CERT_FAILED; |
49 default: | |
50 LOG(ERROR) << "CertDatabase failed to add cert to keychain: " << err; | |
51 // TODO(snej): Map the error code more intelligently. | |
52 return ERR_ADD_USER_CERT_FAILED; | |
53 } | 199 } |
| 200 |
| 201 ImportIntermediates(cert->GetIntermediateCertificates()); |
| 202 return OK; |
54 } | 203 } |
55 | 204 |
56 } // namespace net | 205 } // namespace net |
OLD | NEW |