diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /security/manager/ssl/tests/unit/tlsserver | |
parent | Initial commit. (diff) | |
download | firefox-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')
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"] |