Home | History | Annotate | Download | only in socket
      1 // Copyright (c) 2009 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/socket/ssl_test_util.h"
      6 
      7 #include <algorithm>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "build/build_config.h"
     12 
     13 #if defined(OS_WIN)
     14 #include <windows.h>
     15 #include <wincrypt.h>
     16 #elif defined(USE_NSS)
     17 #include <nspr.h>
     18 #include <nss.h>
     19 #include <secerr.h>
     20 #include <ssl.h>
     21 #include <sslerr.h>
     22 #include <pk11pub.h>
     23 #include "base/nss_util.h"
     24 #elif defined(OS_MACOSX)
     25 #include <Security/Security.h>
     26 #include "base/scoped_cftyperef.h"
     27 #include "net/base/x509_certificate.h"
     28 #endif
     29 
     30 #include "base/file_util.h"
     31 #include "base/logging.h"
     32 #include "base/path_service.h"
     33 #include "base/string_util.h"
     34 #include "net/base/host_resolver.h"
     35 #include "net/base/net_test_constants.h"
     36 #include "net/base/test_completion_callback.h"
     37 #include "net/socket/tcp_client_socket.h"
     38 #include "net/socket/tcp_pinger.h"
     39 #include "testing/platform_test.h"
     40 
     41 #if defined(OS_WIN)
     42 #pragma comment(lib, "crypt32.lib")
     43 #endif
     44 
     45 namespace {
     46 
     47 #if defined(USE_NSS)
     48 static CERTCertificate* LoadTemporaryCert(const FilePath& filename) {
     49   base::EnsureNSSInit();
     50 
     51   std::string rawcert;
     52   if (!file_util::ReadFileToString(filename, &rawcert)) {
     53     LOG(ERROR) << "Can't load certificate " << filename.value();
     54     return NULL;
     55   }
     56 
     57   CERTCertificate *cert;
     58   cert = CERT_DecodeCertFromPackage(const_cast<char *>(rawcert.c_str()),
     59                                     rawcert.length());
     60   if (!cert) {
     61     LOG(ERROR) << "Can't convert certificate " << filename.value();
     62     return NULL;
     63   }
     64 
     65   // TODO(port): remove this const_cast after NSS 3.12.3 is released
     66   CERTCertTrust trust;
     67   int rv = CERT_DecodeTrustString(&trust, const_cast<char *>("TCu,Cu,Tu"));
     68   if (rv != SECSuccess) {
     69     LOG(ERROR) << "Can't decode trust string";
     70     CERT_DestroyCertificate(cert);
     71     return NULL;
     72   }
     73 
     74   rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, &trust);
     75   if (rv != SECSuccess) {
     76     LOG(ERROR) << "Can't change trust for certificate " << filename.value();
     77     CERT_DestroyCertificate(cert);
     78     return NULL;
     79   }
     80 
     81   return cert;
     82 }
     83 #endif
     84 
     85 #if defined(OS_MACOSX)
     86 static net::X509Certificate* LoadTemporaryCert(const FilePath& filename) {
     87   std::string rawcert;
     88   if (!file_util::ReadFileToString(filename, &rawcert)) {
     89     LOG(ERROR) << "Can't load certificate " << filename.value();
     90     return NULL;
     91   }
     92 
     93   CFDataRef pem = CFDataCreate(kCFAllocatorDefault,
     94                                reinterpret_cast<const UInt8*>(rawcert.data()),
     95                                static_cast<CFIndex>(rawcert.size()));
     96   if (!pem)
     97     return NULL;
     98   scoped_cftyperef<CFDataRef> scoped_pem(pem);
     99 
    100   SecExternalFormat input_format = kSecFormatUnknown;
    101   SecExternalItemType item_type = kSecItemTypeUnknown;
    102   CFArrayRef cert_array = NULL;
    103   if (SecKeychainItemImport(pem, NULL, &input_format, &item_type, 0, NULL, NULL,
    104                             &cert_array))
    105     return NULL;
    106   scoped_cftyperef<CFArrayRef> scoped_cert_array(cert_array);
    107 
    108   if (!CFArrayGetCount(cert_array))
    109     return NULL;
    110 
    111   SecCertificateRef cert_ref = static_cast<SecCertificateRef>(
    112       const_cast<void*>(CFArrayGetValueAtIndex(cert_array, 0)));
    113   CFRetain(cert_ref);
    114   return net::X509Certificate::CreateFromHandle(cert_ref,
    115       net::X509Certificate::SOURCE_FROM_NETWORK);
    116 }
    117 #endif
    118 
    119 }  // namespace
    120 
    121 namespace net {
    122 
    123 #if defined(OS_MACOSX)
    124 void SetMacTestCertificate(X509Certificate* cert);
    125 #endif
    126 
    127 // static
    128 const char TestServerLauncher::kHostName[] = "127.0.0.1";
    129 const char TestServerLauncher::kMismatchedHostName[] = "localhost";
    130 const int TestServerLauncher::kOKHTTPSPort = 9443;
    131 const int TestServerLauncher::kBadHTTPSPort = 9666;
    132 
    133 // The issuer name of the cert that should be trusted for the test to work.
    134 const wchar_t TestServerLauncher::kCertIssuerName[] = L"Test CA";
    135 
    136 TestServerLauncher::TestServerLauncher() : process_handle_(
    137     base::kNullProcessHandle),
    138     forking_(false),
    139     connection_attempts_(kDefaultTestConnectionAttempts),
    140     connection_timeout_(kDefaultTestConnectionTimeout)
    141 #if defined(USE_NSS)
    142 , cert_(NULL)
    143 #endif
    144 {
    145   InitCertPath();
    146 }
    147 
    148 TestServerLauncher::TestServerLauncher(int connection_attempts,
    149                                        int connection_timeout)
    150                         : process_handle_(base::kNullProcessHandle),
    151                           forking_(false),
    152                           connection_attempts_(connection_attempts),
    153                           connection_timeout_(connection_timeout)
    154 #if defined(USE_NSS)
    155 , cert_(NULL)
    156 #endif
    157 {
    158   InitCertPath();
    159 }
    160 
    161 void TestServerLauncher::InitCertPath() {
    162   PathService::Get(base::DIR_SOURCE_ROOT, &cert_dir_);
    163   cert_dir_ = cert_dir_.Append(FILE_PATH_LITERAL("net"))
    164                        .Append(FILE_PATH_LITERAL("data"))
    165                        .Append(FILE_PATH_LITERAL("ssl"))
    166                        .Append(FILE_PATH_LITERAL("certificates"));
    167 }
    168 
    169 namespace {
    170 
    171 void AppendToPythonPath(const FilePath& dir) {
    172   // Do nothing if dir already on path.
    173 
    174 #if defined(OS_WIN)
    175   const wchar_t kPythonPath[] = L"PYTHONPATH";
    176   // FIXME(dkegel): handle longer PYTHONPATH variables
    177   wchar_t oldpath[4096];
    178   if (GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) {
    179     SetEnvironmentVariableW(kPythonPath, dir.value().c_str());
    180   } else if (!wcsstr(oldpath, dir.value().c_str())) {
    181     std::wstring newpath(oldpath);
    182     newpath.append(L":");
    183     newpath.append(dir.value());
    184     SetEnvironmentVariableW(kPythonPath, newpath.c_str());
    185   }
    186 #elif defined(OS_POSIX)
    187   const char kPythonPath[] = "PYTHONPATH";
    188   const char* oldpath = getenv(kPythonPath);
    189   // setenv() leaks memory intentionally on Mac
    190   if (!oldpath) {
    191     setenv(kPythonPath, dir.value().c_str(), 1);
    192   } else if (!strstr(oldpath, dir.value().c_str())) {
    193     std::string newpath(oldpath);
    194     newpath.append(":");
    195     newpath.append(dir.value());
    196     setenv(kPythonPath, newpath.c_str(), 1);
    197   }
    198 #endif
    199 }
    200 
    201 }  // end namespace
    202 
    203 void TestServerLauncher::SetPythonPath() {
    204   FilePath third_party_dir;
    205   CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir));
    206   third_party_dir = third_party_dir.Append(FILE_PATH_LITERAL("third_party"));
    207 
    208   AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("tlslite")));
    209   AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("pyftpdlib")));
    210 }
    211 
    212 bool TestServerLauncher::Start(Protocol protocol,
    213                                const std::string& host_name, int port,
    214                                const FilePath& document_root,
    215                                const FilePath& cert_path,
    216                                const std::wstring& file_root_url) {
    217   if (!cert_path.value().empty()) {
    218     if (!LoadTestRootCert())
    219       return false;
    220     if (!CheckCATrusted())
    221       return false;
    222   }
    223 
    224   std::string port_str = IntToString(port);
    225 
    226   // Get path to python server script
    227   FilePath testserver_path;
    228   if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path))
    229     return false;
    230   testserver_path = testserver_path
    231       .Append(FILE_PATH_LITERAL("net"))
    232       .Append(FILE_PATH_LITERAL("tools"))
    233       .Append(FILE_PATH_LITERAL("testserver"))
    234       .Append(FILE_PATH_LITERAL("testserver.py"));
    235 
    236   PathService::Get(base::DIR_SOURCE_ROOT, &document_root_dir_);
    237   document_root_dir_ = document_root_dir_.Append(document_root);
    238 
    239   SetPythonPath();
    240 
    241 #if defined(OS_WIN)
    242   // Get path to python interpreter
    243   if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_runtime_))
    244     return false;
    245   python_runtime_ = python_runtime_
    246       .Append(FILE_PATH_LITERAL("third_party"))
    247       .Append(FILE_PATH_LITERAL("python_24"))
    248       .Append(FILE_PATH_LITERAL("python.exe"));
    249 
    250   std::wstring command_line =
    251       L"\"" + python_runtime_.ToWStringHack() + L"\" " +
    252       L"\"" + testserver_path.ToWStringHack() +
    253       L"\" --port=" + UTF8ToWide(port_str) +
    254       L" --data-dir=\"" + document_root_dir_.ToWStringHack() + L"\"";
    255   if (protocol == ProtoFTP)
    256     command_line.append(L" -f");
    257   if (!cert_path.value().empty()) {
    258     command_line.append(L" --https=\"");
    259     command_line.append(cert_path.ToWStringHack());
    260     command_line.append(L"\"");
    261   }
    262   if (!file_root_url.empty()) {
    263     command_line.append(L" --file-root-url=\"");
    264     command_line.append(file_root_url);
    265     command_line.append(L"\"");
    266   }
    267   // Deliberately do not pass the --forking flag. It breaks the tests
    268   // on Windows.
    269 
    270   if (!base::LaunchApp(command_line, false, true, &process_handle_)) {
    271     LOG(ERROR) << "Failed to launch " << command_line;
    272     return false;
    273   }
    274 #elif defined(OS_POSIX)
    275   std::vector<std::string> command_line;
    276   command_line.push_back("python");
    277   command_line.push_back(testserver_path.value());
    278   command_line.push_back("--port=" + port_str);
    279   command_line.push_back("--data-dir=" + document_root_dir_.value());
    280   if (protocol == ProtoFTP)
    281     command_line.push_back("-f");
    282   if (!cert_path.value().empty())
    283     command_line.push_back("--https=" + cert_path.value());
    284   if (forking_)
    285     command_line.push_back("--forking");
    286 
    287   base::file_handle_mapping_vector no_mappings;
    288   LOG(INFO) << "Trying to launch " << command_line[0] << " ...";
    289   if (!base::LaunchApp(command_line, no_mappings, false, &process_handle_)) {
    290     LOG(ERROR) << "Failed to launch " << command_line[0] << " ...";
    291     return false;
    292   }
    293 #endif
    294 
    295   // Let the server start, then verify that it's up.
    296   // Our server is Python, and takes about 500ms to start
    297   // up the first time, and about 200ms after that.
    298   if (!WaitToStart(host_name, port)) {
    299     LOG(ERROR) << "Failed to connect to server";
    300     Stop();
    301     return false;
    302   }
    303 
    304   LOG(INFO) << "Started on port " << port_str;
    305   return true;
    306 }
    307 
    308 bool TestServerLauncher::WaitToStart(const std::string& host_name, int port) {
    309   // Verify that the webserver is actually started.
    310   // Otherwise tests can fail if they run faster than Python can start.
    311   net::AddressList addr;
    312   scoped_refptr<net::HostResolver> resolver(
    313       net::CreateSystemHostResolver(NULL));
    314   net::HostResolver::RequestInfo info(host_name, port);
    315   int rv = resolver->Resolve(info, &addr, NULL, NULL, NULL);
    316   if (rv != net::OK)
    317     return false;
    318 
    319   net::TCPPinger pinger(addr);
    320   rv = pinger.Ping(base::TimeDelta::FromMilliseconds(connection_timeout_),
    321                    connection_attempts_);
    322   return rv == net::OK;
    323 }
    324 
    325 bool TestServerLauncher::WaitToFinish(int timeout_ms) {
    326   if (!process_handle_)
    327     return true;
    328 
    329   bool ret = base::WaitForSingleProcess(process_handle_, timeout_ms);
    330   if (ret) {
    331     base::CloseProcessHandle(process_handle_);
    332     process_handle_ = base::kNullProcessHandle;
    333     LOG(INFO) << "Finished.";
    334   } else {
    335     LOG(INFO) << "Timed out.";
    336   }
    337   return ret;
    338 }
    339 
    340 bool TestServerLauncher::Stop() {
    341   if (!process_handle_)
    342     return true;
    343 
    344   bool ret = base::KillProcess(process_handle_, 1, true);
    345   if (ret) {
    346     base::CloseProcessHandle(process_handle_);
    347     process_handle_ = base::kNullProcessHandle;
    348     LOG(INFO) << "Stopped.";
    349   } else {
    350     LOG(INFO) << "Kill failed?";
    351   }
    352 
    353   return ret;
    354 }
    355 
    356 TestServerLauncher::~TestServerLauncher() {
    357 #if defined(USE_NSS)
    358   if (cert_)
    359     CERT_DestroyCertificate(reinterpret_cast<CERTCertificate*>(cert_));
    360 #elif defined(OS_MACOSX)
    361   SetMacTestCertificate(NULL);
    362 #endif
    363   Stop();
    364 }
    365 
    366 FilePath TestServerLauncher::GetRootCertPath() {
    367   FilePath path(cert_dir_);
    368   path = path.AppendASCII("root_ca_cert.crt");
    369   return path;
    370 }
    371 
    372 FilePath TestServerLauncher::GetOKCertPath() {
    373   FilePath path(cert_dir_);
    374   path = path.AppendASCII("ok_cert.pem");
    375   return path;
    376 }
    377 
    378 FilePath TestServerLauncher::GetExpiredCertPath() {
    379   FilePath path(cert_dir_);
    380   path = path.AppendASCII("expired_cert.pem");
    381   return path;
    382 }
    383 
    384 bool TestServerLauncher::LoadTestRootCert() {
    385 #if defined(USE_NSS)
    386   if (cert_)
    387     return true;
    388 
    389   // TODO(dkegel): figure out how to get this to only happen once?
    390 
    391   // This currently leaks a little memory.
    392   // TODO(dkegel): fix the leak and remove the entry in
    393   // tools/valgrind/suppressions.txt
    394   cert_ = reinterpret_cast<PrivateCERTCertificate*>(
    395           LoadTemporaryCert(GetRootCertPath()));
    396   DCHECK(cert_);
    397   return (cert_ != NULL);
    398 #elif defined(OS_MACOSX)
    399   X509Certificate* cert = LoadTemporaryCert(GetRootCertPath());
    400   if (!cert)
    401     return false;
    402   SetMacTestCertificate(cert);
    403   return true;
    404 #else
    405   return true;
    406 #endif
    407 }
    408 
    409 bool TestServerLauncher::CheckCATrusted() {
    410 // TODO(port): Port either this or LoadTemporaryCert to MacOSX.
    411 #if defined(OS_WIN)
    412   HCERTSTORE cert_store = CertOpenSystemStore(NULL, L"ROOT");
    413   if (!cert_store) {
    414     LOG(ERROR) << " could not open trusted root CA store";
    415     return false;
    416   }
    417   PCCERT_CONTEXT cert =
    418       CertFindCertificateInStore(cert_store,
    419                                  X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
    420                                  0,
    421                                  CERT_FIND_ISSUER_STR,
    422                                  kCertIssuerName,
    423                                  NULL);
    424   if (cert)
    425     CertFreeCertificateContext(cert);
    426   CertCloseStore(cert_store, 0);
    427 
    428   if (!cert) {
    429     LOG(ERROR) << " TEST CONFIGURATION ERROR: you need to import the test ca "
    430                   "certificate to your trusted roots for this test to work. "
    431                   "For more info visit:\n"
    432                   "http://dev.chromium.org/developers/testing\n";
    433     return false;
    434   }
    435 #endif
    436   return true;
    437 }
    438 
    439 }  // namespace net
    440