diff options
author | ukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-28 08:45:19 +0000 |
---|---|---|
committer | ukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-28 08:45:19 +0000 |
commit | 82fbbe66c39262147ee501daa1df53e3b669e385 (patch) | |
tree | 3895314901a464e30cd60ed4471462d16870a3f6 | |
parent | 8cb4689636ed19bfd3291602e59eedb194ba55cb (diff) | |
download | chromium_src-82fbbe66c39262147ee501daa1df53e3b669e385.zip chromium_src-82fbbe66c39262147ee501daa1df53e3b669e385.tar.gz chromium_src-82fbbe66c39262147ee501daa1df53e3b669e385.tar.bz2 |
Implement X509Certificate::Verify for Linux.
Use CERT_PKIXVerifyCert() with CRL to verify certificate.
With OCSP, CERT_PKIXVerifyCert() failed with
SEC_ERROR_INVALID_ARGS.
Increase stack size. It was not enough size if we use
CERT_PKIXVerifyCert() on some sites. For example,
https://www.google.com/ works, but https://bugs.webkit.org/
or https://www.thawte.com/ would die by SIGSEGV. This is
because pkix_List_Destroy() routine destroys PKIX_List
recursively, so if there are some long PKIX_Lists, it
consumes stack a lot and dies by stack overflow.
Note that X509Certificate::Verify isn't used in SSLClientSocketNSS yet.
BUG=10911
TEST=net_unittests passes
Review URL: http://codereview.chromium.org/113578
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17071 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/worker_pool_linux.cc | 4 | ||||
-rw-r--r-- | net/base/x509_certificate_nss.cc | 277 |
2 files changed, 273 insertions, 8 deletions
diff --git a/base/worker_pool_linux.cc b/base/worker_pool_linux.cc index a650b3b..31fcef1 100644 --- a/base/worker_pool_linux.cc +++ b/base/worker_pool_linux.cc @@ -15,7 +15,9 @@ namespace { const int kIdleSecondsBeforeExit = 10 * 60; -const int kWorkerThreadStackSize = 64 * 1024; +// A stack size of 64 KB is too small for the CERT_PKIXVerifyCert +// function of NSS because of NSS bug 439169. +const int kWorkerThreadStackSize = 128 * 1024; class WorkerPoolImpl { public: diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc index c53fe95..754ed1f 100644 --- a/net/base/x509_certificate_nss.cc +++ b/net/base/x509_certificate_nss.cc @@ -8,8 +8,10 @@ // until NSS 3.12.2 comes out and we update to it. #define Lock FOO_NSS_Lock #include <cert.h> +#include <pk11pub.h> #include <prtime.h> #include <secder.h> +#include <secerr.h> #include <sechash.h> #undef Lock @@ -17,12 +19,168 @@ #include "base/pickle.h" #include "base/time.h" #include "base/nss_init.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cert_verify_result.h" #include "net/base/net_errors.h" namespace net { namespace { +class ScopedCERTCertificate { + public: + explicit ScopedCERTCertificate(CERTCertificate* cert) + : cert_(cert) {} + + ~ScopedCERTCertificate() { + if (cert_) + CERT_DestroyCertificate(cert_); + } + + private: + CERTCertificate* cert_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCERTCertificate); +}; + +class ScopedCERTCertList { + public: + explicit ScopedCERTCertList(CERTCertList* cert_list) + : cert_list_(cert_list) {} + + ~ScopedCERTCertList() { + if (cert_list_) + CERT_DestroyCertList(cert_list_); + } + + private: + CERTCertList* cert_list_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCERTCertList); +}; + +// ScopedCERTValOutParam manages destruction of values in the CERTValOutParam +// array that cvout points to. cvout must be initialized as passed to +// CERT_PKIXVerifyCert, so that the array must be terminated with +// cert_po_end type. +// When it goes out of scope, it destroys values of cert_po_trustAnchor +// and cert_po_certList types, but doesn't release the array itself. +class ScopedCERTValOutParam { + public: + explicit ScopedCERTValOutParam(CERTValOutParam* cvout) + : cvout_(cvout) {} + + ~ScopedCERTValOutParam() { + if (cvout_ == NULL) + return; + for (CERTValOutParam *p = cvout_; p->type != cert_po_end; p++) { + switch (p->type) { + case cert_po_trustAnchor: + if (p->value.pointer.cert) { + CERT_DestroyCertificate(p->value.pointer.cert); + p->value.pointer.cert = NULL; + } + break; + case cert_po_certList: + if (p->value.pointer.chain) { + CERT_DestroyCertList(p->value.pointer.chain); + p->value.pointer.chain = NULL; + } + break; + default: + break; + } + } + } + + private: + CERTValOutParam* cvout_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCERTValOutParam); +}; + +// Map PORT_GetError() return values to our network error codes. +int MapSecurityError(int err) { + switch (err) { + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_EXPIRED_CERTIFICATE: + return ERR_CERT_DATE_INVALID; + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_CA_CERT_INVALID: + case SEC_ERROR_UNTRUSTED_CERT: + return ERR_CERT_AUTHORITY_INVALID; + case SEC_ERROR_REVOKED_CERTIFICATE: + return ERR_CERT_REVOKED; + case SEC_ERROR_BAD_DER: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_CERT_NOT_VALID: + // TODO(port): add an ERR_CERT_WRONG_USAGE error code. + case SEC_ERROR_CERT_USAGES_INVALID: + return ERR_CERT_INVALID; + default: + LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } +} + +// Map PORT_GetError() return values to our cert status flags. +int MapCertErrorToCertStatus(int err) { + switch (err) { + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_EXPIRED_CERTIFICATE: + return CERT_STATUS_DATE_INVALID; + case SEC_ERROR_UNTRUSTED_CERT: + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_CA_CERT_INVALID: + return CERT_STATUS_AUTHORITY_INVALID; + case SEC_ERROR_REVOKED_CERTIFICATE: + return CERT_STATUS_REVOKED; + case SEC_ERROR_BAD_DER: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_CERT_NOT_VALID: + // TODO(port): add an CERT_STATUS_WRONG_USAGE error code. + case SEC_ERROR_CERT_USAGES_INVALID: + return CERT_STATUS_INVALID; + default: + return 0; + } +} + +// Saves some information about the certificate chain cert_list in +// *verify_result. The caller MUST initialize *verify_result before calling +// this function. +// Note that cert_list[0] is the end entity certificate and cert_list doesn't +// contain the root CA certificate. +void GetCertChainInfo(CERTCertList* cert_list, + CertVerifyResult* verify_result) { + int i = 0; + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node), i++) { + SECAlgorithmID& signature = node->cert->signature; + SECOidTag oid_tag = SECOID_FindOIDTag(&signature.algorithm); + switch (oid_tag) { + case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + break; + case SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION: + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + break; + case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION: + verify_result->has_md4 = true; + break; + default: + break; + } + } +} + // TODO(port): Implement this more simply, and put it in the right place base::Time PRTimeToBaseTime(PRTime prtime) { PRExplodedTime prxtime; @@ -212,11 +370,122 @@ bool X509Certificate::HasExpired() const { return false; } +// TODO(ukai): fix to use this method to verify certificate on SSL channel. +// Note that it's not being used yet. We need to fix SSLClientSocketNSS to +// use this method to verify ssl certificate. +// The problem is that we get segfault when unit tests is going to terminate +// if PR_Cleanup is called in NSSInitSingleton destructor. int X509Certificate::Verify(const std::string& hostname, bool rev_checking_enabled, CertVerifyResult* verify_result) const { + verify_result->Reset(); + + // Make sure that the hostname matches with the common name of the cert. + SECStatus status = CERT_VerifyCertName(cert_handle_, hostname.c_str()); + if (status != SECSuccess) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + // Make sure that the cert is valid now. + SECCertTimeValidity validity = CERT_CheckCertValidTimes( + cert_handle_, PR_Now(), PR_TRUE); + if (validity != secCertTimeValid) + verify_result->cert_status |= CERT_STATUS_DATE_INVALID; + + CERTRevocationFlags revocation_flags; + // TODO(ukai): Fix to use OCSP. + // OCSP mode would fail with SEC_ERROR_UNKNOWN_ISSUER. + // We need to set up OCSP and install an HTTP client for NSS. + bool use_ocsp = false; + + PRUint64 revocation_method_flags = + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_ALLOW_NETWORK_FETCHING | + CERT_REV_M_ALLOW_IMPLICIT_DEFAULT_SOURCE | + CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE | + CERT_REV_M_STOP_TESTING_ON_FRESH_INFO; + PRUint64 revocation_method_independent_flags = + CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST | + CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE; + PRUint64 method_flags[2]; + method_flags[cert_revocation_method_crl] = revocation_method_flags; + method_flags[cert_revocation_method_ocsp] = revocation_method_flags; + + int number_of_defined_methods; + CERTRevocationMethodIndex preferred_revocation_methods[1]; + if (use_ocsp) { + number_of_defined_methods = 2; + preferred_revocation_methods[0] = cert_revocation_method_ocsp; + } else { + number_of_defined_methods = 1; + preferred_revocation_methods[0] = cert_revocation_method_crl; + } + + revocation_flags.leafTests.number_of_defined_methods = + number_of_defined_methods; + revocation_flags.leafTests.cert_rev_flags_per_method = method_flags; + revocation_flags.leafTests.number_of_preferred_methods = + arraysize(preferred_revocation_methods); + revocation_flags.leafTests.preferred_methods = preferred_revocation_methods; + revocation_flags.leafTests.cert_rev_method_independent_flags = + revocation_method_independent_flags; + revocation_flags.chainTests.number_of_defined_methods = + number_of_defined_methods; + revocation_flags.chainTests.cert_rev_flags_per_method = method_flags; + revocation_flags.chainTests.number_of_preferred_methods = + arraysize(preferred_revocation_methods); + revocation_flags.chainTests.preferred_methods = preferred_revocation_methods; + revocation_flags.chainTests.cert_rev_method_independent_flags = + revocation_method_independent_flags; + + CERTValInParam cvin[2]; + int cvin_index = 0; + // We can't use PK11_ListCerts(PK11CertListCA, NULL) for cert_pi_trustAnchors. + // We get SEC_ERROR_UNTRUSTED_ISSUER (-8172) for our test root CA cert with + // it by NSS 3.12.0.3. + // No need to set cert_pi_trustAnchors here. + // TODO(ukai): use cert_pi_useAIACertFetch (new feature in NSS 3.12.1). + cvin[cvin_index].type = cert_pi_revocationFlags; + cvin[cvin_index].value.pointer.revocation = &revocation_flags; + cvin_index++; + cvin[cvin_index].type = cert_pi_end; + + CERTValOutParam cvout[3]; + int cvout_index = 0; + cvout[cvout_index].type = cert_po_trustAnchor; + cvout[cvout_index].value.pointer.cert = NULL; + cvout_index++; + cvout[cvout_index].type = cert_po_certList; + cvout[cvout_index].value.pointer.chain = NULL; + int cvout_cert_list_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_end; + ScopedCERTValOutParam scoped_cvout(cvout); + + status = CERT_PKIXVerifyCert(cert_handle_, certificateUsageSSLServer, + cvin, cvout, NULL); + if (status != SECSuccess) { + int err = PORT_GetError(); + LOG(ERROR) << "CERT_PKIXVerifyCert failed err=" << err; + // CERT_PKIXVerifyCert rerports the wrong error code for + // expired certificates (NSS bug 491174) + if (err == SEC_ERROR_CERT_NOT_VALID && + (verify_result->cert_status & CERT_STATUS_DATE_INVALID) != 0) + err = SEC_ERROR_EXPIRED_CERTIFICATE; + verify_result->cert_status |= MapCertErrorToCertStatus(err); + return MapCertStatusToNetError(verify_result->cert_status); + } + + GetCertChainInfo(cvout[cvout_cert_list_index].value.pointer.chain, + verify_result); + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + return OK; +} + +// TODO(port): Implement properly on Linux. +bool X509Certificate::IsEV(int status) const { NOTIMPLEMENTED(); - return ERR_NOT_IMPLEMENTED; + return false; } // static @@ -252,10 +521,4 @@ X509Certificate::Fingerprint X509Certificate::CalculateFingerprint( return sha1; } -// TODO(port): Implement properly on Linux. -bool X509Certificate::IsEV(int status) const { - // http://code.google.com/p/chromium/issues/detail?id=10911 - return false; -} - } // namespace net |