summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/tlsserver/cmd
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/cmd
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.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/cmd')
-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
8 files changed, 1120 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"]