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
45namespace {
46
47#if defined(USE_NSS)
48static 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)
86static 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
121namespace net {
122
123#if defined(OS_MACOSX)
124void SetMacTestCertificate(X509Certificate* cert);
125#endif
126
127// static
128const char TestServerLauncher::kHostName[] = "127.0.0.1";
129const char TestServerLauncher::kMismatchedHostName[] = "localhost";
130const int TestServerLauncher::kOKHTTPSPort = 9443;
131const int TestServerLauncher::kBadHTTPSPort = 9666;
132
133// The issuer name of the cert that should be trusted for the test to work.
134const wchar_t TestServerLauncher::kCertIssuerName[] = L"Test CA";
135
136TestServerLauncher::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
148TestServerLauncher::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
161void 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
169namespace {
170
171void 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
203void 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
212bool 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
308bool 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
325bool 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
340bool 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
356TestServerLauncher::~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
366FilePath TestServerLauncher::GetRootCertPath() {
367  FilePath path(cert_dir_);
368  path = path.AppendASCII("root_ca_cert.crt");
369  return path;
370}
371
372FilePath TestServerLauncher::GetOKCertPath() {
373  FilePath path(cert_dir_);
374  path = path.AppendASCII("ok_cert.pem");
375  return path;
376}
377
378FilePath TestServerLauncher::GetExpiredCertPath() {
379  FilePath path(cert_dir_);
380  path = path.AppendASCII("expired_cert.pem");
381  return path;
382}
383
384bool 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
409bool 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