| Index: net/base/x509_certificate_nss.cc
|
| ===================================================================
|
| --- net/base/x509_certificate_nss.cc (revision 41854)
|
| +++ net/base/x509_certificate_nss.cc (working copy)
|
| @@ -138,6 +138,7 @@
|
| case SEC_ERROR_CERT_NOT_VALID:
|
| // TODO(port): add an ERR_CERT_WRONG_USAGE error code.
|
| case SEC_ERROR_CERT_USAGES_INVALID:
|
| + case SEC_ERROR_POLICY_VALIDATION_FAILED:
|
| return ERR_CERT_INVALID;
|
| default:
|
| LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
|
| @@ -169,6 +170,7 @@
|
| case SEC_ERROR_CERT_NOT_VALID:
|
| // TODO(port): add a CERT_STATUS_WRONG_USAGE error code.
|
| case SEC_ERROR_CERT_USAGES_INVALID:
|
| + case SEC_ERROR_POLICY_VALIDATION_FAILED:
|
| return CERT_STATUS_INVALID;
|
| default:
|
| return 0;
|
| @@ -319,6 +321,12 @@
|
| PORT_FreeArena(arena, PR_FALSE);
|
| }
|
|
|
| +// Forward declarations.
|
| +SECStatus RetryPKIXVerifyCertWithWorkarounds(
|
| + X509Certificate::OSCertHandle cert_handle, int num_policy_oids,
|
| + std::vector<CERTValInParam>* cvin, CERTValOutParam* cvout);
|
| +SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle);
|
| +
|
| // Call CERT_PKIXVerifyCert for the cert_handle.
|
| // Verification results are stored in an array of CERTValOutParam.
|
| // If policy_oids is not NULL and num_policy_oids is positive, policies
|
| @@ -392,60 +400,158 @@
|
| revocation_flags.chainTests.cert_rev_method_independent_flags =
|
| revocation_method_independent_flags;
|
|
|
| - CERTValInParam cvin[4];
|
| - int cvin_index = 0;
|
| + std::vector<CERTValInParam> cvin;
|
| + cvin.reserve(5);
|
| + CERTValInParam in_param;
|
| // No need to set cert_pi_trustAnchors here.
|
| - cvin[cvin_index].type = cert_pi_revocationFlags;
|
| - cvin[cvin_index].value.pointer.revocation = &revocation_flags;
|
| - cvin_index++;
|
| - std::vector<SECOidTag> policies;
|
| + in_param.type = cert_pi_revocationFlags;
|
| + in_param.value.pointer.revocation = &revocation_flags;
|
| + cvin.push_back(in_param);
|
| if (policy_oids && num_policy_oids > 0) {
|
| - cvin[cvin_index].type = cert_pi_policyOID;
|
| - cvin[cvin_index].value.arraySize = num_policy_oids;
|
| - cvin[cvin_index].value.array.oids = policy_oids;
|
| - cvin_index++;
|
| + in_param.type = cert_pi_policyOID;
|
| + in_param.value.arraySize = num_policy_oids;
|
| + in_param.value.array.oids = policy_oids;
|
| + cvin.push_back(in_param);
|
| }
|
| - // Add cert_pi_useAIACertFetch last so we can easily remove it from the
|
| - // cvin array in the workaround below.
|
| - cvin[cvin_index].type = cert_pi_useAIACertFetch;
|
| - cvin[cvin_index].value.scalar.b = PR_TRUE;
|
| - cvin_index++;
|
| - cvin[cvin_index].type = cert_pi_end;
|
| + in_param.type = cert_pi_end;
|
| + cvin.push_back(in_param);
|
|
|
| SECStatus rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
|
| - cvin, cvout, NULL);
|
| + &cvin[0], cvout, NULL);
|
| if (rv != SECSuccess) {
|
| - // cert_pi_useAIACertFetch can't handle a CA issuers access location that
|
| - // is an LDAP URL with an empty host name (NSS bug 528741). If cert fetch
|
| - // fails because of a network error, it also causes CERT_PKIXVerifyCert
|
| - // to report the network error rather than SEC_ERROR_UNKNOWN_ISSUER. To
|
| - // work around these NSS bugs, we retry without cert_pi_useAIACertFetch.
|
| - int nss_error = PORT_GetError();
|
| - if (nss_error == SEC_ERROR_INVALID_ARGS || !IS_SEC_ERROR(nss_error)) {
|
| - cvin_index--;
|
| - DCHECK_EQ(cvin[cvin_index].type, cert_pi_useAIACertFetch);
|
| - cvin[cvin_index].type = cert_pi_end;
|
| + rv = RetryPKIXVerifyCertWithWorkarounds(cert_handle, num_policy_oids,
|
| + &cvin, cvout);
|
| + }
|
| + return rv;
|
| +}
|
| +
|
| +// PKIXVerifyCert calls this function to work around some bugs in
|
| +// CERT_PKIXVerifyCert. All the arguments of this function are either the
|
| +// arguments or local variables of PKIXVerifyCert.
|
| +SECStatus RetryPKIXVerifyCertWithWorkarounds(
|
| + X509Certificate::OSCertHandle cert_handle, int num_policy_oids,
|
| + std::vector<CERTValInParam>* cvin, CERTValOutParam* cvout) {
|
| + // We call this function when the first CERT_PKIXVerifyCert call in
|
| + // PKIXVerifyCert failed, so we initialize |rv| to SECFailure.
|
| + SECStatus rv = SECFailure;
|
| + int nss_error = PORT_GetError();
|
| + CERTValInParam in_param;
|
| +
|
| + // If we get SEC_ERROR_UNKNOWN_ISSUER, we may be missing an intermediate
|
| + // CA certificate, so we retry with cert_pi_useAIACertFetch.
|
| + // cert_pi_useAIACertFetch has several bugs in its error handling and
|
| + // error reporting (NSS bug 528743), so we don't use it by default.
|
| + // Note: When building a certificate chain, CERT_PKIXVerifyCert may
|
| + // incorrectly pick a CA certificate with the same subject name as the
|
| + // missing intermediate CA certificate, and fail with the
|
| + // SEC_ERROR_BAD_SIGNATURE error (NSS bug 524013), so we also retry with
|
| + // cert_pi_useAIACertFetch on SEC_ERROR_BAD_SIGNATURE.
|
| + if (nss_error == SEC_ERROR_UNKNOWN_ISSUER ||
|
| + nss_error == SEC_ERROR_BAD_SIGNATURE) {
|
| + DCHECK_EQ(cvin->back().type, cert_pi_end);
|
| + cvin->pop_back();
|
| + in_param.type = cert_pi_useAIACertFetch;
|
| + in_param.value.scalar.b = PR_TRUE;
|
| + cvin->push_back(in_param);
|
| + in_param.type = cert_pi_end;
|
| + cvin->push_back(in_param);
|
| + rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
|
| + &(*cvin)[0], cvout, NULL);
|
| + if (rv == SECSuccess)
|
| + return rv;
|
| + int new_nss_error = PORT_GetError();
|
| + if (new_nss_error == SEC_ERROR_INVALID_ARGS ||
|
| + new_nss_error == SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE ||
|
| + !IS_SEC_ERROR(new_nss_error)) {
|
| + // Use the original error code because of cert_pi_useAIACertFetch's
|
| + // bad error reporting.
|
| + PORT_SetError(nss_error);
|
| + return rv;
|
| + }
|
| + nss_error = new_nss_error;
|
| + }
|
| +
|
| + // If an intermediate CA certificate has requireExplicitPolicy in its
|
| + // policyConstraints extension, CERT_PKIXVerifyCert fails with
|
| + // SEC_ERROR_POLICY_VALIDATION_FAILED because we didn't specify any
|
| + // certificate policy (NSS bug 552775). So we retry with the certificate
|
| + // policy found in the server certificate.
|
| + if (nss_error == SEC_ERROR_POLICY_VALIDATION_FAILED &&
|
| + num_policy_oids == 0) {
|
| + SECOidTag policy = GetFirstCertPolicy(cert_handle);
|
| + if (policy != SEC_OID_UNKNOWN) {
|
| + DCHECK_EQ(cvin->back().type, cert_pi_end);
|
| + cvin->pop_back();
|
| + in_param.type = cert_pi_policyOID;
|
| + in_param.value.arraySize = 1;
|
| + in_param.value.array.oids = &policy;
|
| + cvin->push_back(in_param);
|
| + in_param.type = cert_pi_end;
|
| + cvin->push_back(in_param);
|
| rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
|
| - cvin, cvout, NULL);
|
| + &(*cvin)[0], cvout, NULL);
|
| + if (rv != SECSuccess) {
|
| + // Use the original error code.
|
| + PORT_SetError(nss_error);
|
| + }
|
| }
|
| }
|
| +
|
| return rv;
|
| }
|
|
|
| -bool CheckCertPolicies(X509Certificate::OSCertHandle cert_handle,
|
| - SECOidTag ev_policy_tag) {
|
| +// Decodes the certificatePolicies extension of the certificate. Returns
|
| +// NULL if the certificate doesn't have the extension or the extension can't
|
| +// be decoded. The returned value must be freed with a
|
| +// CERT_DestroyCertificatePoliciesExtension call.
|
| +CERTCertificatePolicies* DecodeCertPolicies(
|
| + X509Certificate::OSCertHandle cert_handle) {
|
| SECItem policy_ext;
|
| SECStatus rv = CERT_FindCertExtension(
|
| cert_handle, SEC_OID_X509_CERTIFICATE_POLICIES, &policy_ext);
|
| - if (rv != SECSuccess) {
|
| - LOG(ERROR) << "Cert has no policies extension.";
|
| - return false;
|
| - }
|
| + if (rv != SECSuccess)
|
| + return NULL;
|
| CERTCertificatePolicies* policies =
|
| CERT_DecodeCertificatePoliciesExtension(&policy_ext);
|
| SECITEM_FreeItem(&policy_ext, PR_FALSE);
|
| + return policies;
|
| +}
|
| +
|
| +// Returns the OID tag for the first certificate policy in the certificate's
|
| +// certificatePolicies extension. Returns SEC_OID_UNKNOWN if the certificate
|
| +// has no certificate policy.
|
| +SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle) {
|
| + CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle);
|
| + if (!policies)
|
| + return SEC_OID_UNKNOWN;
|
| + ScopedCERTCertificatePolicies scoped_policies(policies);
|
| + CERTPolicyInfo* policy_info = policies->policyInfos[0];
|
| + if (!policy_info)
|
| + return SEC_OID_UNKNOWN;
|
| + if (policy_info->oid != SEC_OID_UNKNOWN)
|
| + return policy_info->oid;
|
| +
|
| + // The certificate policy is unknown to NSS. We need to create a dynamic
|
| + // OID tag for the policy.
|
| + SECOidData od;
|
| + od.oid.len = policy_info->policyID.len;
|
| + od.oid.data = policy_info->policyID.data;
|
| + od.offset = SEC_OID_UNKNOWN;
|
| + // NSS doesn't allow us to pass an empty description, so I use a hardcoded,
|
| + // default description here. The description doesn't need to be unique for
|
| + // each OID.
|
| + od.desc = "a certificate policy";
|
| + od.mechanism = CKM_INVALID_MECHANISM;
|
| + od.supportedExtension = INVALID_CERT_EXTENSION;
|
| + return SECOID_AddEntry(&od);
|
| +}
|
| +
|
| +bool CheckCertPolicies(X509Certificate::OSCertHandle cert_handle,
|
| + SECOidTag ev_policy_tag) {
|
| + CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle);
|
| if (!policies) {
|
| - LOG(ERROR) << "Failed to decode certificate policy.";
|
| + LOG(ERROR) << "Cert has no policies extension or extension couldn't be "
|
| + "decoded.";
|
| return false;
|
| }
|
| ScopedCERTCertificatePolicies scoped_policies(policies);
|
|
|