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