diff options
-rw-r--r-- | base/nss_init.cc | 61 | ||||
-rw-r--r-- | base/nss_init.h | 2 | ||||
-rw-r--r-- | net/base/ssl_client_socket_nss.cc | 161 | ||||
-rw-r--r-- | net/base/ssl_client_socket_nss.h | 2 | ||||
-rw-r--r-- | net/base/ssl_test_util.cc | 145 | ||||
-rw-r--r-- | net/base/ssl_test_util.h | 47 | ||||
-rw-r--r-- | net/net_unittests.scons | 1 | ||||
-rw-r--r-- | net/url_request/url_request_unittest.cc | 41 |
8 files changed, 435 insertions, 25 deletions
diff --git a/base/nss_init.cc b/base/nss_init.cc index c8ba44b..f43bf3f 100644 --- a/base/nss_init.cc +++ b/base/nss_init.cc @@ -9,31 +9,88 @@ // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=455424 // until NSS 3.12.2 comes out and we update to it. #define Lock FOO_NSS_Lock +#include <secmod.h> #include <ssl.h> #undef Lock +#include "base/file_util.h" #include "base/logging.h" #include "base/singleton.h" namespace { +// Load nss's built-in root certs. +// TODO(port): figure out a less hacky way to do this +const char *possible_locations[] = { + "libnssckbi.so", + "/usr/lib32/nss/libnssckbi.so", + "/usr/lib/nss/libnssckbi.so", + "/usr/lib32/libnssckbi.so", + "/usr/lib/libnssckbi.so", + NULL +}; +SECMODModule *InitDefaultRootCerts() { + int i; + for (i=0; possible_locations[i]; i++) { + if (possible_locations[i][0] == '/' && access(possible_locations[i], R_OK)) + continue; + char modparams[1024]; + snprintf(modparams, sizeof(modparams), + "name=\"Root Certs\" library=\"%s\"\n", possible_locations[i]); + SECMODModule *root = SECMOD_LoadUserModule(modparams, NULL, PR_FALSE); + if (root) + return root; + } + // Aw, snap. Can't find/load root cert shared library. + // This will make it hard to talk to anybody via https. + NOTREACHED(); + return NULL; +} + class NSSInitSingleton { public: NSSInitSingleton() { + + // Initialize without using a persistant database (e.g. ~/.netscape) CHECK(NSS_NoDB_Init(".") == SECSuccess); - // Enable ciphers + + root_ = InitDefaultRootCerts(); + NSS_SetDomesticPolicy(); + + // Explicitly enable exactly those ciphers with keys of at least 80 bits + for (int i = 0; i < SSL_NumImplementedCiphers; i++) { + SSLCipherSuiteInfo info; + if (SSL_GetCipherSuiteInfo(SSL_ImplementedCiphers[i], &info, + sizeof(info)) == SECSuccess) { + SSL_CipherPrefSetDefault(SSL_ImplementedCiphers[i], + (info.effectiveKeyBits >= 80)); + } + } + // Enable SSL SSL_OptionSetDefault(SSL_SECURITY, PR_TRUE); + + // All other SSL options are set per-session by SSLClientSocket } ~NSSInitSingleton() { + if (root_) { + SECMOD_UnloadUserModule(root_); + SECMOD_DestroyModule(root_); + root_ = NULL; + } + // Have to clear the cache, or NSS_Shutdown fails with SEC_ERROR_BUSY SSL_ClearSessionCache(); SECStatus status = NSS_Shutdown(); - DCHECK(status == SECSuccess); + if (status != SECSuccess) + LOG(ERROR) << "NSS_Shutdown failed, leak? See " + "http://code.google.com/p/chromium/issues/detail?id=4609"; } + private: + SECMODModule *root_; }; } // namespace diff --git a/base/nss_init.h b/base/nss_init.h index 181d292..ecfd0c6 100644 --- a/base/nss_init.h +++ b/base/nss_init.h @@ -5,6 +5,8 @@ #ifndef BASE_NSS_INIT_H_ #define BASE_NSS_INIT_H_ +#include <string> + namespace base { // Initialize NSS if it isn't already initialized. This must be called before diff --git a/net/base/ssl_client_socket_nss.cc b/net/base/ssl_client_socket_nss.cc index c954ec9..f2f977b 100644 --- a/net/base/ssl_client_socket_nss.cc +++ b/net/base/ssl_client_socket_nss.cc @@ -6,10 +6,12 @@ #include <nspr.h> #include <nss.h> +#include <secerr.h> // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=455424 // until NSS 3.12.2 comes out and we update to it. #define Lock FOO_NSS_Lock #include <ssl.h> +#include <sslerr.h> #include <pk11pub.h> #undef Lock @@ -21,18 +23,16 @@ static const int kRecvBufferSize = 4096; -/* - * nss calls this if an incoming certificate is invalid. - * TODO(port): expose to app via GetSSLInfo so it can put up - * the appropriate GUI and retry with override if desired - */ +// nss calls this if an incoming certificate is invalid. static SECStatus ownBadCertHandler(void * arg, PRFileDesc * socket) { PRErrorCode err = PR_GetError(); - LOG(ERROR) << "server certificate is invalid; NSS error code " << err; - // Return SECSuccess to override the problem, SECFailure to let the original function fail - return SECSuccess; /* override, say it's OK. */ + LOG(INFO) << "server certificate is invalid; NSS error code " << err; + // Return SECSuccess to override the problem, + // or SECFailure to let the original function fail + // Chromium wants it to fail here, and may retry it later. + return SECFailure; } @@ -44,6 +44,7 @@ namespace net { #define EnterFunction(x) #define LeaveFunction(x) #define GotoState(s) next_state_ = s +#define LogData(s, len) #else #define EnterFunction(x) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \ " enter " << x << "; next_state " << next_state_ @@ -51,8 +52,79 @@ namespace net { " leave " << x << "; next_state " << next_state_ #define GotoState(s) do { LOG(INFO) << (void *)this << " " << __FUNCTION__ << \ " jump to state " << s; next_state_ = s; } while (0) +#define LogData(s, len) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \ + " data [" << std::string(s, len) << "]"; + #endif +namespace { + +int NetErrorFromNSPRError(PRErrorCode err) { + // TODO(port): fill this out as we learn what's important + switch (err) { + case PR_WOULD_BLOCK_ERROR: + return ERR_IO_PENDING; + case SSL_ERROR_NO_CYPHER_OVERLAP: + return ERR_SSL_VERSION_OR_CIPHER_MISMATCH; + case SSL_ERROR_BAD_CERT_DOMAIN: + return ERR_CERT_COMMON_NAME_INVALID; + case SEC_ERROR_EXPIRED_CERTIFICATE: + return ERR_CERT_DATE_INVALID; + case SEC_ERROR_BAD_SIGNATURE: + return ERR_CERT_INVALID; + case SSL_ERROR_REVOKED_CERT_ALERT: + case SEC_ERROR_REVOKED_CERTIFICATE: + case SEC_ERROR_REVOKED_KEY: + return ERR_CERT_REVOKED; + case SEC_ERROR_UNKNOWN_ISSUER: + return ERR_CERT_AUTHORITY_INVALID; + + default: { + if (IS_SSL_ERROR(err)) { + LOG(WARNING) << "Unknown SSL error " << err << + " mapped to net::ERR_SSL_PROTOCOL_ERROR"; + return ERR_SSL_PROTOCOL_ERROR; + } + if (IS_SEC_ERROR(err)) { + // TODO(port): Probably not the best mapping + LOG(WARNING) << "Unknown SEC error " << err << + " mapped to net::ERR_CERT_INVALID"; + return ERR_CERT_INVALID; + } + LOG(WARNING) << "Unknown error " << err << + " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } + } +} + +// Shared with the Windows code. TODO(avi): merge to a common place +int CertStatusFromNetError(int error) { + switch (error) { + case ERR_CERT_COMMON_NAME_INVALID: + return CERT_STATUS_COMMON_NAME_INVALID; + case ERR_CERT_DATE_INVALID: + return CERT_STATUS_DATE_INVALID; + case ERR_CERT_AUTHORITY_INVALID: + return CERT_STATUS_AUTHORITY_INVALID; + case ERR_CERT_NO_REVOCATION_MECHANISM: + return CERT_STATUS_NO_REVOCATION_MECHANISM; + case ERR_CERT_UNABLE_TO_CHECK_REVOCATION: + return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + case ERR_CERT_REVOKED: + return CERT_STATUS_REVOKED; + case ERR_CERT_CONTAINS_ERRORS: + NOTREACHED(); + // Falls through. + case ERR_CERT_INVALID: + return CERT_STATUS_INVALID; + default: + return 0; + } +} + +} // namespace + bool SSLClientSocketNSS::nss_options_initialized_ = false; SSLClientSocketNSS::SSLClientSocketNSS(ClientSocket* transport_socket, @@ -70,6 +142,7 @@ SSLClientSocketNSS::SSLClientSocketNSS(ClientSocket* transport_socket, user_callback_(NULL), user_buf_(NULL), user_buf_len_(0), + server_cert_status_(0), completed_handshake_(false), next_state_(STATE_NONE), nss_fd_(NULL), @@ -148,7 +221,7 @@ int SSLClientSocketNSS::Read(char* buf, int buf_len, int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) user_callback_ = callback; - LeaveFunction(""); + LeaveFunction(rv); return rv; } @@ -167,14 +240,30 @@ int SSLClientSocketNSS::Write(const char* buf, int buf_len, int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) user_callback_ = callback; - LeaveFunction(""); + LeaveFunction(rv); return rv; } void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { EnterFunction(""); - // TODO(port): implement! ssl_info->Reset(); + SSLChannelInfo channel_info; + SECStatus ok = SSL_GetChannelInfo(nss_fd_, + &channel_info, sizeof(channel_info)); + if (ok == SECSuccess) { + SSLCipherSuiteInfo cipher_info; + ok = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, + &cipher_info, sizeof(cipher_info)); + if (ok == SECSuccess) { + ssl_info->security_bits = cipher_info.effectiveKeyBits; + } else { + ssl_info->security_bits = -1; + NOTREACHED(); + } + } + ssl_info->cert_status = server_cert_status_; + // TODO(port): implement X509Certificate so we can set the cert field! + // CERTCertificate *nssCert = SSL_PeerCertificate(nss_fd_); LeaveFunction(""); } @@ -378,14 +467,32 @@ int SSLClientSocketNSS::DoConnectComplete(int result) { if (rv != SECSuccess) return ERR_UNEXPECTED; + // V2 compatible hello means no SNI, which would cause errors like + // "common name `mail.google.com' != requested host name `gmail.com'" + // so don't do V2 compatible hellos unless we're really using SSL2. + rv = SSL_OptionSet(nss_fd_, SSL_V2_COMPATIBLE_HELLO, + ssl_config_.ssl2_enabled); + if (rv != SECSuccess) + return ERR_UNEXPECTED; + rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL3, ssl_config_.ssl3_enabled); if (rv != SECSuccess) return ERR_UNEXPECTED; - rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL3, ssl_config_.tls1_enabled); + rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_TLS, ssl_config_.tls1_enabled); if (rv != SECSuccess) return ERR_UNEXPECTED; +#ifdef SSL_ENABLE_SESSION_TICKETS + // Support RFC 5077, if using NSS 3.12 or later + rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_TRUE); + if (rv != SECSuccess) + LOG(INFO) << "SSL_ENABLE_SESSION_TICKETS failed. Old system nss?"; +#else + // TODO(port): drop build-time support for old NSS once we're all on NSS 3.12 + LOG(INFO) << "SSL_ENABLE_SESSION_TICKETS undefined. Old build system nss?"; +#endif + rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); if (rv != SECSuccess) return ERR_UNEXPECTED; @@ -407,31 +514,38 @@ int SSLClientSocketNSS::DoConnectComplete(int result) { int SSLClientSocketNSS::DoHandshakeRead() { EnterFunction(""); + int net_error; int rv = SSL_ForceHandshake(nss_fd_); + if (rv == SECSuccess) { + net_error = OK; // there's a callback for this, too completed_handshake_ = true; // Indicate we're ready to handle I/O. Badly named? GotoState(STATE_NONE); - LeaveFunction(""); - return OK; - } - PRErrorCode prerr = PR_GetError(); - if (prerr == PR_WOULD_BLOCK_ERROR) { - // at this point, it should have tried to send some bytes - GotoState(STATE_HANDSHAKE_READ); - LeaveFunction(""); - return ERR_IO_PENDING; + } else { + PRErrorCode prerr = PR_GetError(); + net_error = NetErrorFromNSPRError(prerr); + + // If not done, stay in this state + if (net_error == ERR_IO_PENDING) { + GotoState(STATE_HANDSHAKE_READ); + } else { + server_cert_status_ = CertStatusFromNetError(net_error); + LOG(ERROR) << "handshake failed; NSS error code " << prerr + << ", net_error " << net_error << ", server_cert_status " << server_cert_status_; + } } - // TODO: map rv to net error code properly + LeaveFunction(""); - return ERR_SSL_PROTOCOL_ERROR; + return net_error; } int SSLClientSocketNSS::DoPayloadRead() { EnterFunction(user_buf_len_); int rv = PR_Read(nss_fd_, user_buf_, user_buf_len_); if (rv >= 0) { + LogData(user_buf_, rv); user_buf_ = NULL; LeaveFunction(""); return rv; @@ -452,6 +566,7 @@ int SSLClientSocketNSS::DoPayloadWrite() { EnterFunction(user_buf_len_); int rv = PR_Write(nss_fd_, user_buf_, user_buf_len_); if (rv >= 0) { + LogData(user_buf_, rv); user_buf_ = NULL; LeaveFunction(""); return rv; diff --git a/net/base/ssl_client_socket_nss.h b/net/base/ssl_client_socket_nss.h index 41098f3..5015e1e 100644 --- a/net/base/ssl_client_socket_nss.h +++ b/net/base/ssl_client_socket_nss.h @@ -76,6 +76,8 @@ class SSLClientSocketNSS : public SSLClientSocket { char* user_buf_; int user_buf_len_; + int server_cert_status_; + bool completed_handshake_; enum State { diff --git a/net/base/ssl_test_util.cc b/net/base/ssl_test_util.cc new file mode 100644 index 0000000..3ce8f71 --- /dev/null +++ b/net/base/ssl_test_util.cc @@ -0,0 +1,145 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <algorithm> + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <shlobj.h> +#elif defined(OS_LINUX) + +#include <nspr.h> +#include <nss.h> +#include <secerr.h> +// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=455424 +// until NSS 3.12.2 comes out and we update to it. +#define Lock FOO_NSS_Lock +#include <ssl.h> +#include <sslerr.h> +#include <pk11pub.h> +#undef Lock +#include "base/nss_init.h" +#endif + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" + +#include "net/base/ssl_test_util.h" + +// static +const wchar_t SSLTestUtil::kDocRoot[] = L"chrome/test/data"; +const char SSLTestUtil::kHostName[] = "127.0.0.1"; +const int SSLTestUtil::kOKHTTPSPort = 9443; + +// The issuer name of the cert that should be trusted for the test to work. +const wchar_t SSLTestUtil::kCertIssuerName[] = L"Test CA"; + +#if defined(OS_LINUX) +static CERTCertificate* LoadTemporaryCert(const std::wstring& filename) { + // TODO(port) Move to net/base so we can use net error codes, + // and maybe make this a static method of ssl_client_socket? + + base::EnsureNSSInit(); + + std::string rawcert; + if (!file_util::ReadFileToString(filename, &rawcert)) { + LOG(ERROR) << "Can't load certificate " << filename; + return NULL; + } + + // TODO(port): remove these const_casts after NSS 3.12.3 is released + CERTCertificate *cert; + cert = CERT_DecodeCertFromPackage(const_cast<char *>(rawcert.c_str()), + rawcert.length()); + if (!cert) { + LOG(ERROR) << "Can't convert certificate " << filename; + return NULL; + } + + CERTCertTrust trust; + int rv = CERT_DecodeTrustString(&trust, const_cast<char *>("TCu,Cu,Tu")); + if (rv != SECSuccess) { + LOG(ERROR) << "Can't decode trust string"; + CERT_DestroyCertificate(cert); + return NULL; + } + + rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, &trust); + if (rv != SECSuccess) { + LOG(ERROR) << "Can't change trust for certificate " << filename; + CERT_DestroyCertificate(cert); + return NULL; + } + + LOG(INFO) << "Loaded temporary certificate " << filename; + return cert; +} +#endif + +SSLTestUtil::SSLTestUtil() { + PathService::Get(base::DIR_SOURCE_ROOT, &cert_dir_); + cert_dir_ += L"/chrome/test/data/ssl/certificates/"; + std::replace(cert_dir_.begin(), cert_dir_.end(), + L'/', file_util::kPathSeparator); + +#if defined(OS_LINUX) + cert_ = reinterpret_cast<PrivateCERTCertificate*>( + LoadTemporaryCert(GetRootCertPath())); + if (!cert_) + NOTREACHED(); +#endif + + CheckCATrusted(); +} + +SSLTestUtil::~SSLTestUtil() { +#if defined(OS_LINUX) + if (cert_) + CERT_DestroyCertificate(reinterpret_cast<CERTCertificate*>(cert_)); +#endif +} + +std::wstring SSLTestUtil::GetRootCertPath() { + std::wstring path(cert_dir_); + file_util::AppendToPath(&path, L"root_ca_cert.crt"); + return path; +} + +std::wstring SSLTestUtil::GetOKCertPath() { + std::wstring path(cert_dir_); + file_util::AppendToPath(&path, L"ok_cert.pem"); + return path; +} + +void SSLTestUtil::CheckCATrusted() { +// TODO(port): Port either this or LoadTemporaryCert to MacOSX. +#if defined(OS_WIN) + HCERTSTORE cert_store = CertOpenSystemStore(NULL, L"ROOT"); + if (!cert_store) { + FAIL() << " could not open trusted root CA store"; + return; + } + PCCERT_CONTEXT cert = + CertFindCertificateInStore(cert_store, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_ISSUER_STR, + kCertIssuerName, + NULL); + if (cert) + CertFreeCertificateContext(cert); + CertCloseStore(cert_store, 0); + + if (!cert) { + FAIL() << " TEST CONFIGURATION ERROR: you need to import the test ca " + "certificate to your trusted roots for this test to work. For more " + "info visit:\n" + "http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUnitUITests\n"; + } +#endif +} diff --git a/net/base/ssl_test_util.h b/net/base/ssl_test_util.h new file mode 100644 index 0000000..3502b9b --- /dev/null +++ b/net/base/ssl_test_util.h @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_SSL_TEST_UTIL_H_ +#define NET_BASE_SSL_TEST_UTIL_H_ + +#include "build/build_config.h" + +#include <string> + +#include "base/path_service.h" + +class SSLTestUtil { + public: + SSLTestUtil(); + + ~SSLTestUtil(); + + std::wstring GetRootCertPath(); + + std::wstring GetOKCertPath(); + + // Where test data is kept in source tree + static const wchar_t kDocRoot[]; + + // Hostname to use for test server + static const char kHostName[]; + + // Port to use for test server + static const int kOKHTTPSPort; + + // Issuer name of the cert that should be trusted for the test to work. + static const wchar_t kCertIssuerName[]; + + private: + void CheckCATrusted(); + + std::wstring cert_dir_; + +#if defined(OS_LINUX) + struct PrivateCERTCertificate; + PrivateCERTCertificate *cert_; +#endif +}; + +#endif diff --git a/net/net_unittests.scons b/net/net_unittests.scons index 0879dee..3535850 100644 --- a/net/net_unittests.scons +++ b/net/net_unittests.scons @@ -54,6 +54,7 @@ input_files = [ 'base/net_util_unittest.cc', 'base/registry_controlled_domain_unittest.cc', 'base/run_all_unittests.cc', + 'base/ssl_test_util.cc', 'base/ssl_client_socket_unittest.cc', 'base/tcp_client_socket_unittest.cc', 'base/telnet_server_unittest.cc', diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc index 05686e6..df7b98b 100644 --- a/net/url_request/url_request_unittest.cc +++ b/net/url_request/url_request_unittest.cc @@ -4,9 +4,13 @@ #include "net/url_request/url_request_unittest.h" +#include "build/build_config.h" + #if defined(OS_WIN) #include <windows.h> #include <shlobj.h> +#elif defined(OS_LINUX) +#include "base/nss_init.h" #endif #include <algorithm> @@ -18,10 +22,12 @@ #include "base/process_util.h" #include "base/string_piece.h" #include "base/string_util.h" +#include "base/trace_event.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/base/net_module.h" #include "net/base/net_util.h" +#include "net/base/ssl_test_util.h" #include "net/disk_cache/disk_cache.h" #include "net/http/http_cache.h" #include "net/http/http_network_layer.h" @@ -116,6 +122,41 @@ TEST_F(URLRequestTest, GetTest) { #endif } +class HTTPSRequestTest : public testing::Test { + protected: + SSLTestUtil util_; +}; + +#if defined(OS_MACOSX) +// TODO(port): support temporary root cert on mac +#define MAYBE_HTTPSGetTest DISABLED_HTTPSGetTest +#else +#define MAYBE_HTTPSGetTest HTTPSGetTest +#endif + +TEST_F(HTTPSRequestTest, MAYBE_HTTPSGetTest) { + base::TraceLog::StartTracing(); + HTTPSTestServer https_server(util_.kHostName, util_.kOKHTTPSPort, + util_.kDocRoot, util_.GetOKCertPath()); + + TestDelegate d; + { + TestURLRequest r(https_server.TestServerPage(""), &d); + + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + EXPECT_EQ(1, d.response_started_count()); + EXPECT_FALSE(d.received_data_before_response()); + EXPECT_NE(0, d.bytes_received()); + } +#ifndef NDEBUG + DCHECK_EQ(url_request_metrics.object_count,0); +#endif +} + TEST_F(URLRequestTest, CancelTest) { TestDelegate d; { |