summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/tlsserver
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /security/manager/ssl/tests/unit/tlsserver
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/ssl/tests/unit/tlsserver')
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/BadCertAndPinningServer.cpp141
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/DelegatedCredentialsServer.cpp142
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/EncryptedClientHelloServer.cpp178
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/FaultyServer.cpp206
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp168
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp153
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/SanctionsTestServer.cpp87
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/moz.build45
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/default-ee.der3
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp204
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h66
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp694
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.h93
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/moz.build50
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/moz.build8
15 files changed, 2238 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertAndPinningServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertAndPinningServer.cpp
new file mode 100644
index 0000000000..1ccd5e876b
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertAndPinningServer.cpp
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a standalone server that uses various bad certificates.
+// The client is expected to connect, initiate an SSL handshake (with SNI
+// to indicate which "server" to connect to), and verify the certificate.
+// If all is good, the client then sends one encrypted byte and receives that
+// same byte back.
+// This server also has the ability to "call back" another process waiting on
+// it. That is, when the server is all set up and ready to receive connections,
+// it will connect to a specified port and issue a simple HTTP request.
+
+#include <stdio.h>
+
+#include "TLSServer.h"
+
+using namespace mozilla;
+using namespace mozilla::test;
+
+struct BadCertAndPinningHost {
+ const char* mHostName;
+ const char* mCertName;
+};
+
+// Hostname, cert nickname pairs.
+const BadCertAndPinningHost sBadCertAndPinningHosts[] = {
+ {"expired.example.com", "expired-ee"},
+ {"notyetvalid.example.com", "notYetValid"},
+ {"before-epoch.example.com", "beforeEpoch"},
+ {"before-epoch-self-signed.example.com", "beforeEpochSelfSigned"},
+ {"selfsigned.example.com", "selfsigned"},
+ {"unknownissuer.example.com", "unknownissuer"},
+ {"mismatch.example.com", "mismatch"},
+ {"mismatch-CN.example.com", "mismatchCN"},
+ {"mitm.example.com", "mitm"},
+ {"expiredissuer.example.com", "expiredissuer"},
+ {"notyetvalidissuer.example.com", "notYetValidIssuer"},
+ {"before-epoch-issuer.example.com", "beforeEpochIssuer"},
+ {"md5signature.example.com", "md5signature"},
+ {"untrusted.example.com", "default-ee"},
+ {"untrustedissuer.example.com", "untrustedissuer"},
+ {"mismatch-expired.example.com", "mismatch-expired"},
+ {"mismatch-notYetValid.example.com", "mismatch-notYetValid"},
+ {"mismatch-untrusted.example.com", "mismatch-untrusted"},
+ {"untrusted-expired.example.com", "untrusted-expired"},
+ {"md5signature-expired.example.com", "md5signature-expired"},
+ {"mismatch-untrusted-expired.example.com", "mismatch-untrusted-expired"},
+ {"inadequatekeyusage.example.com", "inadequatekeyusage-ee"},
+ {"selfsigned-inadequateEKU.example.com", "selfsigned-inadequateEKU"},
+ {"self-signed-end-entity-with-cA-true.example.com",
+ "self-signed-EE-with-cA-true"},
+ {"ca-used-as-end-entity.example.com", "ca-used-as-end-entity"},
+ {"ca-used-as-end-entity-name-mismatch.example.com",
+ "ca-used-as-end-entity"},
+ // All of include-subdomains.pinning.example.com is pinned to End Entity
+ // Test Cert with nick default-ee. Any other nick will only
+ // pass pinning when security.cert_pinning.enforcement.level != strict and
+ // otherCA is added as a user-specified trust anchor. See StaticHPKPins.h.
+ {"include-subdomains.pinning.example.com", "default-ee"},
+ {"good.include-subdomains.pinning.example.com", "default-ee"},
+ {"bad.include-subdomains.pinning.example.com", "other-issuer-ee"},
+ {"bad.include-subdomains.pinning.example.com.", "other-issuer-ee"},
+ {"bad.include-subdomains.pinning.example.com..", "other-issuer-ee"},
+ {"exclude-subdomains.pinning.example.com", "default-ee"},
+ {"sub.exclude-subdomains.pinning.example.com", "other-issuer-ee"},
+ {"test-mode.pinning.example.com", "other-issuer-ee"},
+ {"unknownissuer.include-subdomains.pinning.example.com", "unknownissuer"},
+ {"unknownissuer.test-mode.pinning.example.com", "unknownissuer"},
+ {"nsCertTypeNotCritical.example.com", "nsCertTypeNotCritical"},
+ {"nsCertTypeCriticalWithExtKeyUsage.example.com",
+ "nsCertTypeCriticalWithExtKeyUsage"},
+ {"nsCertTypeCritical.example.com", "nsCertTypeCritical"},
+ {"end-entity-issued-by-v1-cert.example.com", "eeIssuedByV1Cert"},
+ {"end-entity-issued-by-non-CA.example.com", "eeIssuedByNonCA"},
+ {"inadequate-key-size-ee.example.com", "inadequateKeySizeEE"},
+ {"badSubjectAltNames.example.com", "badSubjectAltNames"},
+ {"ipAddressAsDNSNameInSAN.example.com", "ipAddressAsDNSNameInSAN"},
+ {"noValidNames.example.com", "noValidNames"},
+ {"bug413909.xn--hxajbheg2az3al.xn--jxalpdlp", "idn-certificate"},
+ {"emptyissuername.example.com", "emptyIssuerName"},
+ {"ev-test.example.com", "ev-test"},
+ {"ee-from-missing-intermediate.example.com",
+ "ee-from-missing-intermediate"},
+ {"imminently-distrusted.example.com", "ee-imminently-distrusted"},
+ {"localhost", "unknownissuer"},
+ {"a.pinning.example.com", "default-ee"},
+ {"b.pinning.example.com", "default-ee"},
+ {"not-preloaded.example.com", "default-ee"},
+ {"ee.example.com", "default-ee"},
+ {nullptr, nullptr}};
+
+int32_t DoSNISocketConfigBySubjectCN(PRFileDesc* aFd,
+ const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize) {
+ for (uint32_t i = 0; i < aSrvNameArrSize; i++) {
+ UniquePORTString name(
+ static_cast<char*>(PORT_ZAlloc(aSrvNameArr[i].len + 1)));
+ if (name) {
+ PORT_Memcpy(name.get(), aSrvNameArr[i].data, aSrvNameArr[i].len);
+ if (ConfigSecureServerWithNamedCert(aFd, name.get(), nullptr, nullptr,
+ nullptr) == SECSuccess) {
+ return 0;
+ }
+ }
+ }
+
+ return SSL_SNI_SEND_ALERT;
+}
+
+int32_t DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, void* aArg) {
+ const BadCertAndPinningHost* host =
+ GetHostForSNI(aSrvNameArr, aSrvNameArrSize, sBadCertAndPinningHosts);
+ if (!host) {
+ // No static cert <-> hostname mapping found. This happens when we use a
+ // collection of certificates in a given directory and build a cert DB at
+ // runtime, rather than using an NSS cert DB populated at build time.
+ // (This will be the default in the future.)
+ // For all given server names, check if the runtime-built cert DB contains
+ // a certificate with a matching subject CN.
+ return DoSNISocketConfigBySubjectCN(aFd, aSrvNameArr, aSrvNameArrSize);
+ }
+
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
+ }
+
+ UniqueCERTCertificate cert;
+ SSLKEAType certKEA;
+ if (SECSuccess != ConfigSecureServerWithNamedCert(aFd, host->mCertName, &cert,
+ &certKEA, nullptr)) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[]) {
+ return StartServer(argc, argv, DoSNISocketConfig, nullptr);
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/DelegatedCredentialsServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/DelegatedCredentialsServer.cpp
new file mode 100644
index 0000000000..5f12756425
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/DelegatedCredentialsServer.cpp
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a standalone server used to test Delegated Credentials
+// (see: https://tools.ietf.org/html/draft-ietf-tls-subcerts-03).
+//
+// The client is expected to connect, initiate an SSL handshake (with SNI
+// to indicate which "server" to connect to), and verify the certificate.
+// If all is good, the client then sends one encrypted byte and receives that
+// same byte back.
+// This server also has the ability to "call back" another process waiting on
+// it. That is, when the server is all set up and ready to receive connections,
+// it will connect to a specified port and issue a simple HTTP request.
+
+#include <iostream>
+
+#include "TLSServer.h"
+
+#include "sslexp.h"
+
+using namespace mozilla;
+using namespace mozilla::test;
+
+struct DelegatedCertHost {
+ const char* mHostName;
+ const char* mCertName;
+ const char* mDCKeyNick;
+ bool mEnableDelegatedCredentials;
+};
+
+const PRUint32 kDCValidFor = 60 * 60 * 24 * 7 /* 1 week (seconds) */;
+
+// {host, eeCert, dcCert, enableDC}
+const DelegatedCertHost sDelegatedCertHosts[] = {
+ {"delegated-enabled.example.com", "delegated-ee", "delegated.key", true},
+ {"standard-enabled.example.com", "default-ee", "delegated.key", true},
+ {"delegated-disabled.example.com", "delegated-ee",
+ /* anything non-null */ "delegated.key", false},
+ {nullptr, nullptr, nullptr, false}};
+
+int32_t DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, void* aArg) {
+ const DelegatedCertHost* host =
+ GetHostForSNI(aSrvNameArr, aSrvNameArrSize, sDelegatedCertHosts);
+ if (!host) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ std::cerr << "Identified host " << host->mHostName << std::endl;
+ }
+
+ UniqueCERTCertificate delegatorCert(
+ PK11_FindCertFromNickname(host->mCertName, nullptr));
+ if (!delegatorCert) {
+ PrintPRError("PK11_FindCertFromNickname failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ PrintPRError("PK11_GetInternalKeySlot failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ SSLExtraServerCertData extra_data = {ssl_auth_null,
+ /* Filled in by callee */ nullptr,
+ nullptr,
+ nullptr,
+ /* DC */ nullptr,
+ /* DC PrivKey */ nullptr};
+
+ UniqueSECKEYPrivateKey delegatorPriv(
+ PK11_FindKeyByDERCert(slot.get(), delegatorCert.get(), nullptr));
+ if (!delegatorPriv) {
+ PrintPRError("PK11_FindKeyByDERCert failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ // Find the DC keypair by the file (nick) name.
+ ScopedAutoSECItem dc;
+ UniqueSECKEYPrivateKey dcPriv;
+ if (host->mEnableDelegatedCredentials) {
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ std::cerr << "Enabling a delegated credential for host "
+ << host->mHostName << std::endl;
+ }
+
+ if (PK11_NeedLogin(slot.get())) {
+ SECStatus rv = PK11_Authenticate(slot.get(), PR_TRUE, nullptr);
+ if (rv != SECSuccess) {
+ PrintPRError("PK11_Authenticate failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+ }
+ UniqueSECKEYPrivateKeyList list(PK11_ListPrivKeysInSlot(
+ slot.get(), const_cast<char*>(host->mDCKeyNick), nullptr));
+ if (!list) {
+ PrintPRError("PK11_ListPrivKeysInSlot failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+ SECKEYPrivateKeyListNode* node = PRIVKEY_LIST_HEAD(list);
+
+ dcPriv.reset(SECKEY_CopyPrivateKey(node->key));
+ if (!dcPriv) {
+ PrintPRError("PK11_ListPrivKeysInSlot could not find dcPriv");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ UniqueSECKEYPublicKey dcPub(SECKEY_ConvertToPublicKey(dcPriv.get()));
+ if (!dcPub) {
+ PrintPRError("SECKEY_ConvertToPublicKey failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ // Create and set the DC.
+ if (SSL_DelegateCredential(delegatorCert.get(), delegatorPriv.get(),
+ dcPub.get(), ssl_sig_ecdsa_secp384r1_sha384,
+ kDCValidFor, PR_Now(), &dc) != SECSuccess) {
+ PrintPRError("SSL_DelegateCredential failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+ extra_data.delegCred = &dc;
+ extra_data.delegCredPrivKey = dcPriv.get();
+
+ // The list should only have a single key.
+ PORT_Assert(PRIVKEY_LIST_END(PRIVKEY_LIST_NEXT(node), list));
+ }
+
+ if (ConfigSecureServerWithNamedCert(aFd, host->mCertName, nullptr, nullptr,
+ &extra_data) != SECSuccess) {
+ PrintPRError("ConfigSecureServerWithNamedCert failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[]) {
+ return StartServer(argc, argv, DoSNISocketConfig, nullptr);
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/EncryptedClientHelloServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/EncryptedClientHelloServer.cpp
new file mode 100644
index 0000000000..fd284874b3
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/EncryptedClientHelloServer.cpp
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a standalone server that offers TLS 1.3 Encrypted
+// Client Hello support.
+
+#include <stdio.h>
+
+#include "nspr.h"
+#include "ScopedNSSTypes.h"
+#include "ssl.h"
+#include "sslexp.h"
+#include "TLSServer.h"
+#include <pk11pub.h>
+#include <vector>
+
+using namespace mozilla;
+using namespace mozilla::test;
+
+struct EchHost {
+ const char* mHostName;
+ const char* mCertName;
+};
+
+const std::vector<uint32_t> kSuiteChaCha = {
+ (static_cast<uint32_t>(HpkeKdfHkdfSha256) << 16) |
+ HpkeAeadChaCha20Poly1305};
+
+// Hostname, cert nickname pairs.
+const EchHost sEchHosts[] = {{"ech-public.example.com", "default-ee"},
+ {"ech-private.example.com", "private-ee"},
+ {"selfsigned.example.com", "selfsigned"},
+ {nullptr, nullptr}};
+
+int32_t DoSNISocketConfigBySubjectCN(PRFileDesc* aFd,
+ const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize) {
+ for (uint32_t i = 0; i < aSrvNameArrSize; i++) {
+ UniquePORTString name(
+ static_cast<char*>(PORT_ZAlloc(aSrvNameArr[i].len + 1)));
+ if (name) {
+ PORT_Memcpy(name.get(), aSrvNameArr[i].data, aSrvNameArr[i].len);
+ if (ConfigSecureServerWithNamedCert(aFd, name.get(), nullptr, nullptr,
+ nullptr) == SECSuccess) {
+ return 0;
+ }
+ }
+ }
+
+ return SSL_SNI_SEND_ALERT;
+}
+
+int32_t DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, void* aArg) {
+ const EchHost* host = GetHostForSNI(aSrvNameArr, aSrvNameArrSize, sEchHosts);
+ if (!host) {
+ PrintPRError("No cert found for hostname");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
+ }
+
+ UniqueCERTCertificate cert;
+ SSLKEAType certKEA;
+ if (SECSuccess != ConfigSecureServerWithNamedCert(aFd, host->mCertName, &cert,
+ &certKEA, nullptr)) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ return 0;
+}
+
+int32_t SetAlpnOptions(PRFileDesc* aFd, uint8_t flags) {
+ const std::vector<uint8_t> http1 = {0x08, 0x68, 0x74, 0x74, 0x70,
+ 0x2f, 0x31, 0x2e, 0x31};
+ const std::vector<uint8_t> http2 = {0x02, 0x68, 0x32};
+ const std::vector<uint8_t> http3 = {0x02, 0x68, 0x33};
+ std::vector<uint8_t> alpnVec = {};
+ if (flags & 0b001) {
+ alpnVec.insert(alpnVec.end(), http1.begin(), http1.end());
+ }
+ if (flags & 0b010) {
+ alpnVec.insert(alpnVec.end(), http2.begin(), http2.end());
+ }
+ if (flags & 0b100) {
+ alpnVec.insert(alpnVec.end(), http3.begin(), http3.end());
+ }
+ fprintf(stderr, "ALPN Flags: %u\n", flags);
+ fprintf(stderr, "ALPN length: %zu\n", alpnVec.size());
+ if (SSL_SetNextProtoNego(aFd, alpnVec.data(), alpnVec.size()) != SECSuccess) {
+ fprintf(stderr, "Setting ALPN failed!\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+SECStatus ConfigureServer(PRFileDesc* aFd) {
+ const char* alpnFlag = PR_GetEnv("MOZ_TLS_ECH_ALPN_FLAG");
+ if (alpnFlag) {
+ uint8_t flag = atoi(alpnFlag);
+ SetAlpnOptions(aFd, flag);
+ }
+
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ PrintPRError("PK11_GetInternalKeySlot failed");
+ return SECFailure;
+ }
+
+ UniqueSECKEYPublicKey pubKey;
+ UniqueSECKEYPrivateKey privKey;
+ SECKEYPublicKey* tmpPubKey = nullptr;
+ SECKEYPrivateKey* tmpPrivKey = nullptr;
+
+ static const std::vector<uint8_t> pkcs8{
+ 0x30, 0x67, 0x02, 0x01, 0x00, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48,
+ 0xce, 0x3d, 0x02, 0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda,
+ 0x47, 0x0f, 0x01, 0x04, 0x4c, 0x30, 0x4a, 0x02, 0x01, 0x01, 0x04, 0x20,
+ 0x8c, 0x49, 0x0e, 0x5b, 0x0c, 0x7d, 0xbe, 0x0c, 0x6d, 0x21, 0x92, 0x48,
+ 0x4d, 0x2b, 0x7a, 0x04, 0x23, 0xb3, 0xb4, 0x54, 0x4f, 0x24, 0x81, 0x09,
+ 0x5a, 0x99, 0xdb, 0xf2, 0x38, 0xfb, 0x35, 0x0f, 0xa1, 0x23, 0x03, 0x21,
+ 0x00, 0x8a, 0x07, 0x56, 0x39, 0x49, 0xfa, 0xc6, 0x23, 0x29, 0x36, 0xed,
+ 0x6f, 0x36, 0xc4, 0xfa, 0x73, 0x59, 0x30, 0xec, 0xde, 0xae, 0xf6, 0x73,
+ 0x4e, 0x31, 0x4a, 0xea, 0xc3, 0x5a, 0x56, 0xfd, 0x0a};
+
+ SECItem pkcs8Item = {siBuffer, const_cast<uint8_t*>(pkcs8.data()),
+ static_cast<unsigned int>(pkcs8.size())};
+ SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
+ slot.get(), &pkcs8Item, nullptr, nullptr, false, false, KU_ALL,
+ &tmpPrivKey, nullptr);
+
+ if (rv != SECSuccess) {
+ PrintPRError("PK11_ImportDERPrivateKeyInfoAndReturnKey failed");
+ return SECFailure;
+ }
+ privKey.reset(tmpPrivKey);
+ tmpPubKey = SECKEY_ConvertToPublicKey(privKey.get());
+ pubKey.reset(tmpPubKey);
+
+ if (!privKey || !pubKey) {
+ PrintPRError("ECH/HPKE Public or Private key is null!");
+ return SECFailure;
+ }
+
+ std::vector<uint8_t> echConfig(1000, 0);
+ unsigned int len = 0;
+ const PRUint8 configId = 77;
+ const HpkeSymmetricSuite echCipherSuite = {HpkeKdfHkdfSha256,
+ HpkeAeadChaCha20Poly1305};
+ rv = SSL_EncodeEchConfigId(configId, "ech-public.example.com", 100,
+ HpkeDhKemX25519Sha256, pubKey.get(),
+ &echCipherSuite, 1, echConfig.data(), &len,
+ echConfig.size());
+ if (rv != SECSuccess) {
+ PrintPRError("SSL_EncodeEchConfig failed");
+ return rv;
+ }
+
+ rv = SSL_SetServerEchConfigs(aFd, pubKey.get(), privKey.get(),
+ echConfig.data(), len);
+ if (rv != SECSuccess) {
+ PrintPRError("SSL_SetServerEchConfigs failed");
+ return rv;
+ }
+
+ return SECSuccess;
+}
+
+int main(int argc, char* argv[]) {
+ int rv = StartServer(argc, argv, DoSNISocketConfig, nullptr, ConfigureServer);
+ if (rv < 0) {
+ return rv;
+ }
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/FaultyServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/FaultyServer.cpp
new file mode 100644
index 0000000000..4d30f8a87a
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/FaultyServer.cpp
@@ -0,0 +1,206 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+
+#include "nspr.h"
+#include "ScopedNSSTypes.h"
+#include "ssl.h"
+#include "ssl3prot.h"
+#include "sslexp.h"
+#include "sslimpl.h"
+#include "TLSServer.h"
+
+#include "mozilla/Sprintf.h"
+
+using namespace mozilla;
+using namespace mozilla::test;
+
+enum FaultType {
+ None = 0,
+ ZeroRtt,
+ UnknownSNI,
+};
+
+struct FaultyServerHost {
+ const char* mHostName;
+ const char* mCertName;
+ FaultType mFaultType;
+};
+
+const char* kHostOk = "ok.example.com";
+const char* kHostUnknown = "unknown.example.com";
+const char* kHostZeroRttAlertBadMac = "0rtt-alert-bad-mac.example.com";
+const char* kHostZeroRttAlertVersion =
+ "0rtt-alert-protocol-version.example.com";
+const char* kHostZeroRttAlertUnexpected = "0rtt-alert-unexpected.example.com";
+const char* kHostZeroRttAlertDowngrade = "0rtt-alert-downgrade.example.com";
+
+const char* kCertWildcard = "default-ee";
+
+/* Each type of failure gets a different SNI.
+ * the "default-ee" cert has a SAN for *.example.com
+ * the "no-san-ee" cert is signed by the test-ca, but it doesn't have any SANs.
+ */
+const FaultyServerHost sFaultyServerHosts[]{
+ {kHostOk, kCertWildcard, None},
+ {kHostUnknown, kCertWildcard, UnknownSNI},
+ {kHostZeroRttAlertBadMac, kCertWildcard, ZeroRtt},
+ {kHostZeroRttAlertVersion, kCertWildcard, ZeroRtt},
+ {kHostZeroRttAlertUnexpected, kCertWildcard, ZeroRtt},
+ {kHostZeroRttAlertDowngrade, kCertWildcard, ZeroRtt},
+ {nullptr, nullptr},
+};
+
+nsresult SendAll(PRFileDesc* aSocket, const char* aData, size_t aDataLen) {
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "sending '%s'\n", aData);
+ }
+
+ int32_t len = static_cast<int32_t>(aDataLen);
+ while (len > 0) {
+ int32_t bytesSent = PR_Send(aSocket, aData, len, 0, PR_INTERVAL_NO_TIMEOUT);
+ if (bytesSent == -1) {
+ PrintPRError("PR_Send failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ len -= bytesSent;
+ aData += bytesSent;
+ }
+
+ return NS_OK;
+}
+
+// returns 0 on success, non-zero on error
+int DoCallback(const char* path) {
+ UniquePRFileDesc socket(PR_NewTCPSocket());
+ if (!socket) {
+ PrintPRError("PR_NewTCPSocket failed");
+ return 1;
+ }
+
+ uint32_t port = 0;
+ const char* callbackPort = PR_GetEnv("FAULTY_SERVER_CALLBACK_PORT");
+ if (callbackPort) {
+ port = atoi(callbackPort);
+ }
+ if (!port) {
+ return 0;
+ }
+
+ PRNetAddr addr;
+ PR_InitializeNetAddr(PR_IpAddrLoopback, port, &addr);
+ if (PR_Connect(socket.get(), &addr, PR_INTERVAL_NO_TIMEOUT) != PR_SUCCESS) {
+ PrintPRError("PR_Connect failed");
+ return 1;
+ }
+
+ char request[512];
+ SprintfLiteral(request, "GET %s HTTP/1.0\r\n\r\n", path);
+ SendAll(socket.get(), request, strlen(request));
+ char buf[4096];
+ memset(buf, 0, sizeof(buf));
+ int32_t bytesRead =
+ PR_Recv(socket.get(), buf, sizeof(buf) - 1, 0, PR_INTERVAL_NO_TIMEOUT);
+ if (bytesRead < 0) {
+ PrintPRError("PR_Recv failed 1");
+ return 1;
+ }
+ if (bytesRead == 0) {
+ fprintf(stderr, "PR_Recv eof 1\n");
+ return 1;
+ }
+ // fprintf(stderr, "%s\n", buf);
+ return 0;
+}
+
+/* These are very rough examples. In practice the `arg` parameter to a callback
+ * might need to be an object that holds some state, like the various traffic
+ * secrets. */
+
+/* An SSLSecretCallback is called after every key derivation step in the TLS
+ * 1.3 key schedule.
+ *
+ * Epoch 1 is for the early traffic secret.
+ * Epoch 2 is for the handshake traffic secrets.
+ * Epoch 3 is for the application traffic secrets.
+ */
+void SecretCallbackFailZeroRtt(PRFileDesc* fd, PRUint16 epoch,
+ SSLSecretDirection dir, PK11SymKey* secret,
+ void* arg) {
+ fprintf(stderr, "0RTT handler epoch=%d dir=%d\n", epoch, (uint32_t)dir);
+ FaultyServerHost* host = static_cast<FaultyServerHost*>(arg);
+
+ if (epoch == 1 && dir == ssl_secret_read) {
+ sslSocket* ss = ssl_FindSocket(fd);
+ if (!ss) {
+ fprintf(stderr, "0RTT handler, no ss!\n");
+ return;
+ }
+
+ char path[256];
+ SprintfLiteral(path, "/callback/%d", epoch);
+ DoCallback(path);
+
+ fprintf(stderr, "0RTT handler, configuring alert\n");
+ if (!strcmp(host->mHostName, kHostZeroRttAlertBadMac)) {
+ SSL3_SendAlert(ss, alert_fatal, bad_record_mac);
+ } else if (!strcmp(host->mHostName, kHostZeroRttAlertVersion)) {
+ SSL3_SendAlert(ss, alert_fatal, protocol_version);
+ } else if (!strcmp(host->mHostName, kHostZeroRttAlertUnexpected)) {
+ SSL3_SendAlert(ss, alert_fatal, no_alert);
+ }
+ }
+}
+
+/* An SSLRecordWriteCallback can replace the TLS record layer. */
+SECStatus WriteCallbackExample(PRFileDesc* fd, PRUint16 epoch,
+ SSLContentType contentType, const PRUint8* data,
+ unsigned int len, void* arg) {
+ /* do something */
+ return SECSuccess;
+}
+
+int32_t DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, void* aArg) {
+ const FaultyServerHost* host =
+ GetHostForSNI(aSrvNameArr, aSrvNameArrSize, sFaultyServerHosts);
+ if (!host || host->mFaultType == UnknownSNI) {
+ PrintPRError("No cert found for hostname");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
+ }
+
+ switch (host->mFaultType) {
+ case ZeroRtt:
+ SSL_SecretCallback(aFd, &SecretCallbackFailZeroRtt, (void*)host);
+ break;
+ case None:
+ break;
+ default:
+ break;
+ }
+
+ UniqueCERTCertificate cert;
+ SSLKEAType certKEA;
+ if (SECSuccess != ConfigSecureServerWithNamedCert(aFd, host->mCertName, &cert,
+ &certKEA, nullptr)) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ return 0;
+}
+
+SECStatus ConfigureServer(PRFileDesc* aFd) { return SECSuccess; }
+
+int main(int argc, char* argv[]) {
+ int rv = StartServer(argc, argv, DoSNISocketConfig, nullptr, ConfigureServer);
+ if (rv < 0) {
+ return rv;
+ }
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp
new file mode 100644
index 0000000000..113e668f89
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 tw=80 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This simple program takes a database directory, and one or more tuples like
+ * <typeOfResponse> <CertNick> <ExtraCertNick> <outPutFilename>
+ * to generate (one or more) ocsp responses.
+ */
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "mozilla/ArrayUtils.h"
+
+#include "cert.h"
+#include "nspr.h"
+#include "nss.h"
+#include "plarenas.h"
+#include "prerror.h"
+#include "ssl.h"
+#include "secerr.h"
+
+#include "OCSPCommon.h"
+#include "ScopedNSSTypes.h"
+#include "TLSServer.h"
+
+using namespace mozilla;
+using namespace mozilla::test;
+
+struct OCSPResponseName {
+ const char* mTypeString;
+ const OCSPResponseType mORT;
+};
+
+const static OCSPResponseName kOCSPResponseNameList[] = {
+ {"good", ORTGood}, // the certificate is good
+ {"good-delegated", ORTDelegatedIncluded}, // the certificate is good, using
+ // a delegated signer
+ {"revoked", ORTRevoked}, // the certificate has been revoked
+ {"unknown", ORTUnknown}, // the responder doesn't know if the
+ // cert is good
+ {"goodotherca", ORTGoodOtherCA}, // the wrong CA has signed the
+ // response
+ {"expiredresponse", ORTExpired}, // the signature on the response has
+ // expired
+ {"oldvalidperiod", ORTExpiredFreshCA}, // fresh signature, but old validity
+ // period
+ {"empty", ORTEmpty}, // an empty stapled response
+
+ {"malformed", ORTMalformed}, // the response from the responder
+ // was malformed
+ {"serverr", ORTSrverr}, // the response indicates there was a
+ // server error
+ {"trylater", ORTTryLater}, // the responder replied with
+ // "try again later"
+ {"resp-unsigned", ORTNeedsSig}, // the response needs a signature
+ {"unauthorized", ORTUnauthorized}, // the responder does not know about
+ // the cert
+ {"bad-signature", ORTBadSignature}, // the response has a bad signature
+ {"longvalidityalmostold",
+ ORTLongValidityAlmostExpired}, // the response is
+ // still valid, but the generation
+ // is almost a year old
+ {"ancientstillvalid", ORTAncientAlmostExpired}, // The response is still
+ // valid but the generation
+ // is almost two years old
+};
+
+bool StringToOCSPResponseType(const char* respText,
+ /*out*/ OCSPResponseType* OCSPType) {
+ if (!OCSPType) {
+ return false;
+ }
+ for (auto ocspResponseName : kOCSPResponseNameList) {
+ if (strcmp(respText, ocspResponseName.mTypeString) == 0) {
+ *OCSPType = ocspResponseName.mORT;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool WriteResponse(const char* filename, const SECItem* item) {
+ if (!filename || !item || !item->data) {
+ PR_fprintf(PR_STDERR, "invalid parameters to WriteResponse");
+ return false;
+ }
+
+ UniquePRFileDesc outFile(
+ PR_Open(filename, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0644));
+ if (!outFile) {
+ PrintPRError("cannot open file for writing");
+ return false;
+ }
+ int32_t rv = PR_Write(outFile.get(), item->data, item->len);
+ if (rv < 0 || (uint32_t)rv != item->len) {
+ PrintPRError("File write failure");
+ return false;
+ }
+
+ return true;
+}
+
+int main(int argc, char* argv[]) {
+ if (argc < 7 || (argc - 7) % 5 != 0) {
+ PR_fprintf(
+ PR_STDERR,
+ "usage: %s <NSS DB directory> <responsetype> "
+ "<cert_nick> <extranick> <this_update_skew> <outfilename> [<resptype> "
+ "<cert_nick> <extranick> <this_update_skew> <outfilename>]* \n",
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ SECStatus rv = InitializeNSS(argv[1]);
+ if (rv != SECSuccess) {
+ PR_fprintf(PR_STDERR, "Failed to initialize NSS\n");
+ exit(EXIT_FAILURE);
+ }
+ UniquePLArenaPool arena(PORT_NewArena(256 * argc));
+ if (!arena) {
+ PrintPRError("PORT_NewArena failed");
+ exit(EXIT_FAILURE);
+ }
+
+ for (int i = 2; i + 3 < argc; i += 5) {
+ const char* ocspTypeText = argv[i];
+ const char* certNick = argv[i + 1];
+ const char* extraCertname = argv[i + 2];
+ const char* skewChars = argv[i + 3];
+ const char* filename = argv[i + 4];
+
+ OCSPResponseType ORT;
+ if (!StringToOCSPResponseType(ocspTypeText, &ORT)) {
+ PR_fprintf(PR_STDERR, "Cannot generate OCSP response of type %s\n",
+ ocspTypeText);
+ exit(EXIT_FAILURE);
+ }
+
+ UniqueCERTCertificate cert(PK11_FindCertFromNickname(certNick, nullptr));
+ if (!cert) {
+ PrintPRError("PK11_FindCertFromNickname failed");
+ PR_fprintf(PR_STDERR, "Failed to find certificate with nick '%s'\n",
+ certNick);
+ exit(EXIT_FAILURE);
+ }
+
+ time_t skew = static_cast<time_t>(atoll(skewChars));
+
+ SECItemArray* response =
+ GetOCSPResponseForType(ORT, cert, arena, extraCertname, skew);
+ if (!response) {
+ PR_fprintf(PR_STDERR,
+ "Failed to generate OCSP response of type %s "
+ "for %s\n",
+ ocspTypeText, certNick);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!WriteResponse(filename, &response->items[0])) {
+ PR_fprintf(PR_STDERR, "Failed to write file %s\n", filename);
+ exit(EXIT_FAILURE);
+ }
+ }
+ return 0;
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp
new file mode 100644
index 0000000000..b35484572f
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp
@@ -0,0 +1,153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a standalone server that delivers various stapled OCSP responses.
+// The client is expected to connect, initiate an SSL handshake (with SNI
+// to indicate which "server" to connect to), and verify the OCSP response.
+// If all is good, the client then sends one encrypted byte and receives that
+// same byte back.
+// This server also has the ability to "call back" another process waiting on
+// it. That is, when the server is all set up and ready to receive connections,
+// it will connect to a specified port and issue a simple HTTP request.
+
+#include <stdio.h>
+
+#include "OCSPCommon.h"
+#include "TLSServer.h"
+
+using namespace mozilla;
+using namespace mozilla::test;
+
+const OCSPHost sOCSPHosts[] = {
+ {"ocsp-stapling-good.example.com", ORTGood, nullptr, nullptr},
+ {"ocsp-stapling-revoked.example.com", ORTRevoked, nullptr, nullptr},
+ {"ocsp-stapling-revoked-old.example.com", ORTRevokedOld, nullptr, nullptr},
+ {"ocsp-stapling-unknown.example.com", ORTUnknown, nullptr, nullptr},
+ {"ocsp-stapling-unknown-old.example.com", ORTUnknownOld, nullptr, nullptr},
+ {"ocsp-stapling-good-other.example.com", ORTGoodOtherCert,
+ "ocspOtherEndEntity", nullptr},
+ {"ocsp-stapling-good-other-ca.example.com", ORTGoodOtherCA, "other-test-ca",
+ nullptr},
+ {"ocsp-stapling-expired.example.com", ORTExpired, nullptr, nullptr},
+ {"ocsp-stapling-expired-fresh-ca.example.com", ORTExpiredFreshCA, nullptr,
+ nullptr},
+ {"ocsp-stapling-none.example.com", ORTNone, nullptr, nullptr},
+ {"ocsp-stapling-empty.example.com", ORTEmpty, nullptr, nullptr},
+ {"ocsp-stapling-malformed.example.com", ORTMalformed, nullptr, nullptr},
+ {"ocsp-stapling-srverr.example.com", ORTSrverr, nullptr, nullptr},
+ {"ocsp-stapling-trylater.example.com", ORTTryLater, nullptr, nullptr},
+ {"ocsp-stapling-needssig.example.com", ORTNeedsSig, nullptr, nullptr},
+ {"ocsp-stapling-unauthorized.example.com", ORTUnauthorized, nullptr,
+ nullptr},
+ {"ocsp-stapling-with-intermediate.example.com", ORTGood, nullptr,
+ "ocspEEWithIntermediate"},
+ {"ocsp-stapling-bad-signature.example.com", ORTBadSignature, nullptr,
+ nullptr},
+ {"ocsp-stapling-skip-responseBytes.example.com", ORTSkipResponseBytes,
+ nullptr, nullptr},
+ {"ocsp-stapling-critical-extension.example.com", ORTCriticalExtension,
+ nullptr, nullptr},
+ {"ocsp-stapling-noncritical-extension.example.com", ORTNoncriticalExtension,
+ nullptr, nullptr},
+ {"ocsp-stapling-empty-extensions.example.com", ORTEmptyExtensions, nullptr,
+ nullptr},
+ {"ocsp-stapling-delegated-included.example.com", ORTDelegatedIncluded,
+ "delegatedSigner", nullptr},
+ {"ocsp-stapling-delegated-included-last.example.com",
+ ORTDelegatedIncludedLast, "delegatedSigner", nullptr},
+ {"ocsp-stapling-delegated-missing.example.com", ORTDelegatedMissing,
+ "delegatedSigner", nullptr},
+ {"ocsp-stapling-delegated-missing-multiple.example.com",
+ ORTDelegatedMissingMultiple, "delegatedSigner", nullptr},
+ {"ocsp-stapling-delegated-no-extKeyUsage.example.com", ORTDelegatedIncluded,
+ "invalidDelegatedSignerNoExtKeyUsage", nullptr},
+ {"ocsp-stapling-delegated-from-intermediate.example.com",
+ ORTDelegatedIncluded, "invalidDelegatedSignerFromIntermediate", nullptr},
+ {"ocsp-stapling-delegated-keyUsage-crlSigning.example.com",
+ ORTDelegatedIncluded, "invalidDelegatedSignerKeyUsageCrlSigning", nullptr},
+ {"ocsp-stapling-delegated-wrong-extKeyUsage.example.com",
+ ORTDelegatedIncluded, "invalidDelegatedSignerWrongExtKeyUsage", nullptr},
+ {"ocsp-stapling-ancient-valid.example.com", ORTAncientAlmostExpired,
+ nullptr, nullptr},
+ {"keysize-ocsp-delegated.example.com", ORTDelegatedIncluded,
+ "rsa-1016-keysizeDelegatedSigner", nullptr},
+ {"revoked-ca-cert-used-as-end-entity.example.com", ORTRevoked,
+ "ca-used-as-end-entity", nullptr},
+ {"ocsp-stapling-must-staple.example.com", ORTGood, nullptr,
+ "must-staple-ee"},
+ {"ocsp-stapling-must-staple-revoked.example.com", ORTRevoked, nullptr,
+ "must-staple-ee"},
+ {"ocsp-stapling-must-staple-missing.example.com", ORTNone, nullptr,
+ "must-staple-ee"},
+ {"ocsp-stapling-must-staple-empty.example.com", ORTEmpty, nullptr,
+ "must-staple-ee"},
+ {"ocsp-stapling-must-staple-ee-with-must-staple-int.example.com", ORTGood,
+ nullptr, "must-staple-ee-with-must-staple-int"},
+ {"ocsp-stapling-plain-ee-with-must-staple-int.example.com", ORTGood,
+ nullptr, "must-staple-missing-ee"},
+ {"ocsp-stapling-must-staple-expired.example.com", ORTExpired, nullptr,
+ "must-staple-ee"},
+ {"ocsp-stapling-must-staple-try-later.example.com", ORTTryLater, nullptr,
+ "must-staple-ee"},
+ {"ocsp-stapling-must-staple-invalid-signer.example.com", ORTGoodOtherCA,
+ "other-test-ca", "must-staple-ee"},
+ {"multi-tls-feature-good.example.com", ORTNone, nullptr,
+ "multi-tls-feature-good-ee"},
+ {"multi-tls-feature-bad.example.com", ORTNone, nullptr,
+ "multi-tls-feature-bad-ee"},
+ {nullptr, ORTNull, nullptr, nullptr}};
+
+int32_t DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, void* aArg) {
+ const OCSPHost* host =
+ GetHostForSNI(aSrvNameArr, aSrvNameArrSize, sOCSPHosts);
+ if (!host) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
+ }
+
+ const char* certNickname =
+ host->mServerCertName ? host->mServerCertName : DEFAULT_CERT_NICKNAME;
+
+ UniqueCERTCertificate cert;
+ SSLKEAType certKEA;
+ if (SECSuccess != ConfigSecureServerWithNamedCert(aFd, certNickname, &cert,
+ &certKEA, nullptr)) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ // If the OCSP response type is "none", don't staple a response.
+ if (host->mORT == ORTNone) {
+ return 0;
+ }
+
+ UniquePLArenaPool arena(PORT_NewArena(1024));
+ if (!arena) {
+ PrintPRError("PORT_NewArena failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ // response is contained by the arena - freeing the arena will free it
+ SECItemArray* response = GetOCSPResponseForType(host->mORT, cert, arena,
+ host->mAdditionalCertName, 0);
+ if (!response) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ // SSL_SetStapledOCSPResponses makes a deep copy of response
+ SECStatus st = SSL_SetStapledOCSPResponses(aFd, response, certKEA);
+ if (st != SECSuccess) {
+ PrintPRError("SSL_SetStapledOCSPResponses failed");
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[]) {
+ return StartServer(argc, argv, DoSNISocketConfig, nullptr);
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/SanctionsTestServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/SanctionsTestServer.cpp
new file mode 100644
index 0000000000..9371617305
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/SanctionsTestServer.cpp
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a standalone server that uses various bad certificates.
+// The client is expected to connect, initiate an SSL handshake (with SNI
+// to indicate which "server" to connect to), and verify the certificate.
+// If all is good, the client then sends one encrypted byte and receives that
+// same byte back.
+// This server also has the ability to "call back" another process waiting on
+// it. That is, when the server is all set up and ready to receive connections,
+// it will connect to a specified port and issue a simple HTTP request.
+
+#include <stdio.h>
+
+#include "TLSServer.h"
+
+using namespace mozilla;
+using namespace mozilla::test;
+
+struct SanctionsCertHost {
+ const char* mHostName;
+ const char* mCertName;
+};
+
+// Hostname, cert nickname pairs.
+const SanctionsCertHost sSanctionsCertHosts[] = {
+ {"symantec-allowlist-after-cutoff.example.com",
+ "symantec-ee-from-allowlist-after-cutoff"},
+ {"symantec-allowlist-before-cutoff.example.com",
+ "symantec-ee-from-allowlist-before-cutoff"},
+ {"symantec-not-allowlisted-after-cutoff.example.com",
+ "symantec-ee-not-allowlisted-after-cutoff"},
+ {"symantec-not-allowlisted-before-cutoff.example.com",
+ "symantec-ee-not-allowlisted-before-cutoff"},
+ {"symantec-unaffected.example.com", "symantec-ee-unaffected"},
+ {nullptr, nullptr}};
+
+int32_t DoSNISocketConfigBySubjectCN(PRFileDesc* aFd,
+ const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize) {
+ for (uint32_t i = 0; i < aSrvNameArrSize; i++) {
+ UniquePORTString name(
+ static_cast<char*>(PORT_ZAlloc(aSrvNameArr[i].len + 1)));
+ if (name) {
+ PORT_Memcpy(name.get(), aSrvNameArr[i].data, aSrvNameArr[i].len);
+ if (ConfigSecureServerWithNamedCert(aFd, name.get(), nullptr, nullptr,
+ nullptr) == SECSuccess) {
+ return 0;
+ }
+ }
+ }
+
+ return SSL_SNI_SEND_ALERT;
+}
+
+int32_t DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, void* aArg) {
+ const SanctionsCertHost* host =
+ GetHostForSNI(aSrvNameArr, aSrvNameArrSize, sSanctionsCertHosts);
+ if (!host) {
+ // No static cert <-> hostname mapping found. This happens when we use a
+ // collection of certificates in a given directory and build a cert DB at
+ // runtime, rather than using an NSS cert DB populated at build time.
+ // (This will be the default in the future.)
+ // For all given server names, check if the runtime-built cert DB contains
+ // a certificate with a matching subject CN.
+ return DoSNISocketConfigBySubjectCN(aFd, aSrvNameArr, aSrvNameArrSize);
+ }
+
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
+ }
+
+ UniqueCERTCertificate cert;
+ SSLKEAType certKEA;
+ if (SECSuccess != ConfigSecureServerWithNamedCert(aFd, host->mCertName, &cert,
+ &certKEA, nullptr)) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[]) {
+ return StartServer(argc, argv, DoSNISocketConfig, nullptr);
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/moz.build b/security/manager/ssl/tests/unit/tlsserver/cmd/moz.build
new file mode 100644
index 0000000000..ebf8f8e3e7
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+GeckoSimplePrograms(
+ [
+ "BadCertAndPinningServer",
+ "DelegatedCredentialsServer",
+ "EncryptedClientHelloServer",
+ "GenerateOCSPResponse",
+ "OCSPStaplingServer",
+ "SanctionsTestServer",
+ ],
+ linkage=None,
+)
+
+if not CONFIG["MOZ_SYSTEM_NSS"]:
+ # Bug 1805371. See comment in ../lib/moz.build
+ GeckoSimplePrograms(
+ [
+ "FaultyServer",
+ ],
+ linkage=None,
+ )
+
+ DEFINES["NSS_USE_STATIC_LIBS"] = True
+
+ LOCAL_INCLUDES += [
+ "../../../../../../nss/lib/ssl",
+ "../lib",
+ ]
+ USE_LIBS += [
+ "tlsserver",
+ ]
+else:
+ LOCAL_INCLUDES += [
+ "../lib",
+ ]
+ USE_LIBS += ["mozpkix", "nspr", "nss", "tlsserver"]
+
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
diff --git a/security/manager/ssl/tests/unit/tlsserver/default-ee.der b/security/manager/ssl/tests/unit/tlsserver/default-ee.der
new file mode 100644
index 0000000000..3a9b8fa9bc
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/default-ee.der
@@ -0,0 +1,3 @@
+This is now an unused file. It exists to ease the coordination between gecko
+development trees and the automation infrastructure that runs periodic updates.
+See bug 1203312 and bug 1205406.
diff --git a/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
new file mode 100644
index 0000000000..be9a9af9b1
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OCSPCommon.h"
+
+#include <stdio.h>
+
+#include "mozpkix/test/pkixtestutil.h"
+#include "mozpkix/test/pkixtestnss.h"
+#include "TLSServer.h"
+#include "secder.h"
+#include "secerr.h"
+
+using namespace mozilla;
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+using namespace mozilla::test;
+
+static TestKeyPair* CreateTestKeyPairFromCert(
+ const UniqueCERTCertificate& cert) {
+ ScopedSECKEYPrivateKey privateKey(PK11_FindKeyByAnyCert(cert.get(), nullptr));
+ if (!privateKey) {
+ return nullptr;
+ }
+ ScopedSECKEYPublicKey publicKey(CERT_ExtractPublicKey(cert.get()));
+ if (!publicKey) {
+ return nullptr;
+ }
+ return CreateTestKeyPair(RSA_PKCS1(), publicKey, privateKey);
+}
+
+SECItemArray* GetOCSPResponseForType(OCSPResponseType aORT,
+ const UniqueCERTCertificate& aCert,
+ const UniquePLArenaPool& aArena,
+ const char* aAdditionalCertName,
+ time_t aThisUpdateSkew) {
+ MOZ_ASSERT(aArena);
+ MOZ_ASSERT(aCert);
+ // Note: |aAdditionalCertName| may or may not need to be non-null depending
+ // on the |aORT| value given.
+
+ if (aORT == ORTNone) {
+ if (gDebugLevel >= DEBUG_WARNINGS) {
+ fprintf(stderr,
+ "GetOCSPResponseForType called with type ORTNone, "
+ "which makes no sense.\n");
+ }
+ return nullptr;
+ }
+
+ if (aORT == ORTEmpty) {
+ SECItemArray* arr = SECITEM_AllocArray(aArena.get(), nullptr, 1);
+ arr->items[0].data = nullptr;
+ arr->items[0].len = 0;
+ return arr;
+ }
+
+ time_t now = time(nullptr) + aThisUpdateSkew;
+ time_t oldNow = now - (8 * Time::ONE_DAY_IN_SECONDS);
+
+ mozilla::UniqueCERTCertificate cert(CERT_DupCertificate(aCert.get()));
+
+ if (aORT == ORTGoodOtherCert) {
+ cert.reset(PK11_FindCertFromNickname(aAdditionalCertName, nullptr));
+ if (!cert) {
+ PrintPRError("PK11_FindCertFromNickname failed");
+ return nullptr;
+ }
+ }
+ // XXX CERT_FindCertIssuer uses the old, deprecated path-building logic
+ mozilla::UniqueCERTCertificate issuerCert(
+ CERT_FindCertIssuer(aCert.get(), PR_Now(), certUsageSSLCA));
+ if (!issuerCert) {
+ PrintPRError("CERT_FindCertIssuer failed");
+ return nullptr;
+ }
+ Input issuer;
+ if (issuer.Init(cert->derIssuer.data, cert->derIssuer.len) != Success) {
+ return nullptr;
+ }
+ Input issuerPublicKey;
+ if (issuerPublicKey.Init(issuerCert->derPublicKey.data,
+ issuerCert->derPublicKey.len) != Success) {
+ return nullptr;
+ }
+ Input serialNumber;
+ if (serialNumber.Init(cert->serialNumber.data, cert->serialNumber.len) !=
+ Success) {
+ return nullptr;
+ }
+ CertID certID(issuer, issuerPublicKey, serialNumber);
+ OCSPResponseContext context(certID, now);
+
+ mozilla::UniqueCERTCertificate signerCert;
+ if (aORT == ORTGoodOtherCA || aORT == ORTDelegatedIncluded ||
+ aORT == ORTDelegatedIncludedLast || aORT == ORTDelegatedMissing ||
+ aORT == ORTDelegatedMissingMultiple) {
+ signerCert.reset(PK11_FindCertFromNickname(aAdditionalCertName, nullptr));
+ if (!signerCert) {
+ PrintPRError("PK11_FindCertFromNickname failed");
+ return nullptr;
+ }
+ }
+
+ ByteString certs[5];
+
+ if (aORT == ORTDelegatedIncluded) {
+ certs[0].assign(signerCert->derCert.data, signerCert->derCert.len);
+ context.certs = certs;
+ }
+ if (aORT == ORTDelegatedIncludedLast || aORT == ORTDelegatedMissingMultiple) {
+ certs[0].assign(issuerCert->derCert.data, issuerCert->derCert.len);
+ certs[1].assign(cert->derCert.data, cert->derCert.len);
+ certs[2].assign(issuerCert->derCert.data, issuerCert->derCert.len);
+ if (aORT != ORTDelegatedMissingMultiple) {
+ certs[3].assign(signerCert->derCert.data, signerCert->derCert.len);
+ }
+ context.certs = certs;
+ }
+
+ switch (aORT) {
+ case ORTMalformed:
+ context.responseStatus = 1;
+ break;
+ case ORTSrverr:
+ context.responseStatus = 2;
+ break;
+ case ORTTryLater:
+ context.responseStatus = 3;
+ break;
+ case ORTNeedsSig:
+ context.responseStatus = 5;
+ break;
+ case ORTUnauthorized:
+ context.responseStatus = 6;
+ break;
+ default:
+ // context.responseStatus is 0 in all other cases, and it has
+ // already been initialized in the constructor.
+ break;
+ }
+ if (aORT == ORTSkipResponseBytes) {
+ context.skipResponseBytes = true;
+ }
+ if (aORT == ORTExpired || aORT == ORTExpiredFreshCA ||
+ aORT == ORTRevokedOld || aORT == ORTUnknownOld) {
+ context.thisUpdate = oldNow;
+ context.nextUpdate = oldNow + Time::ONE_DAY_IN_SECONDS;
+ }
+ if (aORT == ORTLongValidityAlmostExpired) {
+ context.thisUpdate = now - (320 * Time::ONE_DAY_IN_SECONDS);
+ }
+ if (aORT == ORTAncientAlmostExpired) {
+ context.thisUpdate = now - (640 * Time::ONE_DAY_IN_SECONDS);
+ }
+ if (aORT == ORTRevoked || aORT == ORTRevokedOld) {
+ context.certStatus = 1;
+ }
+ if (aORT == ORTUnknown || aORT == ORTUnknownOld) {
+ context.certStatus = 2;
+ }
+ if (aORT == ORTBadSignature) {
+ context.badSignature = true;
+ }
+ OCSPResponseExtension extension;
+ if (aORT == ORTCriticalExtension || aORT == ORTNoncriticalExtension) {
+ // python DottedOIDToCode.py --tlv
+ // some-Mozilla-OID 1.3.6.1.4.1.13769.666.666.666.1.500.9.2
+ static const uint8_t tlv_some_Mozilla_OID[] = {
+ 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85,
+ 0x1a, 0x85, 0x1a, 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x02};
+
+ extension.id.assign(tlv_some_Mozilla_OID, sizeof(tlv_some_Mozilla_OID));
+ extension.critical = (aORT == ORTCriticalExtension);
+ extension.value.push_back(0x05); // tag: NULL
+ extension.value.push_back(0x00); // length: 0
+ extension.next = nullptr;
+ context.responseExtensions = &extension;
+ }
+ if (aORT == ORTEmptyExtensions) {
+ context.includeEmptyExtensions = true;
+ }
+
+ if (!signerCert) {
+ signerCert.reset(CERT_DupCertificate(issuerCert.get()));
+ }
+ context.signerKeyPair.reset(CreateTestKeyPairFromCert(signerCert));
+ if (!context.signerKeyPair) {
+ PrintPRError("PK11_FindKeyByAnyCert failed");
+ return nullptr;
+ }
+
+ ByteString response(CreateEncodedOCSPResponse(context));
+ if (ENCODING_FAILED(response)) {
+ PrintPRError("CreateEncodedOCSPResponse failed");
+ return nullptr;
+ }
+
+ SECItem item = {siBuffer, const_cast<uint8_t*>(response.data()),
+ static_cast<unsigned int>(response.length())};
+ SECItemArray arr = {&item, 1};
+ return SECITEM_DupArray(aArena.get(), &arr);
+}
diff --git a/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h
new file mode 100644
index 0000000000..c72eae6a8e
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Implements generating OCSP responses of various types. Used by the
+// programs in tlsserver/cmd.
+
+#ifndef OCSPCommon_h
+#define OCSPCommon_h
+
+#include "ScopedNSSTypes.h"
+#include "certt.h"
+#include "seccomon.h"
+
+enum OCSPResponseType {
+ ORTNull = 0,
+ ORTGood, // the certificate is good
+ ORTRevoked, // the certificate has been revoked
+ ORTRevokedOld, // same, but the response is old
+ ORTUnknown, // the responder doesn't know if the cert is good
+ ORTUnknownOld, // same, but the response is old
+ ORTGoodOtherCert, // the response references a different certificate
+ ORTGoodOtherCA, // the wrong CA has signed the response
+ ORTExpired, // the signature on the response has expired
+ ORTExpiredFreshCA, // fresh signature, but old validity period
+ ORTNone, // no stapled response
+ ORTEmpty, // an empty stapled response
+ ORTMalformed, // the response from the responder was malformed
+ ORTSrverr, // the response indicates there was a server error
+ ORTTryLater, // the responder replied with "try again later"
+ ORTNeedsSig, // the response needs a signature
+ ORTUnauthorized, // the responder is not authorized for this certificate
+ ORTBadSignature, // the response has a signature that does not verify
+ ORTSkipResponseBytes, // the response does not include responseBytes
+ ORTCriticalExtension, // the response includes a critical extension
+ ORTNoncriticalExtension, // the response includes an extension that is not
+ // critical
+ ORTEmptyExtensions, // the response includes a SEQUENCE OF Extension that is
+ // empty
+ ORTDelegatedIncluded, // the response is signed by an included delegated
+ // responder
+ ORTDelegatedIncludedLast, // same, but multiple other certificates are
+ // included
+ ORTDelegatedMissing, // the response is signed by a not included delegated
+ // responder
+ ORTDelegatedMissingMultiple, // same, but multiple other certificates are
+ // included
+ ORTLongValidityAlmostExpired, // a good response, but that was generated a
+ // almost a year ago
+ ORTAncientAlmostExpired, // a good response, with a validity of almost two
+ // years almost expiring
+};
+
+struct OCSPHost {
+ const char* mHostName;
+ OCSPResponseType mORT;
+ const char* mAdditionalCertName; // useful for ORTGoodOtherCert, etc.
+ const char* mServerCertName;
+};
+
+SECItemArray* GetOCSPResponseForType(
+ OCSPResponseType aORT, const mozilla::UniqueCERTCertificate& aCert,
+ const mozilla::UniquePLArenaPool& aArena, const char* aAdditionalCertName,
+ time_t aThisUpdateSkew);
+
+#endif // OCSPCommon_h
diff --git a/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp b/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp
new file mode 100644
index 0000000000..8eca895525
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp
@@ -0,0 +1,694 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TLSServer.h"
+
+#include <stdio.h>
+#include <string>
+#include <thread>
+#include <vector>
+#include <fstream>
+#include <iostream>
+#ifdef XP_WIN
+# include <windows.h>
+#else
+# include <unistd.h>
+#endif
+
+#include <utility>
+
+#include "base64.h"
+#include "mozilla/Sprintf.h"
+#include "nspr.h"
+#include "nss.h"
+#include "plarenas.h"
+#include "prenv.h"
+#include "prerror.h"
+#include "prnetdb.h"
+#include "prtime.h"
+#include "ssl.h"
+#include "sslexp.h"
+#include "sslproto.h"
+
+namespace mozilla {
+namespace test {
+
+static const uint16_t LISTEN_PORT = 8443;
+
+SSLAntiReplayContext* antiReplay = nullptr;
+
+DebugLevel gDebugLevel = DEBUG_ERRORS;
+uint16_t gCallbackPort = 0;
+
+const std::string kPEMBegin = "-----BEGIN ";
+const std::string kPEMEnd = "-----END ";
+const char DEFAULT_CERT_NICKNAME[] = "default-ee";
+
+struct Connection {
+ PRFileDesc* mSocket;
+ char mByte;
+
+ explicit Connection(PRFileDesc* aSocket);
+ ~Connection();
+};
+
+Connection::Connection(PRFileDesc* aSocket) : mSocket(aSocket), mByte(0) {}
+
+Connection::~Connection() {
+ if (mSocket) {
+ PR_Close(mSocket);
+ }
+}
+
+void PrintPRError(const char* aPrefix) {
+ const char* err = PR_ErrorToName(PR_GetError());
+ if (err) {
+ if (gDebugLevel >= DEBUG_ERRORS) {
+ fprintf(stderr, "%s: %s\n", aPrefix, err);
+ }
+ } else {
+ if (gDebugLevel >= DEBUG_ERRORS) {
+ fprintf(stderr, "%s\n", aPrefix);
+ }
+ }
+}
+
+// This decodes a PEM file into `item`. The line endings need to be
+// UNIX-style, or there will be cross-platform issues.
+static bool DecodePEMFile(const std::string& filename, SECItem* item) {
+ std::ifstream in(filename);
+ if (in.bad()) {
+ return false;
+ }
+
+ char buf[1024];
+ in.getline(buf, sizeof(buf));
+ if (in.bad()) {
+ return false;
+ }
+
+ if (strncmp(buf, kPEMBegin.c_str(), kPEMBegin.size()) != 0) {
+ return false;
+ }
+
+ std::string value;
+ for (;;) {
+ in.getline(buf, sizeof(buf));
+ if (in.bad()) {
+ return false;
+ }
+
+ if (strncmp(buf, kPEMEnd.c_str(), kPEMEnd.size()) == 0) {
+ break;
+ }
+
+ value += buf;
+ }
+
+ unsigned int binLength;
+ UniquePORTString bin(BitwiseCast<char*, unsigned char*>(
+ ATOB_AsciiToData(value.c_str(), &binLength)));
+ if (!bin || binLength == 0) {
+ PrintPRError("ATOB_AsciiToData failed");
+ return false;
+ }
+
+ if (SECITEM_AllocItem(nullptr, item, binLength) == nullptr) {
+ return false;
+ }
+
+ PORT_Memcpy(item->data, bin.get(), binLength);
+ return true;
+}
+
+static SECStatus AddKeyFromFile(const std::string& path,
+ const std::string& filename) {
+ ScopedAutoSECItem item;
+
+ std::string file = path + "/" + filename;
+ if (!DecodePEMFile(file, &item)) {
+ return SECFailure;
+ }
+
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ PrintPRError("PK11_GetInternalKeySlot failed");
+ return SECFailure;
+ }
+
+ if (PK11_NeedUserInit(slot.get())) {
+ if (PK11_InitPin(slot.get(), nullptr, nullptr) != SECSuccess) {
+ PrintPRError("PK11_InitPin failed");
+ return SECFailure;
+ }
+ }
+
+ SECKEYPrivateKey* privateKey = nullptr;
+ SECItem nick = {siBuffer,
+ BitwiseCast<unsigned char*, const char*>(filename.data()),
+ static_cast<unsigned int>(filename.size())};
+ if (PK11_ImportDERPrivateKeyInfoAndReturnKey(
+ slot.get(), &item, &nick, nullptr, true, false, KU_ALL, &privateKey,
+ nullptr) != SECSuccess) {
+ PrintPRError("PK11_ImportDERPrivateKeyInfoAndReturnKey failed");
+ return SECFailure;
+ }
+
+ SECKEY_DestroyPrivateKey(privateKey);
+ return SECSuccess;
+}
+
+static SECStatus AddCertificateFromFile(const std::string& path,
+ const std::string& filename) {
+ ScopedAutoSECItem item;
+
+ std::string file = path + "/" + filename;
+ if (!DecodePEMFile(file, &item)) {
+ return SECFailure;
+ }
+
+ UniqueCERTCertificate cert(CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &item, nullptr, false, true));
+ if (!cert) {
+ PrintPRError("CERT_NewTempCertificate failed");
+ return SECFailure;
+ }
+
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ PrintPRError("PK11_GetInternalKeySlot failed");
+ return SECFailure;
+ }
+ // The nickname is the filename without '.pem'.
+ std::string nickname = filename.substr(0, filename.length() - 4);
+ SECStatus rv = PK11_ImportCert(slot.get(), cert.get(), CK_INVALID_HANDLE,
+ nickname.c_str(), false);
+ if (rv != SECSuccess) {
+ PrintPRError("PK11_ImportCert failed");
+ return rv;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus LoadCertificatesAndKeys(const char* basePath) {
+ // The NSS cert DB path could have been specified as "sql:path". Trim off
+ // the leading "sql:" if so.
+ if (strncmp(basePath, "sql:", 4) == 0) {
+ basePath = basePath + 4;
+ }
+
+ UniquePRDir fdDir(PR_OpenDir(basePath));
+ if (!fdDir) {
+ PrintPRError("PR_OpenDir failed");
+ return SECFailure;
+ }
+ // On the B2G ICS emulator, operations taken in AddCertificateFromFile
+ // appear to interact poorly with readdir (more specifically, something is
+ // causing readdir to never return null - it indefinitely loops through every
+ // file in the directory, which causes timeouts). Rather than waste more time
+ // chasing this down, loading certificates and keys happens in two phases:
+ // filename collection and then loading. (This is probably a good
+ // idea anyway because readdir isn't reentrant. Something could change later
+ // such that it gets called as a result of calling AddCertificateFromFile or
+ // AddKeyFromFile.)
+ std::vector<std::string> certificates;
+ std::vector<std::string> keys;
+ for (PRDirEntry* dirEntry = PR_ReadDir(fdDir.get(), PR_SKIP_BOTH); dirEntry;
+ dirEntry = PR_ReadDir(fdDir.get(), PR_SKIP_BOTH)) {
+ size_t nameLength = strlen(dirEntry->name);
+ if (nameLength > 4) {
+ if (strncmp(dirEntry->name + nameLength - 4, ".pem", 4) == 0) {
+ certificates.push_back(dirEntry->name);
+ } else if (strncmp(dirEntry->name + nameLength - 4, ".key", 4) == 0) {
+ keys.push_back(dirEntry->name);
+ }
+ }
+ }
+ SECStatus rv;
+ for (std::string& certificate : certificates) {
+ rv = AddCertificateFromFile(basePath, certificate.c_str());
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ }
+ for (std::string& key : keys) {
+ rv = AddKeyFromFile(basePath, key.c_str());
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ }
+ return SECSuccess;
+}
+
+SECStatus InitializeNSS(const char* nssCertDBDir) {
+ // Try initializing an existing DB.
+ if (NSS_Init(nssCertDBDir) == SECSuccess) {
+ return SECSuccess;
+ }
+
+ // Create a new DB if there is none...
+ SECStatus rv = NSS_Initialize(nssCertDBDir, nullptr, nullptr, nullptr, 0);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ // ...and load all certificates into it.
+ return LoadCertificatesAndKeys(nssCertDBDir);
+}
+
+nsresult SendAll(PRFileDesc* aSocket, const char* aData, size_t aDataLen) {
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "sending '%s'\n", aData);
+ }
+
+ while (aDataLen > 0) {
+ int32_t bytesSent =
+ PR_Send(aSocket, aData, aDataLen, 0, PR_INTERVAL_NO_TIMEOUT);
+ if (bytesSent == -1) {
+ PrintPRError("PR_Send failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ aDataLen -= bytesSent;
+ aData += bytesSent;
+ }
+
+ return NS_OK;
+}
+
+nsresult ReplyToRequest(Connection* aConn) {
+ // For debugging purposes, SendAll can print out what it's sending.
+ // So, any strings we give to it to send need to be null-terminated.
+ char buf[2] = {aConn->mByte, 0};
+ return SendAll(aConn->mSocket, buf, 1);
+}
+
+nsresult SetupTLS(Connection* aConn, PRFileDesc* aModelSocket) {
+ PRFileDesc* sslSocket = SSL_ImportFD(aModelSocket, aConn->mSocket);
+ if (!sslSocket) {
+ PrintPRError("SSL_ImportFD failed");
+ return NS_ERROR_FAILURE;
+ }
+ aConn->mSocket = sslSocket;
+
+ /* anti-replay must be configured to accept 0RTT */
+ if (antiReplay) {
+ SECStatus rv = SSL_SetAntiReplayContext(sslSocket, antiReplay);
+ if (rv != SECSuccess) {
+ PrintPRError("error configuring anti-replay ");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ SSL_OptionSet(sslSocket, SSL_SECURITY, true);
+ SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, false);
+ SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_SERVER, true);
+ // Unconditionally enabling 0RTT makes test_session_resumption.js fail
+ SSL_OptionSet(sslSocket, SSL_ENABLE_0RTT_DATA,
+ !!PR_GetEnv("MOZ_TLS_SERVER_0RTT"));
+
+ SSL_ResetHandshake(sslSocket, /* asServer */ 1);
+
+ return NS_OK;
+}
+
+nsresult ReadRequest(Connection* aConn) {
+ int32_t bytesRead =
+ PR_Recv(aConn->mSocket, &aConn->mByte, 1, 0, PR_INTERVAL_NO_TIMEOUT);
+ if (bytesRead < 0) {
+ PrintPRError("PR_Recv failed");
+ return NS_ERROR_FAILURE;
+ } else if (bytesRead == 0) {
+ PR_SetError(PR_IO_ERROR, 0);
+ PrintPRError("PR_Recv EOF in ReadRequest");
+ return NS_ERROR_FAILURE;
+ } else {
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "read '0x%hhx'\n", aConn->mByte);
+ }
+ }
+ return NS_OK;
+}
+
+void HandleConnection(PRFileDesc* aSocket,
+ const UniquePRFileDesc& aModelSocket) {
+ Connection conn(aSocket);
+ nsresult rv = SetupTLS(&conn, aModelSocket.get());
+ if (NS_FAILED(rv)) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ PrintPRError("PR_Recv failed");
+ exit(1);
+ }
+
+ // TODO: On tests that are expected to fail (e.g. due to a revoked
+ // certificate), the client will close the connection wtihout sending us the
+ // request byte. In those cases, we should keep going. But, in the cases
+ // where the connection is supposed to suceed, we should verify that we
+ // successfully receive the request and send the response.
+ rv = ReadRequest(&conn);
+ if (NS_SUCCEEDED(rv)) {
+ rv = ReplyToRequest(&conn);
+ }
+}
+
+// returns 0 on success, non-zero on error
+int DoCallback() {
+ UniquePRFileDesc socket(PR_NewTCPSocket());
+ if (!socket) {
+ PrintPRError("PR_NewTCPSocket failed");
+ return 1;
+ }
+
+ PRNetAddr addr;
+ PR_InitializeNetAddr(PR_IpAddrLoopback, gCallbackPort, &addr);
+ if (PR_Connect(socket.get(), &addr, PR_INTERVAL_NO_TIMEOUT) != PR_SUCCESS) {
+ PrintPRError("PR_Connect failed");
+ return 1;
+ }
+
+ const char* request = "GET / HTTP/1.0\r\n\r\n";
+ SendAll(socket.get(), request, strlen(request));
+ char buf[4096];
+ memset(buf, 0, sizeof(buf));
+ int32_t bytesRead =
+ PR_Recv(socket.get(), buf, sizeof(buf) - 1, 0, PR_INTERVAL_NO_TIMEOUT);
+ if (bytesRead < 0) {
+ PrintPRError("PR_Recv failed 1");
+ return 1;
+ }
+ if (bytesRead == 0) {
+ fprintf(stderr, "PR_Recv eof 1\n");
+ return 1;
+ }
+ fprintf(stderr, "%s\n", buf);
+ return 0;
+}
+
+SECStatus ConfigSecureServerWithNamedCert(
+ PRFileDesc* fd, const char* certName,
+ /*optional*/ UniqueCERTCertificate* certOut,
+ /*optional*/ SSLKEAType* keaOut,
+ /*optional*/ SSLExtraServerCertData* extraData) {
+ UniqueCERTCertificate cert(PK11_FindCertFromNickname(certName, nullptr));
+ if (!cert) {
+ PrintPRError("PK11_FindCertFromNickname failed");
+ return SECFailure;
+ }
+ // If an intermediate certificate issued the server certificate (rather than
+ // directly by a trust anchor), we want to send it along in the handshake so
+ // we don't encounter unknown issuer errors when that's not what we're
+ // testing.
+ UniqueCERTCertificateList certList;
+ UniqueCERTCertificate issuerCert(
+ CERT_FindCertByName(CERT_GetDefaultCertDB(), &cert->derIssuer));
+ // If we can't find the issuer cert, continue without it.
+ if (issuerCert) {
+ // Sadly, CERTCertificateList does not have a CERT_NewCertificateList
+ // utility function, so we must create it ourselves. This consists
+ // of creating an arena, allocating space for the CERTCertificateList,
+ // and then transferring ownership of the arena to that list.
+ UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ PrintPRError("PORT_NewArena failed");
+ return SECFailure;
+ }
+ certList.reset(static_cast<CERTCertificateList*>(
+ PORT_ArenaAlloc(arena.get(), sizeof(CERTCertificateList))));
+ if (!certList) {
+ PrintPRError("PORT_ArenaAlloc failed");
+ return SECFailure;
+ }
+ certList->arena = arena.release();
+ // We also have to manually copy the certificates we care about to the
+ // list, because there aren't any utility functions for that either.
+ certList->certs = static_cast<SECItem*>(
+ PORT_ArenaAlloc(certList->arena, 2 * sizeof(SECItem)));
+ if (SECITEM_CopyItem(certList->arena, certList->certs, &cert->derCert) !=
+ SECSuccess) {
+ PrintPRError("SECITEM_CopyItem failed");
+ return SECFailure;
+ }
+ if (SECITEM_CopyItem(certList->arena, certList->certs + 1,
+ &issuerCert->derCert) != SECSuccess) {
+ PrintPRError("SECITEM_CopyItem failed");
+ return SECFailure;
+ }
+ certList->len = 2;
+ }
+
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ PrintPRError("PK11_GetInternalKeySlot failed");
+ return SECFailure;
+ }
+ UniqueSECKEYPrivateKey key(
+ PK11_FindKeyByDERCert(slot.get(), cert.get(), nullptr));
+ if (!key) {
+ PrintPRError("PK11_FindKeyByDERCert failed");
+ return SECFailure;
+ }
+
+ if (extraData) {
+ SSLExtraServerCertData dataCopy = {ssl_auth_null, nullptr, nullptr,
+ nullptr, nullptr, nullptr};
+ memcpy(&dataCopy, extraData, sizeof(dataCopy));
+ dataCopy.certChain = certList.get();
+
+ if (SSL_ConfigServerCert(fd, cert.get(), key.get(), &dataCopy,
+ sizeof(dataCopy)) != SECSuccess) {
+ PrintPRError("SSL_ConfigServerCert failed");
+ return SECFailure;
+ }
+
+ } else {
+ // This is the deprecated setup mechanism, to be cleaned up in Bug 1569222
+ SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
+ if (SSL_ConfigSecureServerWithCertChain(fd, cert.get(), certList.get(),
+ key.get(), certKEA) != SECSuccess) {
+ PrintPRError("SSL_ConfigSecureServer failed");
+ return SECFailure;
+ }
+
+ if (keaOut) {
+ *keaOut = certKEA;
+ }
+ }
+
+ if (certOut) {
+ *certOut = std::move(cert);
+ }
+
+ SSL_OptionSet(fd, SSL_NO_CACHE, false);
+ SSL_OptionSet(fd, SSL_ENABLE_SESSION_TICKETS, true);
+ // Unconditionally enabling 0RTT makes test_session_resumption.js fail
+ SSL_OptionSet(fd, SSL_ENABLE_0RTT_DATA, !!PR_GetEnv("MOZ_TLS_SERVER_0RTT"));
+
+ return SECSuccess;
+}
+
+#ifdef XP_WIN
+using PidType = DWORD;
+constexpr bool IsValidPid(long long pid) {
+ // Excluding `(DWORD)-1` because it is not a valid process ID.
+ // See https://devblogs.microsoft.com/oldnewthing/20040223-00/?p=40503
+ return pid > 0 && pid < std::numeric_limits<PidType>::max();
+}
+#else
+using PidType = pid_t;
+constexpr bool IsValidPid(long long pid) {
+ return pid > 0 && pid <= std::numeric_limits<PidType>::max();
+}
+#endif
+
+PidType ConvertPid(const char* pidStr) {
+ long long pid = strtoll(pidStr, nullptr, 10);
+ if (!IsValidPid(pid)) {
+ return 0;
+ }
+ return static_cast<PidType>(pid);
+}
+
+int StartServer(int argc, char* argv[], SSLSNISocketConfig sniSocketConfig,
+ void* sniSocketConfigArg, ServerConfigFunc configFunc) {
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s <NSS DB directory> <ppid>\n", argv[0]);
+ return 1;
+ }
+ const char* nssCertDBDir = argv[1];
+ PidType ppid = ConvertPid(argv[2]);
+
+ const char* debugLevel = PR_GetEnv("MOZ_TLS_SERVER_DEBUG_LEVEL");
+ if (debugLevel) {
+ int level = atoi(debugLevel);
+ switch (level) {
+ case DEBUG_ERRORS:
+ gDebugLevel = DEBUG_ERRORS;
+ break;
+ case DEBUG_WARNINGS:
+ gDebugLevel = DEBUG_WARNINGS;
+ break;
+ case DEBUG_VERBOSE:
+ gDebugLevel = DEBUG_VERBOSE;
+ break;
+ default:
+ PrintPRError("invalid MOZ_TLS_SERVER_DEBUG_LEVEL");
+ return 1;
+ }
+ }
+
+ const char* callbackPort = PR_GetEnv("MOZ_TLS_SERVER_CALLBACK_PORT");
+ if (callbackPort) {
+ gCallbackPort = atoi(callbackPort);
+ }
+
+ if (InitializeNSS(nssCertDBDir) != SECSuccess) {
+ PR_fprintf(PR_STDERR, "InitializeNSS failed");
+ return 1;
+ }
+
+ if (NSS_SetDomesticPolicy() != SECSuccess) {
+ PrintPRError("NSS_SetDomesticPolicy failed");
+ return 1;
+ }
+
+ if (SSL_ConfigServerSessionIDCache(0, 0, 0, nullptr) != SECSuccess) {
+ PrintPRError("SSL_ConfigServerSessionIDCache failed");
+ return 1;
+ }
+
+ UniquePRFileDesc serverSocket(PR_NewTCPSocket());
+ if (!serverSocket) {
+ PrintPRError("PR_NewTCPSocket failed");
+ return 1;
+ }
+
+ PRSocketOptionData socketOption;
+ socketOption.option = PR_SockOpt_Reuseaddr;
+ socketOption.value.reuse_addr = true;
+ PR_SetSocketOption(serverSocket.get(), &socketOption);
+
+ PRNetAddr serverAddr;
+ PR_InitializeNetAddr(PR_IpAddrLoopback, LISTEN_PORT, &serverAddr);
+ if (PR_Bind(serverSocket.get(), &serverAddr) != PR_SUCCESS) {
+ PrintPRError("PR_Bind failed");
+ return 1;
+ }
+
+ if (PR_Listen(serverSocket.get(), 1) != PR_SUCCESS) {
+ PrintPRError("PR_Listen failed");
+ return 1;
+ }
+
+ UniquePRFileDesc rawModelSocket(PR_NewTCPSocket());
+ if (!rawModelSocket) {
+ PrintPRError("PR_NewTCPSocket failed for rawModelSocket");
+ return 1;
+ }
+
+ UniquePRFileDesc modelSocket(SSL_ImportFD(nullptr, rawModelSocket.release()));
+ if (!modelSocket) {
+ PrintPRError("SSL_ImportFD of rawModelSocket failed");
+ return 1;
+ }
+
+ SSLVersionRange range = {0, 0};
+ if (SSL_VersionRangeGet(modelSocket.get(), &range) != SECSuccess) {
+ PrintPRError("SSL_VersionRangeGet failed");
+ return 1;
+ }
+
+ if (range.max < SSL_LIBRARY_VERSION_TLS_1_3) {
+ range.max = SSL_LIBRARY_VERSION_TLS_1_3;
+ if (SSL_VersionRangeSet(modelSocket.get(), &range) != SECSuccess) {
+ PrintPRError("SSL_VersionRangeSet failed");
+ return 1;
+ }
+ }
+
+ if (PR_GetEnv("MOZ_TLS_SERVER_0RTT")) {
+ if (SSL_CreateAntiReplayContext(PR_Now(), 1L * PR_USEC_PER_SEC, 7, 14,
+ &antiReplay) != SECSuccess) {
+ PrintPRError("Unable to create anti-replay context for 0-RTT.");
+ return 1;
+ }
+ }
+
+ if (SSL_SNISocketConfigHook(modelSocket.get(), sniSocketConfig,
+ sniSocketConfigArg) != SECSuccess) {
+ PrintPRError("SSL_SNISocketConfigHook failed");
+ return 1;
+ }
+
+ // We have to configure the server with a certificate, but it's not one
+ // we're actually going to end up using. In the SNI callback, we pick
+ // the right certificate for the connection.
+ //
+ // Provide an empty |extra_data| to force config via SSL_ConfigServerCert.
+ // This is a temporary mechanism to work around inconsistent setting of
+ // |authType| in the deprecated API (preventing the default cert from
+ // being removed in favor of the SNI-selected cert). This may be removed
+ // after Bug 1569222 removes the deprecated mechanism.
+ SSLExtraServerCertData extra_data = {ssl_auth_null, nullptr, nullptr,
+ nullptr, nullptr, nullptr};
+ if (ConfigSecureServerWithNamedCert(modelSocket.get(), DEFAULT_CERT_NICKNAME,
+ nullptr, nullptr,
+ &extra_data) != SECSuccess) {
+ return 1;
+ }
+
+ // Call back to implementation-defined configuration func, if provided.
+ if (configFunc) {
+ if (((configFunc)(modelSocket.get())) != SECSuccess) {
+ PrintPRError("configFunc failed");
+ return 1;
+ }
+ }
+
+ if (gCallbackPort != 0) {
+ if (DoCallback()) {
+ return 1;
+ }
+ }
+
+ std::thread([ppid] {
+ if (!ppid) {
+ if (gDebugLevel >= DEBUG_ERRORS) {
+ fprintf(stderr, "invalid ppid\n");
+ }
+ return;
+ }
+#ifdef XP_WIN
+ HANDLE parent = OpenProcess(SYNCHRONIZE, false, ppid);
+ if (!parent) {
+ if (gDebugLevel >= DEBUG_ERRORS) {
+ fprintf(stderr, "OpenProcess failed\n");
+ }
+ return;
+ }
+ WaitForSingleObject(parent, INFINITE);
+ CloseHandle(parent);
+#else
+ while (getppid() == ppid) {
+ sleep(1);
+ }
+#endif
+ if (gDebugLevel >= DEBUG_ERRORS) {
+ fprintf(stderr, "Parent process crashed\n");
+ }
+ exit(1);
+ }).detach();
+
+ while (true) {
+ PRNetAddr clientAddr;
+ PRFileDesc* clientSocket =
+ PR_Accept(serverSocket.get(), &clientAddr, PR_INTERVAL_NO_TIMEOUT);
+ HandleConnection(clientSocket, modelSocket);
+ }
+
+ return 0;
+}
+
+} // namespace test
+} // namespace mozilla
diff --git a/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.h b/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.h
new file mode 100644
index 0000000000..3927b3e541
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.h
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TLSServer_h
+#define TLSServer_h
+
+// This is a standalone server for testing SSL features of Gecko.
+// The client is expected to connect and initiate an SSL handshake (with SNI
+// to indicate which "server" to connect to). If all is good, the client then
+// sends one encrypted byte and receives that same byte back.
+// This server also has the ability to "call back" another process waiting on
+// it. That is, when the server is all set up and ready to receive connections,
+// it will connect to a specified port and issue a simple HTTP request.
+
+#include <stdint.h>
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Casting.h"
+#include "prio.h"
+#include "secerr.h"
+#include "ssl.h"
+
+namespace mozilla {
+
+MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniquePRDir, PRDir, PR_CloseDir);
+
+} // namespace mozilla
+
+namespace mozilla {
+namespace test {
+
+typedef SECStatus (*ServerConfigFunc)(PRFileDesc* fd);
+
+enum DebugLevel { DEBUG_ERRORS = 1, DEBUG_WARNINGS = 2, DEBUG_VERBOSE = 3 };
+
+extern DebugLevel gDebugLevel;
+
+void PrintPRError(const char* aPrefix);
+
+// The default certificate is trusted for localhost and *.example.com
+extern const char DEFAULT_CERT_NICKNAME[];
+
+// ConfigSecureServerWithNamedCert sets up the hostname name provided. If the
+// extraData parameter is presented, extraData->certChain will be automatically
+// filled in using database information.
+// Pass DEFAULT_CERT_NICKNAME as certName unless you need a specific
+// certificate.
+SECStatus ConfigSecureServerWithNamedCert(
+ PRFileDesc* fd, const char* certName,
+ /*optional*/ UniqueCERTCertificate* cert,
+ /*optional*/ SSLKEAType* kea,
+ /*optional*/ SSLExtraServerCertData* extraData);
+
+SECStatus InitializeNSS(const char* nssCertDBDir);
+
+// StartServer initializes NSS, sockets, the SNI callback, and a default
+// certificate. configFunc (optional) is a pointer to an implementation-
+// defined configuration function, which is called on the model socket
+// prior to handling any connections.
+int StartServer(int argc, char* argv[], SSLSNISocketConfig sniSocketConfig,
+ void* sniSocketConfigArg,
+ ServerConfigFunc configFunc = nullptr);
+
+template <typename Host>
+inline const Host* GetHostForSNI(const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, const Host* hosts) {
+ for (uint32_t i = 0; i < aSrvNameArrSize; i++) {
+ for (const Host* host = hosts; host->mHostName; ++host) {
+ SECItem hostName;
+ hostName.data = BitwiseCast<unsigned char*, const char*>(host->mHostName);
+ hostName.len = strlen(host->mHostName);
+ if (SECITEM_ItemsAreEqual(&hostName, &aSrvNameArr[i])) {
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
+ }
+ return host;
+ }
+ }
+ }
+
+ if (gDebugLevel >= DEBUG_VERBOSE) {
+ fprintf(stderr, "could not find host info from SNI\n");
+ }
+
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+ return nullptr;
+}
+
+} // namespace test
+} // namespace mozilla
+
+#endif // TLSServer_h
diff --git a/security/manager/ssl/tests/unit/tlsserver/lib/moz.build b/security/manager/ssl/tests/unit/tlsserver/lib/moz.build
new file mode 100644
index 0000000000..8420cbd87f
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/moz.build
@@ -0,0 +1,50 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "OCSPCommon.cpp",
+ "TLSServer.cpp",
+]
+
+USE_LIBS += [
+ "mozpkix-testlib",
+]
+
+if not CONFIG["MOZ_SYSTEM_NSS"]:
+ # Bug 1805371: The FaultyServer binary added in Bug 1754746 needs to
+ # be statically linked against NSS, but the configuration here breaks
+ # builds with system NSS. A complete solution involves some changes
+ # to the NSS build system. For now we're disabling FaultyServer when
+ # using system NSS and linking the rest of the tests dynamically.
+ DEFINES["NSS_USE_STATIC_LIBS"] = True
+
+ USE_LIBS += [
+ "certdb",
+ "certhi",
+ "cryptohi",
+ "freebl",
+ "mozpkix",
+ "mozpkix-testlib",
+ "nspr",
+ "nss_static",
+ "nssb",
+ "nssdev",
+ "nsspki",
+ "pk11wrap",
+ "smime",
+ "softokn3",
+ "sqlite",
+ "ssl",
+ ]
+
+ if CONFIG["MOZ_FOLD_LIBS"]:
+ USE_LIBS += ["nssutil"]
+ else:
+ USE_LIBS += ["nssutil3"]
+
+Library("tlsserver")
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/security/manager/ssl/tests/unit/tlsserver/moz.build b/security/manager/ssl/tests/unit/tlsserver/moz.build
new file mode 100644
index 0000000000..1488352914
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# lib must be first, because cmd depends on its output
+DIRS += ["lib", "cmd"]