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