0

Point nss at root certs so test_shell can talk to mail.google.com without warnings.

(gmail.com's certificate is for mail.google.com, which doesn't match gmail.com,
so on some distros, test_shell will now refuse to talk with gmail.com.)

Support ciphers needed to talk to testserver.py.

Load temporary testing cert needed to run unit tests (can't
do it manually like on Windows, since we don't use a writable cert database in the filesystem.)

Implement part of GetSSLInfo.

Re-enable url_request_unittest.cc, which seems to have been removed from the list of files to compile by mistake.

Addresses part of http://code.google.com/p/chromium/issues/detail?id=4510

Later changesets will implement x509 certificates for nss,
finish GetSSLInfo support, and update chrome/browser/ssl_uitest.cc
to use SSLTestUtil.

Review URL: http://codereview.chromium.org/11249

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@6063 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
dank@chromium.org
2008-11-26 22:21:17 +00:00
parent e5f659d04a
commit ab63eccc1f
8 changed files with 435 additions and 25 deletions

@ -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

@ -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

@ -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;
} 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_;
}
}
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;
}
// 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;

@ -76,6 +76,8 @@ class SSLClientSocketNSS : public SSLClientSocket {
char* user_buf_;
int user_buf_len_;
int server_cert_status_;
bool completed_handshake_;
enum State {

145
net/base/ssl_test_util.cc Normal file

@ -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
}

47
net/base/ssl_test_util.h Normal file

@ -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

@ -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',

@ -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;
{