Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/security/manager/ssl/tests/unit/tlsserver/lib/./../../../../../../../../security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp
Warning:line 372, column 5
Value stored to 'rv' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_unit_tlsserver_lib0.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/security/manager/ssl/tests/unit/tlsserver/lib -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/security/manager/ssl/tests/unit/tlsserver/lib -resource-dir /usr/lib/llvm-23/lib/clang/23 -include /root/firefox-clang/config/gcc_hidden.h -include /root/firefox-clang/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -D _GLIBCXX_ASSERTIONS=1 -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D NSS_USE_STATIC_LIBS -I /root/firefox-clang/security/manager/ssl/tests/unit/tlsserver/lib -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/security/manager/ssl/tests/unit/tlsserver/lib -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-23/lib/clang/23/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-error=pessimizing-move -Wno-error=large-by-value-copy=128 -Wno-error=implicit-int-float-conversion -Wno-error=thread-safety-analysis -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -Wno-character-conversion -std=gnu++20 -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -fdiagnostics-absolute-paths -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -mllvm -dwarf-linkage-names=Abstract -faddrsig -fdwarf2-cfi-asm -o /tmp/scan-build-2026-09-01-224014-2642839-1 -x c++ Unified_cpp_unit_tlsserver_lib0.cpp
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#include "TLSServer.h"
6
7#include <stdio.h>
8
9#include <fstream>
10#include <string>
11#include <thread>
12#include <vector>
13#ifdef XP_WIN
14# include <windows.h>
15#else
16# include <unistd.h>
17#endif
18
19#include <utility>
20
21#include "base64.h"
22#include "certdb.h"
23#include "mozilla/Sprintf.h"
24#include "nspr.h"
25#include "nss.h"
26#include "plarenas.h"
27#include "prenv.h"
28#include "prerror.h"
29#include "prnetdb.h"
30#include "prtime.h"
31#include "ssl.h"
32#include "sslexp.h"
33#include "sslproto.h"
34
35namespace mozilla {
36namespace test {
37
38static const uint16_t LISTEN_PORT = 8443;
39
40SSLAntiReplayContext* antiReplay = nullptr;
41
42SSLAntiReplayContext* GetAntiReplayContext() { return antiReplay; }
43
44DebugLevel gDebugLevel = DEBUG_ERRORS;
45uint16_t gCallbackPort = 0;
46
47static const char kPEMBegin[] = "-----BEGIN ";
48static const char kPEMEnd[] = "-----END ";
49const char DEFAULT_CERT_NICKNAME[] = "default-ee";
50
51Connection::Connection(PRFileDesc* aSocket) : mSocket(aSocket), mByte(0) {}
52
53Connection::~Connection() {
54 if (mSocket) {
55 PR_Close(mSocket);
56 }
57}
58
59void PrintPRError(const char* aPrefix) {
60 const char* err = PR_ErrorToName(PR_GetError());
61 if (err) {
62 if (gDebugLevel >= DEBUG_ERRORS) {
63 fprintf(stderrstderr, "%s: %s\n", aPrefix, err);
64 }
65 } else {
66 if (gDebugLevel >= DEBUG_ERRORS) {
67 fprintf(stderrstderr, "%s\n", aPrefix);
68 }
69 }
70}
71
72// This decodes a PEM file into `item`. The line endings need to be
73// UNIX-style, or there will be cross-platform issues.
74static bool DecodePEMFile(const std::string& filename, SECItem* item) {
75 std::ifstream in(filename);
76 if (in.bad()) {
77 return false;
78 }
79
80 char buf[1024];
81 in.getline(buf, sizeof(buf));
82 if (in.bad()) {
83 return false;
84 }
85
86 if (strncmp(buf, kPEMBegin, std::string::traits_type::length(kPEMBegin)) !=
87 0) {
88 return false;
89 }
90
91 std::string value;
92 for (;;) {
93 in.getline(buf, sizeof(buf));
94 if (in.bad()) {
95 return false;
96 }
97
98 if (strncmp(buf, kPEMEnd, std::string::traits_type::length(kPEMEnd)) == 0) {
99 break;
100 }
101
102 value += buf;
103 }
104
105 unsigned int binLength;
106 UniquePORTString bin(BitwiseCast<char*, unsigned char*>(
107 ATOB_AsciiToData(value.c_str(), &binLength)));
108 if (!bin || binLength == 0) {
109 PrintPRError("ATOB_AsciiToData failed");
110 return false;
111 }
112
113 if (SECITEM_AllocItem(nullptr, item, binLength) == nullptr) {
114 return false;
115 }
116
117 PORT_Memcpymemcpy(item->data, bin.get(), binLength);
118 return true;
119}
120
121static SECStatus AddKeyFromFile(const std::string& path,
122 const std::string& filename) {
123 ScopedAutoSECItem item;
124
125 std::string file = path + "/" + filename;
126 if (!DecodePEMFile(file, &item)) {
127 return SECFailure;
128 }
129
130 UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
131 if (!slot) {
132 PrintPRError("PK11_GetInternalKeySlot failed");
133 return SECFailure;
134 }
135
136 if (PK11_NeedUserInit(slot.get())) {
137 if (PK11_InitPin(slot.get(), nullptr, nullptr) != SECSuccess) {
138 PrintPRError("PK11_InitPin failed");
139 return SECFailure;
140 }
141 }
142
143 SECKEYPrivateKey* privateKey = nullptr;
144 SECItem nick = {siBuffer,
145 BitwiseCast<unsigned char*, const char*>(filename.data()),
146 static_cast<unsigned int>(filename.size())};
147 if (PK11_ImportDERPrivateKeyInfoAndReturnKey(
148 slot.get(), &item, &nick, nullptr, true, false, KU_ALL((0x80) | (0x40) | (0x20) | (0x10) | (0x08) | (0x04) | (0x02)
| (0x01))
, &privateKey,
149 nullptr) != SECSuccess) {
150 PrintPRError("PK11_ImportDERPrivateKeyInfoAndReturnKey failed");
151 return SECFailure;
152 }
153
154 SECKEY_DestroyPrivateKey(privateKey);
155 return SECSuccess;
156}
157
158static SECStatus AddCertificateFromFile(const std::string& path,
159 const std::string& filename) {
160 ScopedAutoSECItem item;
161
162 std::string file = path + "/" + filename;
163 if (!DecodePEMFile(file, &item)) {
164 return SECFailure;
165 }
166
167 UniqueCERTCertificate cert(CERT_NewTempCertificate(
168 CERT_GetDefaultCertDB(), &item, nullptr, false, true));
169 if (!cert) {
170 PrintPRError("CERT_NewTempCertificate failed");
171 return SECFailure;
172 }
173
174 UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
175 if (!slot) {
176 PrintPRError("PK11_GetInternalKeySlot failed");
177 return SECFailure;
178 }
179 // The nickname is the filename without '.pem'.
180 std::string nickname = filename.substr(0, filename.length() - 4);
181 SECStatus rv = PK11_ImportCert(slot.get(), cert.get(), CK_INVALID_HANDLE0,
182 nickname.c_str(), false);
183 if (rv != SECSuccess) {
184 PrintPRError("PK11_ImportCert failed");
185 return rv;
186 }
187
188 // By convention, the file `test-ca.pem` is a trust anchor.
189 if (!filename.compare("test-ca.pem")) {
190 if (PK11_NeedUserInit(slot.get())) {
191 rv = PK11_InitPin(slot.get(), nullptr, nullptr);
192 if (rv != SECSuccess) {
193 PrintPRError("PK11_InitPin failed");
194 return rv;
195 }
196 }
197 rv = PK11_CheckUserPassword(slot.get(), "");
198 if (rv != SECSuccess) {
199 PrintPRError("PK11_CheckUserPassword failed");
200 return rv;
201 }
202 CERTCertTrust trust{
203 CERTDB_TERMINAL_RECORD(1u << 0) | CERTDB_TRUSTED_CA(1u << 4) | CERTDB_TRUSTED_CLIENT_CA(1u << 7),
204 0, 0};
205 rv = CERT_ChangeCertTrust(nullptr, cert.get(), &trust);
206 if (rv != SECSuccess) {
207 PrintPRError("CERT_ChangeCertTrust failed");
208 return rv;
209 }
210 }
211
212 return SECSuccess;
213}
214
215SECStatus LoadCertificatesAndKeys(const char* basePath) {
216 // The NSS cert DB path could have been specified as "sql:path". Trim off
217 // the leading "sql:" if so.
218 if (strncmp(basePath, "sql:", 4) == 0) {
219 basePath = basePath + 4;
220 }
221
222 UniquePRDir fdDir(PR_OpenDir(basePath));
223 if (!fdDir) {
224 PrintPRError("PR_OpenDir failed");
225 return SECFailure;
226 }
227 // On the B2G ICS emulator, operations taken in AddCertificateFromFile
228 // appear to interact poorly with readdir (more specifically, something is
229 // causing readdir to never return null - it indefinitely loops through every
230 // file in the directory, which causes timeouts). Rather than waste more time
231 // chasing this down, loading certificates and keys happens in two phases:
232 // filename collection and then loading. (This is probably a good
233 // idea anyway because readdir isn't reentrant. Something could change later
234 // such that it gets called as a result of calling AddCertificateFromFile or
235 // AddKeyFromFile.)
236 std::vector<std::string> certificates;
237 std::vector<std::string> keys;
238 for (PRDirEntry* dirEntry = PR_ReadDir(fdDir.get(), PR_SKIP_BOTH); dirEntry;
239 dirEntry = PR_ReadDir(fdDir.get(), PR_SKIP_BOTH)) {
240 size_t nameLength = strlen(dirEntry->name);
241 if (nameLength > 4) {
242 if (strncmp(dirEntry->name + nameLength - 4, ".pem", 4) == 0) {
243 certificates.push_back(dirEntry->name);
244 } else if (strncmp(dirEntry->name + nameLength - 4, ".key", 4) == 0) {
245 keys.push_back(dirEntry->name);
246 }
247 }
248 }
249 SECStatus rv;
250 for (std::string& certificate : certificates) {
251 rv = AddCertificateFromFile(basePath, certificate.c_str());
252 if (rv != SECSuccess) {
253 return rv;
254 }
255 }
256 for (std::string& key : keys) {
257 rv = AddKeyFromFile(basePath, key.c_str());
258 if (rv != SECSuccess) {
259 return rv;
260 }
261 }
262 return SECSuccess;
263}
264
265SECStatus InitializeNSS(const char* nssCertDBDir) {
266 // Try initializing an existing DB.
267 if (NSS_Init(nssCertDBDir) == SECSuccess) {
268 return SECSuccess;
269 }
270
271 // Create a new DB if there is none...
272 SECStatus rv = NSS_Initialize(nssCertDBDir, nullptr, nullptr, nullptr, 0);
273 if (rv != SECSuccess) {
274 return rv;
275 }
276
277 // ...and load all certificates into it.
278 return LoadCertificatesAndKeys(nssCertDBDir);
279}
280
281nsresult SendAll(PRFileDesc* aSocket, const char* aData, size_t aDataLen) {
282 if (gDebugLevel >= DEBUG_VERBOSE) {
283 fprintf(stderrstderr, "sending '%s'\n", aData);
284 }
285
286 while (aDataLen > 0) {
287 int32_t bytesSent =
288 PR_Send(aSocket, aData, aDataLen, 0, PR_INTERVAL_NO_TIMEOUT0xffffffffUL);
289 if (bytesSent == -1) {
290 PrintPRError("PR_Send failed");
291 return NS_ERROR_FAILURE;
292 }
293
294 aDataLen -= bytesSent;
295 aData += bytesSent;
296 }
297
298 return NS_OK;
299}
300
301nsresult ReplyToRequest(Connection* aConn) {
302 // For debugging purposes, SendAll can print out what it's sending.
303 // So, any strings we give to it to send need to be null-terminated.
304 char buf[2] = {aConn->mByte, 0};
305 return SendAll(aConn->mSocket, buf, 1);
306}
307
308nsresult SetupTLS(Connection* aConn, PRFileDesc* aModelSocket) {
309 PRFileDesc* sslSocket = SSL_ImportFD(aModelSocket, aConn->mSocket);
310 if (!sslSocket) {
311 PrintPRError("SSL_ImportFD failed");
312 return NS_ERROR_FAILURE;
313 }
314 aConn->mSocket = sslSocket;
315
316 /* anti-replay must be configured to accept 0RTT */
317 if (antiReplay) {
318 SECStatus rv = SSL_SetAntiReplayContext(sslSocket, antiReplay)(SSL_GetExperimentalAPI("SSL_SetAntiReplayContext") ? ((SECStatus
(*) (PRFileDesc * _fd, SSLAntiReplayContext * _ctx))SSL_GetExperimentalAPI
("SSL_SetAntiReplayContext"))(sslSocket, antiReplay) : SECFailure
)
;
319 if (rv != SECSuccess) {
320 PrintPRError("error configuring anti-replay ");
321 return NS_ERROR_FAILURE;
322 }
323 }
324
325 SSL_OptionSet(sslSocket, SSL_SECURITY1, true);
326 SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT5, false);
327 SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_SERVER6, true);
328 // Unconditionally enabling 0RTT makes test_session_resumption.js fail
329 SSL_OptionSet(sslSocket, SSL_ENABLE_0RTT_DATA33,
330 !!PR_GetEnv("MOZ_TLS_SERVER_0RTT"));
331
332 SSL_ResetHandshake(sslSocket, /* asServer */ 1);
333
334 return NS_OK;
335}
336
337nsresult ReadRequest(Connection* aConn) {
338 int32_t bytesRead =
339 PR_Recv(aConn->mSocket, &aConn->mByte, 1, 0, PR_INTERVAL_NO_TIMEOUT0xffffffffUL);
340 if (bytesRead < 0) {
341 PrintPRError("PR_Recv failed");
342 return NS_ERROR_FAILURE;
343 } else if (bytesRead == 0) {
344 PR_SetError(PR_IO_ERROR(-5991L), 0);
345 PrintPRError("PR_Recv EOF in ReadRequest");
346 return NS_ERROR_FAILURE;
347 } else {
348 if (gDebugLevel >= DEBUG_VERBOSE) {
349 fprintf(stderrstderr, "read '0x%hhx'\n", aConn->mByte);
350 }
351 }
352 return NS_OK;
353}
354
355void HandleConnection(PRFileDesc* aSocket,
356 const UniquePRFileDesc& aModelSocket) {
357 Connection conn(aSocket);
358 nsresult rv = SetupTLS(&conn, aModelSocket.get());
359 if (NS_FAILED(rv)((bool)(__builtin_expect(!!(NS_FAILED_impl(rv)), 0)))) {
360 PR_SetError(PR_INVALID_STATE_ERROR(-5931L), 0);
361 PrintPRError("PR_Recv failed");
362 exit(1);
363 }
364
365 // TODO: On tests that are expected to fail (e.g. due to a revoked
366 // certificate), the client will close the connection wtihout sending us the
367 // request byte. In those cases, we should keep going. But, in the cases
368 // where the connection is supposed to suceed, we should verify that we
369 // successfully receive the request and send the response.
370 rv = ReadRequest(&conn);
371 if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1)))) {
372 rv = ReplyToRequest(&conn);
Value stored to 'rv' is never read
373 }
374}
375
376// returns 0 on success, non-zero on error
377int DoCallback() {
378 UniquePRFileDesc socket(PR_NewTCPSocket());
379 if (!socket) {
380 PrintPRError("PR_NewTCPSocket failed");
381 return 1;
382 }
383
384 PRNetAddr addr;
385 PR_InitializeNetAddr(PR_IpAddrLoopback, gCallbackPort, &addr);
386 if (PR_Connect(socket.get(), &addr, PR_INTERVAL_NO_TIMEOUT0xffffffffUL) != PR_SUCCESS) {
387 PrintPRError("PR_Connect failed");
388 return 1;
389 }
390
391 const char* request = "GET / HTTP/1.0\r\n\r\n";
392 SendAll(socket.get(), request, strlen(request));
393 char buf[4096];
394 memset(buf, 0, sizeof(buf));
395 int32_t bytesRead =
396 PR_Recv(socket.get(), buf, sizeof(buf) - 1, 0, PR_INTERVAL_NO_TIMEOUT0xffffffffUL);
397 if (bytesRead < 0) {
398 PrintPRError("PR_Recv failed 1");
399 return 1;
400 }
401 if (bytesRead == 0) {
402 fprintf(stderrstderr, "PR_Recv eof 1\n");
403 return 1;
404 }
405 fprintf(stderrstderr, "%s\n", buf);
406 return 0;
407}
408
409SECStatus ConfigSecureServerWithNamedCert(
410 PRFileDesc* fd, const char* certName,
411 /*optional*/ UniqueCERTCertificate* certOut,
412 /*optional*/ SSLKEAType* keaOut,
413 /*optional*/ SSLExtraServerCertData* extraData) {
414 UniqueCERTCertificate cert(PK11_FindCertFromNickname(certName, nullptr));
415 if (!cert) {
416 PrintPRError("PK11_FindCertFromNickname failed");
417 return SECFailure;
418 }
419 // If an intermediate certificate issued the server certificate (rather than
420 // directly by a trust anchor), we want to send it along in the handshake so
421 // we don't encounter unknown issuer errors when that's not what we're
422 // testing.
423 UniqueCERTCertificateList certList;
424 UniqueCERTCertificate issuerCert(
425 CERT_FindCertByName(CERT_GetDefaultCertDB(), &cert->derIssuer));
426 // If we can't find the issuer cert, continue without it.
427 if (issuerCert) {
428 // Sadly, CERTCertificateList does not have a CERT_NewCertificateList
429 // utility function, so we must create it ourselves. This consists
430 // of creating an arena, allocating space for the CERTCertificateList,
431 // and then transferring ownership of the arena to that list.
432 UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE(2048)));
433 if (!arena) {
434 PrintPRError("PORT_NewArena failed");
435 return SECFailure;
436 }
437 certList.reset(static_cast<CERTCertificateList*>(
438 PORT_ArenaAlloc(arena.get(), sizeof(CERTCertificateList))));
439 if (!certList) {
440 PrintPRError("PORT_ArenaAlloc failed");
441 return SECFailure;
442 }
443 certList->arena = arena.release();
444 // We also have to manually copy the certificates we care about to the
445 // list, because there aren't any utility functions for that either.
446 certList->certs = static_cast<SECItem*>(
447 PORT_ArenaAlloc(certList->arena, 2 * sizeof(SECItem)));
448 if (SECITEM_CopyItem(certList->arena, certList->certs, &cert->derCert) !=
449 SECSuccess) {
450 PrintPRError("SECITEM_CopyItem failed");
451 return SECFailure;
452 }
453 if (SECITEM_CopyItem(certList->arena, certList->certs + 1,
454 &issuerCert->derCert) != SECSuccess) {
455 PrintPRError("SECITEM_CopyItem failed");
456 return SECFailure;
457 }
458 certList->len = 2;
459 }
460
461 UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
462 if (!slot) {
463 PrintPRError("PK11_GetInternalKeySlot failed");
464 return SECFailure;
465 }
466 UniqueSECKEYPrivateKey key(
467 PK11_FindKeyByDERCert(slot.get(), cert.get(), nullptr));
468 if (!key) {
469 PrintPRError("PK11_FindKeyByDERCert failed");
470 return SECFailure;
471 }
472
473 // SSL_ConfigServerCert is the preferred way to configure the server
474 // certificate, but it is too strict for the inadequate key usage tests.
475 SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
476 if (cert->keyUsage & KU_DIGITAL_SIGNATURE(0x80)) {
477 SSLExtraServerCertData dataCopy = {ssl_auth_null, nullptr, nullptr,
478 nullptr, nullptr, nullptr};
479 if (extraData) {
480 memcpy(&dataCopy, extraData, sizeof(dataCopy));
481 }
482 dataCopy.certChain = certList.get();
483
484 if (SSL_ConfigServerCert(fd, cert.get(), key.get(), &dataCopy,
485 sizeof(dataCopy)) != SECSuccess) {
486 PrintPRError("SSL_ConfigServerCert failed");
487 return SECFailure;
488 }
489 } else {
490 if (SSL_ConfigSecureServerWithCertChain(fd, cert.get(), certList.get(),
491 key.get(), certKEA) != SECSuccess) {
492 PrintPRError("SSL_ConfigSecureServerWithCertChain failed");
493 return SECFailure;
494 }
495 }
496
497 if (keaOut) {
498 *keaOut = certKEA;
499 }
500
501 if (certOut) {
502 *certOut = std::move(cert);
503 }
504
505 SSL_OptionSet(fd, SSL_NO_CACHE9, false);
506 SSL_OptionSet(fd, SSL_ENABLE_SESSION_TICKETS18, true);
507 // Unconditionally enabling 0RTT makes test_session_resumption.js fail
508 SSL_OptionSet(fd, SSL_ENABLE_0RTT_DATA33, !!PR_GetEnv("MOZ_TLS_SERVER_0RTT"));
509
510 return SECSuccess;
511}
512
513#ifdef XP_WIN
514using PidType = DWORD;
515constexpr bool IsValidPid(long long pid) {
516 // Excluding `(DWORD)-1` because it is not a valid process ID.
517 // See https://devblogs.microsoft.com/oldnewthing/20040223-00/?p=40503
518 return pid > 0 && pid < std::numeric_limits<PidType>::max();
519}
520#else
521using PidType = pid_t;
522constexpr bool IsValidPid(long long pid) {
523 return pid > 0 && pid <= std::numeric_limits<PidType>::max();
524}
525#endif
526
527PidType ConvertPid(const char* pidStr) {
528 long long pid = strtoll(pidStr, nullptr, 10);
529 if (!IsValidPid(pid)) {
530 return 0;
531 }
532 return static_cast<PidType>(pid);
533}
534
535int StartServer(int argc, char* argv[], SSLSNISocketConfig sniSocketConfig,
536 void* sniSocketConfigArg, ServerConfigFunc configFunc,
537 ConnectionHandlerFunc connectionHandler) {
538 if (argc != 3) {
539 fprintf(stderrstderr, "usage: %s <NSS DB directory> <ppid>\n", argv[0]);
540 return 1;
541 }
542 const char* nssCertDBDir = argv[1];
543 PidType ppid = ConvertPid(argv[2]);
544
545 const char* debugLevel = PR_GetEnv("MOZ_TLS_SERVER_DEBUG_LEVEL");
546 if (debugLevel) {
547 int level = atoi(debugLevel);
548 switch (level) {
549 case DEBUG_ERRORS:
550 gDebugLevel = DEBUG_ERRORS;
551 break;
552 case DEBUG_WARNINGS:
553 gDebugLevel = DEBUG_WARNINGS;
554 break;
555 case DEBUG_VERBOSE:
556 gDebugLevel = DEBUG_VERBOSE;
557 break;
558 default:
559 PrintPRError("invalid MOZ_TLS_SERVER_DEBUG_LEVEL");
560 return 1;
561 }
562 }
563
564 const char* callbackPort = PR_GetEnv("MOZ_TLS_SERVER_CALLBACK_PORT");
565 if (callbackPort) {
566 gCallbackPort = atoi(callbackPort);
567 }
568
569 if (InitializeNSS(nssCertDBDir) != SECSuccess) {
570 PR_fprintf(PR_STDERRPR_GetSpecialFD(PR_StandardError), "InitializeNSS failed");
571 return 1;
572 }
573
574 if (NSS_SetDomesticPolicy() != SECSuccess) {
575 PrintPRError("NSS_SetDomesticPolicy failed");
576 return 1;
577 }
578
579 /* Disabling NSS_KEY_SIZE_POLICY as we operate with short keys. */
580 NSS_OptionSet(NSS_KEY_SIZE_POLICY_FLAGS0x00e, 0);
581
582 if (SSL_ConfigServerSessionIDCache(0, 0, 0, nullptr) != SECSuccess) {
583 PrintPRError("SSL_ConfigServerSessionIDCache failed");
584 return 1;
585 }
586
587 UniquePRFileDesc serverSocket(PR_NewTCPSocket());
588 if (!serverSocket) {
589 PrintPRError("PR_NewTCPSocket failed");
590 return 1;
591 }
592
593 PRSocketOptionData socketOption;
594 socketOption.option = PR_SockOpt_Reuseaddr;
595 socketOption.value.reuse_addr = true;
596 PR_SetSocketOption(serverSocket.get(), &socketOption);
597
598 PRNetAddr serverAddr;
599 PR_InitializeNetAddr(PR_IpAddrLoopback, LISTEN_PORT, &serverAddr);
600 if (PR_Bind(serverSocket.get(), &serverAddr) != PR_SUCCESS) {
601 PrintPRError("PR_Bind failed");
602 return 1;
603 }
604
605 // Backlog of 1 is enough for tests that strictly open one conn at a
606 // time, but the HE 0-RTT race tests drive several overlapping TCP
607 // connects via a reverse proxy — with backlog=1 macOS silently
608 // drops the extras and the proxy retransmits the SYN, arriving
609 // after the server has already moved its TLS state forward on the
610 // accepted conn. A modest backlog is enough to absorb the race.
611 if (PR_Listen(serverSocket.get(), 32) != PR_SUCCESS) {
612 PrintPRError("PR_Listen failed");
613 return 1;
614 }
615
616 UniquePRFileDesc rawModelSocket(PR_NewTCPSocket());
617 if (!rawModelSocket) {
618 PrintPRError("PR_NewTCPSocket failed for rawModelSocket");
619 return 1;
620 }
621
622 UniquePRFileDesc modelSocket(SSL_ImportFD(nullptr, rawModelSocket.release()));
623 if (!modelSocket) {
624 PrintPRError("SSL_ImportFD of rawModelSocket failed");
625 return 1;
626 }
627
628 SSLVersionRange range = {0, 0};
629 if (SSL_VersionRangeGet(modelSocket.get(), &range) != SECSuccess) {
630 PrintPRError("SSL_VersionRangeGet failed");
631 return 1;
632 }
633
634 if (range.max < SSL_LIBRARY_VERSION_TLS_1_30x0304) {
635 range.max = SSL_LIBRARY_VERSION_TLS_1_30x0304;
636 if (SSL_VersionRangeSet(modelSocket.get(), &range) != SECSuccess) {
637 PrintPRError("SSL_VersionRangeSet failed");
638 return 1;
639 }
640 }
641
642 if (PR_GetEnv("MOZ_TLS_SERVER_0RTT")) {
643 if (SSL_CreateAntiReplayContext(PR_Now(), 1L * PR_USEC_PER_SEC, 7, 14,(SSL_GetExperimentalAPI("SSL_CreateAntiReplayContext") ? ((SECStatus
(*) (PRTime _now, PRTime _window, unsigned int _k, unsigned int
_bits, SSLAntiReplayContext **_ctx))SSL_GetExperimentalAPI("SSL_CreateAntiReplayContext"
))(PR_Now(), 1L * 1000000L, 7, 14, &antiReplay) : SECFailure
)
644 &antiReplay)(SSL_GetExperimentalAPI("SSL_CreateAntiReplayContext") ? ((SECStatus
(*) (PRTime _now, PRTime _window, unsigned int _k, unsigned int
_bits, SSLAntiReplayContext **_ctx))SSL_GetExperimentalAPI("SSL_CreateAntiReplayContext"
))(PR_Now(), 1L * 1000000L, 7, 14, &antiReplay) : SECFailure
)
!= SECSuccess) {
645 PrintPRError("Unable to create anti-replay context for 0-RTT.");
646 return 1;
647 }
648 }
649
650 if (SSL_SNISocketConfigHook(modelSocket.get(), sniSocketConfig,
651 sniSocketConfigArg) != SECSuccess) {
652 PrintPRError("SSL_SNISocketConfigHook failed");
653 return 1;
654 }
655
656 // We have to configure the server with a certificate, but it's not one
657 // we're actually going to end up using. In the SNI callback, we pick
658 // the right certificate for the connection.
659 //
660 // Provide an empty |extra_data| to force config via SSL_ConfigServerCert.
661 // This is a temporary mechanism to work around inconsistent setting of
662 // |authType| in the deprecated API (preventing the default cert from
663 // being removed in favor of the SNI-selected cert). This may be removed
664 // after Bug 1569222 removes the deprecated mechanism.
665 SSLExtraServerCertData extra_data = {ssl_auth_null, nullptr, nullptr,
666 nullptr, nullptr, nullptr};
667 if (ConfigSecureServerWithNamedCert(modelSocket.get(), DEFAULT_CERT_NICKNAME,
668 nullptr, nullptr,
669 &extra_data) != SECSuccess) {
670 return 1;
671 }
672
673 // Call back to implementation-defined configuration func, if provided.
674 if (configFunc) {
675 if (((configFunc)(modelSocket.get())) != SECSuccess) {
676 PrintPRError("configFunc failed");
677 return 1;
678 }
679 }
680
681 if (gCallbackPort != 0) {
682 if (DoCallback()) {
683 return 1;
684 }
685 }
686
687 std::thread([ppid] {
688 if (!ppid) {
689 if (gDebugLevel >= DEBUG_ERRORS) {
690 fprintf(stderrstderr, "invalid ppid\n");
691 }
692 return;
693 }
694#ifdef XP_WIN
695 HANDLE parent = OpenProcess(SYNCHRONIZE, false, ppid);
696 if (!parent) {
697 if (gDebugLevel >= DEBUG_ERRORS) {
698 fprintf(stderrstderr, "OpenProcess failed\n");
699 }
700 return;
701 }
702 WaitForSingleObject(parent, INFINITE);
703 CloseHandle(parent);
704#else
705 while (getppid() == ppid) {
706 sleep(1);
707 }
708#endif
709 if (gDebugLevel >= DEBUG_ERRORS) {
710 fprintf(stderrstderr, "Parent process crashed\n");
711 }
712 exit(1);
713 }).detach();
714
715 while (true) {
716 PRNetAddr clientAddr;
717 PRFileDesc* clientSocket =
718 PR_Accept(serverSocket.get(), &clientAddr, PR_INTERVAL_NO_TIMEOUT0xffffffffUL);
719 if (connectionHandler) {
720 connectionHandler(clientSocket, modelSocket);
721 } else {
722 HandleConnection(clientSocket, modelSocket);
723 }
724 }
725}
726
727} // namespace test
728} // namespace mozilla