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/. */
22 #include "mozilla/Sprintf.h"
37 static const uint16_t LISTEN_PORT
= 8443;
39 SSLAntiReplayContext
* antiReplay
= nullptr;
41 DebugLevel gDebugLevel
= DEBUG_ERRORS
;
42 uint16_t gCallbackPort
= 0;
44 static const char kPEMBegin
[] = "-----BEGIN ";
45 static const char kPEMEnd
[] = "-----END ";
46 const char DEFAULT_CERT_NICKNAME
[] = "default-ee";
52 explicit Connection(PRFileDesc
* aSocket
);
56 Connection::Connection(PRFileDesc
* aSocket
) : mSocket(aSocket
), mByte(0) {}
58 Connection::~Connection() {
64 void PrintPRError(const char* aPrefix
) {
65 const char* err
= PR_ErrorToName(PR_GetError());
67 if (gDebugLevel
>= DEBUG_ERRORS
) {
68 fprintf(stderr
, "%s: %s\n", aPrefix
, err
);
71 if (gDebugLevel
>= DEBUG_ERRORS
) {
72 fprintf(stderr
, "%s\n", aPrefix
);
77 // This decodes a PEM file into `item`. The line endings need to be
78 // UNIX-style, or there will be cross-platform issues.
79 static bool DecodePEMFile(const std::string
& filename
, SECItem
* item
) {
80 std::ifstream
in(filename
);
86 in
.getline(buf
, sizeof(buf
));
91 if (strncmp(buf
, kPEMBegin
, std::string::traits_type::length(kPEMBegin
)) !=
98 in
.getline(buf
, sizeof(buf
));
103 if (strncmp(buf
, kPEMEnd
, std::string::traits_type::length(kPEMEnd
)) == 0) {
110 unsigned int binLength
;
111 UniquePORTString
bin(BitwiseCast
<char*, unsigned char*>(
112 ATOB_AsciiToData(value
.c_str(), &binLength
)));
113 if (!bin
|| binLength
== 0) {
114 PrintPRError("ATOB_AsciiToData failed");
118 if (SECITEM_AllocItem(nullptr, item
, binLength
) == nullptr) {
122 PORT_Memcpy(item
->data
, bin
.get(), binLength
);
126 static SECStatus
AddKeyFromFile(const std::string
& path
,
127 const std::string
& filename
) {
128 ScopedAutoSECItem item
;
130 std::string file
= path
+ "/" + filename
;
131 if (!DecodePEMFile(file
, &item
)) {
135 UniquePK11SlotInfo
slot(PK11_GetInternalKeySlot());
137 PrintPRError("PK11_GetInternalKeySlot failed");
141 if (PK11_NeedUserInit(slot
.get())) {
142 if (PK11_InitPin(slot
.get(), nullptr, nullptr) != SECSuccess
) {
143 PrintPRError("PK11_InitPin failed");
148 SECKEYPrivateKey
* privateKey
= nullptr;
149 SECItem nick
= {siBuffer
,
150 BitwiseCast
<unsigned char*, const char*>(filename
.data()),
151 static_cast<unsigned int>(filename
.size())};
152 if (PK11_ImportDERPrivateKeyInfoAndReturnKey(
153 slot
.get(), &item
, &nick
, nullptr, true, false, KU_ALL
, &privateKey
,
154 nullptr) != SECSuccess
) {
155 PrintPRError("PK11_ImportDERPrivateKeyInfoAndReturnKey failed");
159 SECKEY_DestroyPrivateKey(privateKey
);
163 static SECStatus
AddCertificateFromFile(const std::string
& path
,
164 const std::string
& filename
) {
165 ScopedAutoSECItem item
;
167 std::string file
= path
+ "/" + filename
;
168 if (!DecodePEMFile(file
, &item
)) {
172 UniqueCERTCertificate
cert(CERT_NewTempCertificate(
173 CERT_GetDefaultCertDB(), &item
, nullptr, false, true));
175 PrintPRError("CERT_NewTempCertificate failed");
179 UniquePK11SlotInfo
slot(PK11_GetInternalKeySlot());
181 PrintPRError("PK11_GetInternalKeySlot failed");
184 // The nickname is the filename without '.pem'.
185 std::string nickname
= filename
.substr(0, filename
.length() - 4);
186 SECStatus rv
= PK11_ImportCert(slot
.get(), cert
.get(), CK_INVALID_HANDLE
,
187 nickname
.c_str(), false);
188 if (rv
!= SECSuccess
) {
189 PrintPRError("PK11_ImportCert failed");
196 SECStatus
LoadCertificatesAndKeys(const char* basePath
) {
197 // The NSS cert DB path could have been specified as "sql:path". Trim off
198 // the leading "sql:" if so.
199 if (strncmp(basePath
, "sql:", 4) == 0) {
200 basePath
= basePath
+ 4;
203 UniquePRDir
fdDir(PR_OpenDir(basePath
));
205 PrintPRError("PR_OpenDir failed");
208 // On the B2G ICS emulator, operations taken in AddCertificateFromFile
209 // appear to interact poorly with readdir (more specifically, something is
210 // causing readdir to never return null - it indefinitely loops through every
211 // file in the directory, which causes timeouts). Rather than waste more time
212 // chasing this down, loading certificates and keys happens in two phases:
213 // filename collection and then loading. (This is probably a good
214 // idea anyway because readdir isn't reentrant. Something could change later
215 // such that it gets called as a result of calling AddCertificateFromFile or
217 std::vector
<std::string
> certificates
;
218 std::vector
<std::string
> keys
;
219 for (PRDirEntry
* dirEntry
= PR_ReadDir(fdDir
.get(), PR_SKIP_BOTH
); dirEntry
;
220 dirEntry
= PR_ReadDir(fdDir
.get(), PR_SKIP_BOTH
)) {
221 size_t nameLength
= strlen(dirEntry
->name
);
222 if (nameLength
> 4) {
223 if (strncmp(dirEntry
->name
+ nameLength
- 4, ".pem", 4) == 0) {
224 certificates
.push_back(dirEntry
->name
);
225 } else if (strncmp(dirEntry
->name
+ nameLength
- 4, ".key", 4) == 0) {
226 keys
.push_back(dirEntry
->name
);
231 for (std::string
& certificate
: certificates
) {
232 rv
= AddCertificateFromFile(basePath
, certificate
.c_str());
233 if (rv
!= SECSuccess
) {
237 for (std::string
& key
: keys
) {
238 rv
= AddKeyFromFile(basePath
, key
.c_str());
239 if (rv
!= SECSuccess
) {
246 SECStatus
InitializeNSS(const char* nssCertDBDir
) {
247 // Try initializing an existing DB.
248 if (NSS_Init(nssCertDBDir
) == SECSuccess
) {
252 // Create a new DB if there is none...
253 SECStatus rv
= NSS_Initialize(nssCertDBDir
, nullptr, nullptr, nullptr, 0);
254 if (rv
!= SECSuccess
) {
258 // ...and load all certificates into it.
259 return LoadCertificatesAndKeys(nssCertDBDir
);
262 nsresult
SendAll(PRFileDesc
* aSocket
, const char* aData
, size_t aDataLen
) {
263 if (gDebugLevel
>= DEBUG_VERBOSE
) {
264 fprintf(stderr
, "sending '%s'\n", aData
);
267 while (aDataLen
> 0) {
269 PR_Send(aSocket
, aData
, aDataLen
, 0, PR_INTERVAL_NO_TIMEOUT
);
270 if (bytesSent
== -1) {
271 PrintPRError("PR_Send failed");
272 return NS_ERROR_FAILURE
;
275 aDataLen
-= bytesSent
;
282 nsresult
ReplyToRequest(Connection
* aConn
) {
283 // For debugging purposes, SendAll can print out what it's sending.
284 // So, any strings we give to it to send need to be null-terminated.
285 char buf
[2] = {aConn
->mByte
, 0};
286 return SendAll(aConn
->mSocket
, buf
, 1);
289 nsresult
SetupTLS(Connection
* aConn
, PRFileDesc
* aModelSocket
) {
290 PRFileDesc
* sslSocket
= SSL_ImportFD(aModelSocket
, aConn
->mSocket
);
292 PrintPRError("SSL_ImportFD failed");
293 return NS_ERROR_FAILURE
;
295 aConn
->mSocket
= sslSocket
;
297 /* anti-replay must be configured to accept 0RTT */
299 SECStatus rv
= SSL_SetAntiReplayContext(sslSocket
, antiReplay
);
300 if (rv
!= SECSuccess
) {
301 PrintPRError("error configuring anti-replay ");
302 return NS_ERROR_FAILURE
;
306 SSL_OptionSet(sslSocket
, SSL_SECURITY
, true);
307 SSL_OptionSet(sslSocket
, SSL_HANDSHAKE_AS_CLIENT
, false);
308 SSL_OptionSet(sslSocket
, SSL_HANDSHAKE_AS_SERVER
, true);
309 // Unconditionally enabling 0RTT makes test_session_resumption.js fail
310 SSL_OptionSet(sslSocket
, SSL_ENABLE_0RTT_DATA
,
311 !!PR_GetEnv("MOZ_TLS_SERVER_0RTT"));
313 SSL_ResetHandshake(sslSocket
, /* asServer */ 1);
318 nsresult
ReadRequest(Connection
* aConn
) {
320 PR_Recv(aConn
->mSocket
, &aConn
->mByte
, 1, 0, PR_INTERVAL_NO_TIMEOUT
);
322 PrintPRError("PR_Recv failed");
323 return NS_ERROR_FAILURE
;
324 } else if (bytesRead
== 0) {
325 PR_SetError(PR_IO_ERROR
, 0);
326 PrintPRError("PR_Recv EOF in ReadRequest");
327 return NS_ERROR_FAILURE
;
329 if (gDebugLevel
>= DEBUG_VERBOSE
) {
330 fprintf(stderr
, "read '0x%hhx'\n", aConn
->mByte
);
336 void HandleConnection(PRFileDesc
* aSocket
,
337 const UniquePRFileDesc
& aModelSocket
) {
338 Connection
conn(aSocket
);
339 nsresult rv
= SetupTLS(&conn
, aModelSocket
.get());
341 PR_SetError(PR_INVALID_STATE_ERROR
, 0);
342 PrintPRError("PR_Recv failed");
346 // TODO: On tests that are expected to fail (e.g. due to a revoked
347 // certificate), the client will close the connection wtihout sending us the
348 // request byte. In those cases, we should keep going. But, in the cases
349 // where the connection is supposed to suceed, we should verify that we
350 // successfully receive the request and send the response.
351 rv
= ReadRequest(&conn
);
352 if (NS_SUCCEEDED(rv
)) {
353 rv
= ReplyToRequest(&conn
);
357 // returns 0 on success, non-zero on error
359 UniquePRFileDesc
socket(PR_NewTCPSocket());
361 PrintPRError("PR_NewTCPSocket failed");
366 PR_InitializeNetAddr(PR_IpAddrLoopback
, gCallbackPort
, &addr
);
367 if (PR_Connect(socket
.get(), &addr
, PR_INTERVAL_NO_TIMEOUT
) != PR_SUCCESS
) {
368 PrintPRError("PR_Connect failed");
372 const char* request
= "GET / HTTP/1.0\r\n\r\n";
373 SendAll(socket
.get(), request
, strlen(request
));
375 memset(buf
, 0, sizeof(buf
));
377 PR_Recv(socket
.get(), buf
, sizeof(buf
) - 1, 0, PR_INTERVAL_NO_TIMEOUT
);
379 PrintPRError("PR_Recv failed 1");
382 if (bytesRead
== 0) {
383 fprintf(stderr
, "PR_Recv eof 1\n");
386 fprintf(stderr
, "%s\n", buf
);
390 SECStatus
ConfigSecureServerWithNamedCert(
391 PRFileDesc
* fd
, const char* certName
,
392 /*optional*/ UniqueCERTCertificate
* certOut
,
393 /*optional*/ SSLKEAType
* keaOut
,
394 /*optional*/ SSLExtraServerCertData
* extraData
) {
395 UniqueCERTCertificate
cert(PK11_FindCertFromNickname(certName
, nullptr));
397 PrintPRError("PK11_FindCertFromNickname failed");
400 // If an intermediate certificate issued the server certificate (rather than
401 // directly by a trust anchor), we want to send it along in the handshake so
402 // we don't encounter unknown issuer errors when that's not what we're
404 UniqueCERTCertificateList certList
;
405 UniqueCERTCertificate
issuerCert(
406 CERT_FindCertByName(CERT_GetDefaultCertDB(), &cert
->derIssuer
));
407 // If we can't find the issuer cert, continue without it.
409 // Sadly, CERTCertificateList does not have a CERT_NewCertificateList
410 // utility function, so we must create it ourselves. This consists
411 // of creating an arena, allocating space for the CERTCertificateList,
412 // and then transferring ownership of the arena to that list.
413 UniquePLArenaPool
arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE
));
415 PrintPRError("PORT_NewArena failed");
418 certList
.reset(static_cast<CERTCertificateList
*>(
419 PORT_ArenaAlloc(arena
.get(), sizeof(CERTCertificateList
))));
421 PrintPRError("PORT_ArenaAlloc failed");
424 certList
->arena
= arena
.release();
425 // We also have to manually copy the certificates we care about to the
426 // list, because there aren't any utility functions for that either.
427 certList
->certs
= static_cast<SECItem
*>(
428 PORT_ArenaAlloc(certList
->arena
, 2 * sizeof(SECItem
)));
429 if (SECITEM_CopyItem(certList
->arena
, certList
->certs
, &cert
->derCert
) !=
431 PrintPRError("SECITEM_CopyItem failed");
434 if (SECITEM_CopyItem(certList
->arena
, certList
->certs
+ 1,
435 &issuerCert
->derCert
) != SECSuccess
) {
436 PrintPRError("SECITEM_CopyItem failed");
442 UniquePK11SlotInfo
slot(PK11_GetInternalKeySlot());
444 PrintPRError("PK11_GetInternalKeySlot failed");
447 UniqueSECKEYPrivateKey
key(
448 PK11_FindKeyByDERCert(slot
.get(), cert
.get(), nullptr));
450 PrintPRError("PK11_FindKeyByDERCert failed");
455 SSLExtraServerCertData dataCopy
= {ssl_auth_null
, nullptr, nullptr,
456 nullptr, nullptr, nullptr};
457 memcpy(&dataCopy
, extraData
, sizeof(dataCopy
));
458 dataCopy
.certChain
= certList
.get();
460 if (SSL_ConfigServerCert(fd
, cert
.get(), key
.get(), &dataCopy
,
461 sizeof(dataCopy
)) != SECSuccess
) {
462 PrintPRError("SSL_ConfigServerCert failed");
467 // This is the deprecated setup mechanism, to be cleaned up in Bug 1569222
468 SSLKEAType certKEA
= NSS_FindCertKEAType(cert
.get());
469 if (SSL_ConfigSecureServerWithCertChain(fd
, cert
.get(), certList
.get(),
470 key
.get(), certKEA
) != SECSuccess
) {
471 PrintPRError("SSL_ConfigSecureServer failed");
481 *certOut
= std::move(cert
);
484 SSL_OptionSet(fd
, SSL_NO_CACHE
, false);
485 SSL_OptionSet(fd
, SSL_ENABLE_SESSION_TICKETS
, true);
486 // Unconditionally enabling 0RTT makes test_session_resumption.js fail
487 SSL_OptionSet(fd
, SSL_ENABLE_0RTT_DATA
, !!PR_GetEnv("MOZ_TLS_SERVER_0RTT"));
493 using PidType
= DWORD
;
494 constexpr bool IsValidPid(long long pid
) {
495 // Excluding `(DWORD)-1` because it is not a valid process ID.
496 // See https://devblogs.microsoft.com/oldnewthing/20040223-00/?p=40503
497 return pid
> 0 && pid
< std::numeric_limits
<PidType
>::max();
500 using PidType
= pid_t
;
501 constexpr bool IsValidPid(long long pid
) {
502 return pid
> 0 && pid
<= std::numeric_limits
<PidType
>::max();
506 PidType
ConvertPid(const char* pidStr
) {
507 long long pid
= strtoll(pidStr
, nullptr, 10);
508 if (!IsValidPid(pid
)) {
511 return static_cast<PidType
>(pid
);
514 int StartServer(int argc
, char* argv
[], SSLSNISocketConfig sniSocketConfig
,
515 void* sniSocketConfigArg
, ServerConfigFunc configFunc
) {
517 fprintf(stderr
, "usage: %s <NSS DB directory> <ppid>\n", argv
[0]);
520 const char* nssCertDBDir
= argv
[1];
521 PidType ppid
= ConvertPid(argv
[2]);
523 const char* debugLevel
= PR_GetEnv("MOZ_TLS_SERVER_DEBUG_LEVEL");
525 int level
= atoi(debugLevel
);
528 gDebugLevel
= DEBUG_ERRORS
;
531 gDebugLevel
= DEBUG_WARNINGS
;
534 gDebugLevel
= DEBUG_VERBOSE
;
537 PrintPRError("invalid MOZ_TLS_SERVER_DEBUG_LEVEL");
542 const char* callbackPort
= PR_GetEnv("MOZ_TLS_SERVER_CALLBACK_PORT");
544 gCallbackPort
= atoi(callbackPort
);
547 if (InitializeNSS(nssCertDBDir
) != SECSuccess
) {
548 PR_fprintf(PR_STDERR
, "InitializeNSS failed");
552 if (NSS_SetDomesticPolicy() != SECSuccess
) {
553 PrintPRError("NSS_SetDomesticPolicy failed");
557 if (SSL_ConfigServerSessionIDCache(0, 0, 0, nullptr) != SECSuccess
) {
558 PrintPRError("SSL_ConfigServerSessionIDCache failed");
562 UniquePRFileDesc
serverSocket(PR_NewTCPSocket());
564 PrintPRError("PR_NewTCPSocket failed");
568 PRSocketOptionData socketOption
;
569 socketOption
.option
= PR_SockOpt_Reuseaddr
;
570 socketOption
.value
.reuse_addr
= true;
571 PR_SetSocketOption(serverSocket
.get(), &socketOption
);
573 PRNetAddr serverAddr
;
574 PR_InitializeNetAddr(PR_IpAddrLoopback
, LISTEN_PORT
, &serverAddr
);
575 if (PR_Bind(serverSocket
.get(), &serverAddr
) != PR_SUCCESS
) {
576 PrintPRError("PR_Bind failed");
580 if (PR_Listen(serverSocket
.get(), 1) != PR_SUCCESS
) {
581 PrintPRError("PR_Listen failed");
585 UniquePRFileDesc
rawModelSocket(PR_NewTCPSocket());
586 if (!rawModelSocket
) {
587 PrintPRError("PR_NewTCPSocket failed for rawModelSocket");
591 UniquePRFileDesc
modelSocket(SSL_ImportFD(nullptr, rawModelSocket
.release()));
593 PrintPRError("SSL_ImportFD of rawModelSocket failed");
597 SSLVersionRange range
= {0, 0};
598 if (SSL_VersionRangeGet(modelSocket
.get(), &range
) != SECSuccess
) {
599 PrintPRError("SSL_VersionRangeGet failed");
603 if (range
.max
< SSL_LIBRARY_VERSION_TLS_1_3
) {
604 range
.max
= SSL_LIBRARY_VERSION_TLS_1_3
;
605 if (SSL_VersionRangeSet(modelSocket
.get(), &range
) != SECSuccess
) {
606 PrintPRError("SSL_VersionRangeSet failed");
611 if (PR_GetEnv("MOZ_TLS_SERVER_0RTT")) {
612 if (SSL_CreateAntiReplayContext(PR_Now(), 1L * PR_USEC_PER_SEC
, 7, 14,
613 &antiReplay
) != SECSuccess
) {
614 PrintPRError("Unable to create anti-replay context for 0-RTT.");
619 if (SSL_SNISocketConfigHook(modelSocket
.get(), sniSocketConfig
,
620 sniSocketConfigArg
) != SECSuccess
) {
621 PrintPRError("SSL_SNISocketConfigHook failed");
625 // We have to configure the server with a certificate, but it's not one
626 // we're actually going to end up using. In the SNI callback, we pick
627 // the right certificate for the connection.
629 // Provide an empty |extra_data| to force config via SSL_ConfigServerCert.
630 // This is a temporary mechanism to work around inconsistent setting of
631 // |authType| in the deprecated API (preventing the default cert from
632 // being removed in favor of the SNI-selected cert). This may be removed
633 // after Bug 1569222 removes the deprecated mechanism.
634 SSLExtraServerCertData extra_data
= {ssl_auth_null
, nullptr, nullptr,
635 nullptr, nullptr, nullptr};
636 if (ConfigSecureServerWithNamedCert(modelSocket
.get(), DEFAULT_CERT_NICKNAME
,
638 &extra_data
) != SECSuccess
) {
642 // Call back to implementation-defined configuration func, if provided.
644 if (((configFunc
)(modelSocket
.get())) != SECSuccess
) {
645 PrintPRError("configFunc failed");
650 if (gCallbackPort
!= 0) {
658 if (gDebugLevel
>= DEBUG_ERRORS
) {
659 fprintf(stderr
, "invalid ppid\n");
664 HANDLE parent
= OpenProcess(SYNCHRONIZE
, false, ppid
);
666 if (gDebugLevel
>= DEBUG_ERRORS
) {
667 fprintf(stderr
, "OpenProcess failed\n");
671 WaitForSingleObject(parent
, INFINITE
);
674 while (getppid() == ppid
) {
678 if (gDebugLevel
>= DEBUG_ERRORS
) {
679 fprintf(stderr
, "Parent process crashed\n");
685 PRNetAddr clientAddr
;
686 PRFileDesc
* clientSocket
=
687 PR_Accept(serverSocket
.get(), &clientAddr
, PR_INTERVAL_NO_TIMEOUT
);
688 HandleConnection(clientSocket
, modelSocket
);
693 } // namespace mozilla