OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "net/base/x509_chain.h" |
| 6 |
| 7 #include <Security/Security.h> |
| 8 |
| 9 #include "base/logging.h" |
| 10 #include "base/scoped_cftyperef.h" |
| 11 #include "net/base/cert_status_flags.h" |
| 12 #include "net/base/cert_verify_result.h" |
| 13 #include "net/base/net_errors.h" |
| 14 #include "net/base/x509_certificate.h" |
| 15 |
| 16 namespace net { |
| 17 |
| 18 class MacTrustedCertificates { |
| 19 public: |
| 20 // Sets the trusted root certificate used by tests. Call with |cert| set |
| 21 // to NULL to clear the test certificate. |
| 22 void SetTestCertificate(X509Certificate* cert) { |
| 23 AutoLock lock(lock_); |
| 24 test_certificate_ = cert; |
| 25 } |
| 26 |
| 27 // Returns an array containing the trusted certificates for use with |
| 28 // SecTrustSetAnchorCertificates(). Returns NULL if the system-supplied |
| 29 // list of trust anchors is acceptable (that is, there is not test |
| 30 // certificate available). Ownership follows the Create Rule (caller |
| 31 // is responsible for calling CFRelease on the non-NULL result). |
| 32 CFArrayRef CopyTrustedCertificateArray() { |
| 33 AutoLock lock(lock_); |
| 34 |
| 35 if (!test_certificate_) |
| 36 return NULL; |
| 37 |
| 38 // Failure to copy the anchor certificates or add the test certificate |
| 39 // is non-fatal; SecTrustEvaluate() will use the system anchors instead. |
| 40 CFArrayRef anchor_array; |
| 41 OSStatus status = SecTrustCopyAnchorCertificates(&anchor_array); |
| 42 if (status) |
| 43 return NULL; |
| 44 scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array); |
| 45 CFMutableArrayRef merged_array = CFArrayCreateMutableCopy( |
| 46 kCFAllocatorDefault, 0, anchor_array); |
| 47 if (!merged_array) |
| 48 return NULL; |
| 49 CFArrayAppendValue(merged_array, test_certificate_->os_cert_handle()); |
| 50 |
| 51 return merged_array; |
| 52 } |
| 53 private: |
| 54 friend struct DefaultSingletonTraits<MacTrustedCertificates>; |
| 55 |
| 56 // Obtain an instance of MacTrustedCertificates via the singleton |
| 57 // interface. |
| 58 MacTrustedCertificates() : test_certificate_(NULL) { } |
| 59 |
| 60 // An X509Certificate object that may be appended to the list of |
| 61 // system trusted anchors. |
| 62 scoped_refptr<X509Certificate> test_certificate_; |
| 63 |
| 64 // The trusted cache may be accessed from multiple threads. |
| 65 mutable Lock lock_; |
| 66 |
| 67 DISALLOW_COPY_AND_ASSIGN(MacTrustedCertificates); |
| 68 }; |
| 69 |
| 70 void SetMacTestCertificate(X509Certificate* cert) { |
| 71 Singleton<MacTrustedCertificates>::get()->SetTestCertificate(cert); |
| 72 } |
| 73 |
| 74 namespace { |
| 75 |
| 76 typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef, |
| 77 CFDictionaryRef*); |
| 78 |
| 79 int NetErrorFromOSStatus(OSStatus status) { |
| 80 switch (status) { |
| 81 case noErr: |
| 82 return OK; |
| 83 case errSecNotAvailable: |
| 84 case errSecNoCertificateModule: |
| 85 case errSecNoPolicyModule: |
| 86 return ERR_NOT_IMPLEMENTED; |
| 87 case errSecAuthFailed: |
| 88 return ERR_ACCESS_DENIED; |
| 89 default: |
| 90 LOG(ERROR) << "Unknown error " << status << " mapped to net::ERR_FAILED"; |
| 91 return ERR_FAILED; |
| 92 } |
| 93 } |
| 94 |
| 95 int CertStatusFromOSStatus(OSStatus status) { |
| 96 switch (status) { |
| 97 case noErr: |
| 98 return 0; |
| 99 |
| 100 case CSSMERR_TP_INVALID_ANCHOR_CERT: |
| 101 case CSSMERR_TP_NOT_TRUSTED: |
| 102 case CSSMERR_TP_INVALID_CERT_AUTHORITY: |
| 103 return CERT_STATUS_AUTHORITY_INVALID; |
| 104 |
| 105 case CSSMERR_TP_CERT_EXPIRED: |
| 106 case CSSMERR_TP_CERT_NOT_VALID_YET: |
| 107 // "Expired" and "not yet valid" collapse into a single status. |
| 108 return CERT_STATUS_DATE_INVALID; |
| 109 |
| 110 case CSSMERR_TP_CERT_REVOKED: |
| 111 case CSSMERR_TP_CERT_SUSPENDED: |
| 112 return CERT_STATUS_REVOKED; |
| 113 |
| 114 case CSSMERR_APPLETP_HOSTNAME_MISMATCH: |
| 115 return CERT_STATUS_COMMON_NAME_INVALID; |
| 116 |
| 117 case CSSMERR_APPLETP_CRL_NOT_FOUND: |
| 118 case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK: |
| 119 case CSSMERR_APPLETP_OCSP_UNAVAILABLE: |
| 120 return CERT_STATUS_NO_REVOCATION_MECHANISM; |
| 121 |
| 122 case CSSMERR_APPLETP_CRL_NOT_TRUSTED: |
| 123 case CSSMERR_APPLETP_CRL_SERVER_DOWN: |
| 124 case CSSMERR_APPLETP_CRL_NOT_VALID_YET: |
| 125 case CSSMERR_APPLETP_NETWORK_FAILURE: |
| 126 case CSSMERR_APPLETP_OCSP_BAD_RESPONSE: |
| 127 case CSSMERR_APPLETP_OCSP_NO_SIGNER: |
| 128 case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED: |
| 129 case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED: |
| 130 case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ: |
| 131 case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR: |
| 132 case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER: |
| 133 // We asked for a revocation check, but didn't get it. |
| 134 return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; |
| 135 |
| 136 default: |
| 137 // Failure was due to something Chromium doesn't define a |
| 138 // specific status for (such as basic constraints violation, or |
| 139 // unknown critical extension) |
| 140 return CERT_STATUS_INVALID; |
| 141 } |
| 142 } |
| 143 |
| 144 bool OverrideHostnameMismatch(const std::string& hostname, |
| 145 std::vector<std::string>* dns_names) { |
| 146 // SecTrustEvaluate() does not check dotted IP addresses. If |
| 147 // hostname is provided as, say, 127.0.0.1, then the error |
| 148 // CSSMERR_APPLETP_HOSTNAME_MISMATCH will always be returned, |
| 149 // even if the certificate contains 127.0.0.1 as one of its names. |
| 150 // We, however, want to allow that behavior. SecTrustEvaluate() |
| 151 // only checks for digits and dots when considering whether a |
| 152 // hostname is an IP address, so IPv6 and hex addresses go through |
| 153 // its normal comparison. |
| 154 bool is_dotted_ip = true; |
| 155 bool override_hostname_mismatch = false; |
| 156 for (std::string::const_iterator c = hostname.begin(); |
| 157 c != hostname.end() && is_dotted_ip; ++c) |
| 158 is_dotted_ip = (*c >= '0' && *c <= '9') || *c == '.'; |
| 159 if (is_dotted_ip) { |
| 160 for (std::vector<std::string>::const_iterator name = dns_names->begin(); |
| 161 name != dns_names->end() && !override_hostname_mismatch; ++name) |
| 162 override_hostname_mismatch = (*name == hostname); |
| 163 } |
| 164 return override_hostname_mismatch; |
| 165 } |
| 166 |
| 167 // Creates a SecPolicyRef for the given OID, with optional value. |
| 168 OSStatus CreatePolicy(const CSSM_OID* policy_OID, |
| 169 void* option_data, |
| 170 size_t option_length, |
| 171 SecPolicyRef* policy) { |
| 172 SecPolicySearchRef search; |
| 173 OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_OID, NULL, |
| 174 &search); |
| 175 if (err) |
| 176 return err; |
| 177 err = SecPolicySearchCopyNext(search, policy); |
| 178 CFRelease(search); |
| 179 if (err) |
| 180 return err; |
| 181 |
| 182 if (option_data) { |
| 183 CSSM_DATA options_data = { |
| 184 option_length, |
| 185 reinterpret_cast<uint8_t*>(option_data) |
| 186 }; |
| 187 err = SecPolicySetValue(*policy, &options_data); |
| 188 if (err) { |
| 189 CFRelease(*policy); |
| 190 return err; |
| 191 } |
| 192 } |
| 193 return noErr; |
| 194 } |
| 195 |
| 196 } // namespace |
| 197 |
| 198 namespace x509_chain { |
| 199 |
| 200 int VerifySSLServer(X509Certificate* certificate, const std::string& hostname, |
| 201 int flags, CertVerifyResult* verify_result) { |
| 202 verify_result->Reset(); |
| 203 if (!certificate || !certificate->os_cert_handle()) |
| 204 return ERR_UNEXPECTED; |
| 205 |
| 206 // Create an SSL SecPolicyRef, and configure it to perform hostname |
| 207 // validation. The hostname check does 99% of what we want, with the |
| 208 // exception of dotted IPv4 addreses, which we handle ourselves below. |
| 209 CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { |
| 210 CSSM_APPLE_TP_SSL_OPTS_VERSION, |
| 211 hostname.size(), |
| 212 hostname.data(), |
| 213 0 |
| 214 }; |
| 215 SecPolicyRef ssl_policy; |
| 216 OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_SSL, |
| 217 &tp_ssl_options, |
| 218 sizeof(tp_ssl_options), |
| 219 &ssl_policy); |
| 220 if (status) |
| 221 return NetErrorFromOSStatus(status); |
| 222 scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy); |
| 223 |
| 224 // Create and configure a SecTrustRef, which takes our certificate(s) |
| 225 // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an |
| 226 // array of certificates, the first of which is the certificate we're |
| 227 // verifying, and the subsequent (optional) certificates are used for |
| 228 // chain building. |
| 229 scoped_cftyperef<CFArrayRef> cert_list( |
| 230 certificate->CreateOSCertListHandle()); |
| 231 |
| 232 SecTrustRef trust_ref = NULL; |
| 233 status = SecTrustCreateWithCertificates(cert_list, ssl_policy, &trust_ref); |
| 234 if (status) |
| 235 return NetErrorFromOSStatus(status); |
| 236 scoped_cftyperef<SecTrustRef> scoped_trust_ref(trust_ref); |
| 237 |
| 238 // Set the trusted anchor certificates for the SecTrustRef by merging the |
| 239 // system trust anchors and the test root certificate. |
| 240 CFArrayRef anchor_array = |
| 241 Singleton<MacTrustedCertificates>::get()->CopyTrustedCertificateArray(); |
| 242 scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array); |
| 243 if (anchor_array) { |
| 244 status = SecTrustSetAnchorCertificates(trust_ref, anchor_array); |
| 245 if (status) |
| 246 return NetErrorFromOSStatus(status); |
| 247 } |
| 248 |
| 249 if (flags & VERIFY_REV_CHECKING_ENABLED) { |
| 250 // When called with VERIFY_REV_CHECKING_ENABLED, we ask SecTrustEvaluate() |
| 251 // to apply OCSP and CRL checking, but we're still subject to the global |
| 252 // settings, which are configured in the Keychain Access application (in |
| 253 // the Certificates tab of the Preferences dialog). If the user has |
| 254 // revocation disabled (which is the default), then we will get |
| 255 // kSecTrustResultRecoverableTrustFailure back from SecTrustEvaluate() |
| 256 // with one of a number of sub error codes indicating that revocation |
| 257 // checking did not occur. In that case, we'll set our own result to include |
| 258 // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION. |
| 259 // |
| 260 // NOTE: This does not apply to EV certificates, which always get |
| 261 // revocation checks regardless of the global settings. |
| 262 verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; |
| 263 CSSM_APPLE_TP_ACTION_DATA tp_action_data = { CSSM_APPLE_TP_ACTION_VERSION }; |
| 264 tp_action_data.ActionFlags = CSSM_TP_ACTION_REQUIRE_REV_PER_CERT; |
| 265 CFDataRef action_data_ref = |
| 266 CFDataCreate(NULL, reinterpret_cast<UInt8*>(&tp_action_data), |
| 267 sizeof(tp_action_data)); |
| 268 if (!action_data_ref) |
| 269 return ERR_OUT_OF_MEMORY; |
| 270 scoped_cftyperef<CFDataRef> scoped_action_data_ref(action_data_ref); |
| 271 status = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT, |
| 272 action_data_ref); |
| 273 if (status) |
| 274 return NetErrorFromOSStatus(status); |
| 275 } |
| 276 |
| 277 // Verify the certificate. A non-zero result from SecTrustGetResult() |
| 278 // indicates that some fatal error occurred and the chain couldn't be |
| 279 // processed, not that the chain contains no errors. We need to examine the |
| 280 // output of SecTrustGetResult() to determine that. |
| 281 SecTrustResultType trust_result; |
| 282 status = SecTrustEvaluate(trust_ref, &trust_result); |
| 283 if (status) |
| 284 return NetErrorFromOSStatus(status); |
| 285 CFArrayRef completed_chain = NULL; |
| 286 CSSM_TP_APPLE_EVIDENCE_INFO* chain_info; |
| 287 status = SecTrustGetResult(trust_ref, &trust_result, &completed_chain, |
| 288 &chain_info); |
| 289 if (status) |
| 290 return NetErrorFromOSStatus(status); |
| 291 scoped_cftyperef<CFArrayRef> scoped_completed_chain(completed_chain); |
| 292 |
| 293 // Evaluate the results |
| 294 OSStatus cssm_result; |
| 295 bool got_certificate_error = false; |
| 296 switch (trust_result) { |
| 297 case kSecTrustResultUnspecified: |
| 298 case kSecTrustResultProceed: |
| 299 // Certificate chain is valid and trusted ("unspecified" indicates that |
| 300 // the user has not explicitly set a trust setting) |
| 301 break; |
| 302 |
| 303 case kSecTrustResultDeny: |
| 304 case kSecTrustResultConfirm: |
| 305 // Certificate chain is explicitly untrusted. For kSecTrustResultConfirm, |
| 306 // we're following what Secure Transport does and treating it as |
| 307 // "deny". |
| 308 verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; |
| 309 break; |
| 310 |
| 311 case kSecTrustResultRecoverableTrustFailure: |
| 312 // Certificate chain has a failure that can be overridden by the user. |
| 313 status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); |
| 314 if (status) |
| 315 return NetErrorFromOSStatus(status); |
| 316 switch (cssm_result) { |
| 317 case CSSMERR_TP_NOT_TRUSTED: |
| 318 case CSSMERR_TP_INVALID_ANCHOR_CERT: |
| 319 verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; |
| 320 break; |
| 321 case CSSMERR_TP_CERT_EXPIRED: |
| 322 case CSSMERR_TP_CERT_NOT_VALID_YET: |
| 323 verify_result->cert_status |= CERT_STATUS_DATE_INVALID; |
| 324 break; |
| 325 case CSSMERR_TP_CERT_REVOKED: |
| 326 case CSSMERR_TP_CERT_SUSPENDED: |
| 327 verify_result->cert_status |= CERT_STATUS_REVOKED; |
| 328 break; |
| 329 default: |
| 330 // Look for specific per-certificate errors below. |
| 331 break; |
| 332 } |
| 333 // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO |
| 334 // structure which can catch multiple errors from each certificate. |
| 335 for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain); |
| 336 index < chain_count; ++index) { |
| 337 if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED || |
| 338 chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET) |
| 339 verify_result->cert_status |= CERT_STATUS_DATE_INVALID; |
| 340 for (uint32 status_code_index = 0; |
| 341 status_code_index < chain_info[index].NumStatusCodes; |
| 342 ++status_code_index) { |
| 343 got_certificate_error = true; |
| 344 int cert_status = CertStatusFromOSStatus( |
| 345 chain_info[index].StatusCodes[status_code_index]); |
| 346 if (cert_status == CERT_STATUS_COMMON_NAME_INVALID) { |
| 347 std::vector<std::string> names; |
| 348 certificate->GetDNSNames(&names); |
| 349 if (OverrideHostnameMismatch(hostname, &names)) { |
| 350 cert_status = 0; |
| 351 } |
| 352 } |
| 353 verify_result->cert_status |= cert_status; |
| 354 } |
| 355 } |
| 356 // Be paranoid and ensure that we recorded at least one certificate |
| 357 // status on receiving kSecTrustResultRecoverableTrustFailure. The |
| 358 // call to SecTrustGetCssmResultCode() should pick up when the chain |
| 359 // is not trusted and the loop through CSSM_TP_APPLE_EVIDENCE_INFO |
| 360 // should pick up everything else, but let's be safe. |
| 361 if (!verify_result->cert_status && !got_certificate_error) { |
| 362 verify_result->cert_status |= CERT_STATUS_INVALID; |
| 363 NOTREACHED(); |
| 364 } |
| 365 break; |
| 366 |
| 367 default: |
| 368 status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); |
| 369 if (status) |
| 370 return NetErrorFromOSStatus(status); |
| 371 verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); |
| 372 if (!verify_result->cert_status) { |
| 373 verify_result->cert_status |= CERT_STATUS_INVALID; |
| 374 } |
| 375 break; |
| 376 } |
| 377 |
| 378 // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be |
| 379 // compatible with Windows, which in turn implements this behavior to be |
| 380 // compatible with WinHTTP, which doesn't report this error (bug 3004). |
| 381 verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; |
| 382 |
| 383 if (IsCertStatusError(verify_result->cert_status)) |
| 384 return MapCertStatusToNetError(verify_result->cert_status); |
| 385 |
| 386 if (flags & VERIFY_EV_CERT) { |
| 387 // Determine the certificate's EV status using SecTrustCopyExtendedResult(), |
| 388 // which we need to look up because the function wasn't added until |
| 389 // Mac OS X 10.5.7. |
| 390 CFBundleRef bundle = |
| 391 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); |
| 392 if (bundle) { |
| 393 SecTrustCopyExtendedResultFuncPtr copy_extended_result = |
| 394 reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>( |
| 395 CFBundleGetFunctionPointerForName(bundle, |
| 396 CFSTR("SecTrustCopyExtendedResult"))); |
| 397 if (copy_extended_result) { |
| 398 CFDictionaryRef ev_dict = NULL; |
| 399 status = copy_extended_result(trust_ref, &ev_dict); |
| 400 if (!status && ev_dict) { |
| 401 // The returned dictionary contains the EV organization name from the |
| 402 // server certificate, which we don't need at this point (and we |
| 403 // have other ways to access, anyway). All we care is that |
| 404 // SecTrustCopyExtendedResult() returned noErr and a non-NULL |
| 405 // dictionary. |
| 406 CFRelease(ev_dict); |
| 407 verify_result->cert_status |= CERT_STATUS_IS_EV; |
| 408 } |
| 409 } |
| 410 } |
| 411 } |
| 412 |
| 413 return OK; |
| 414 } |
| 415 |
| 416 } // namespace x509_chain |
| 417 |
| 418 } // namespace net |
OLD | NEW |