summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/tlsserver
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/tests/unit/tlsserver')
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp141
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp170
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp129
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/cmd/moz.build25
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/default-ee.der3
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp215
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h61
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp600
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.h89
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/lib/moz.build17
-rw-r--r--security/manager/ssl/tests/unit/tlsserver/moz.build8
11 files changed, 1458 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp
new file mode 100644
index 000000000..348dcfc5c
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.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 BadCertHost
+{
+ const char *mHostName;
+ const char *mCertName;
+};
+
+// Hostname, cert nickname pairs.
+const BadCertHost sBadCertHosts[] =
+{
+ { "expired.example.com", "expired-ee" },
+ { "notyetvalid.example.com", "notYetValid" },
+ { "before-epoch.example.com", "beforeEpoch" },
+ { "selfsigned.example.com", "selfsigned" },
+ { "unknownissuer.example.com", "unknownissuer" },
+ { "mismatch.example.com", "mismatch" },
+ { "mismatch-CN.example.com", "mismatchCN" },
+ { "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" },
+ { 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)
+ == SECSuccess) {
+ return 0;
+ }
+ }
+ }
+
+ return SSL_SNI_SEND_ALERT;
+}
+
+int32_t
+DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
+ uint32_t aSrvNameArrSize, void* aArg)
+{
+ const BadCertHost* host = GetHostForSNI(aSrvNameArr, aSrvNameArrSize,
+ sBadCertHosts);
+ 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)) {
+ return SSL_SNI_SEND_ALERT;
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <NSS DB directory>\n", argv[0]);
+ return 1;
+ }
+
+ return StartServer(argv[1], DoSNISocketConfig, nullptr);
+}
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 000000000..775f1f06e
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp
@@ -0,0 +1,170 @@
+/* -*- 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 "base64.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 (uint32_t i = 0; i < mozilla::ArrayLength(kOCSPResponseNameList); i++) {
+ if (strcmp(respText, kOCSPResponseNameList[i].mTypeString) == 0) {
+ *OCSPType = kOCSPResponseNameList[i].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 < 6 || (argc - 6) % 4 != 0) {
+ PR_fprintf(PR_STDERR, "usage: %s <NSS DB directory> <responsetype> "
+ "<cert_nick> <extranick> <outfilename> [<resptype> "
+ "<cert_nick> <extranick> <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 += 4) {
+ const char* ocspTypeText = argv[i];
+ const char* certNick = argv[i + 1];
+ const char* extraCertname = argv[i + 2];
+ const char* filename = argv[i + 3];
+
+ 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);
+ }
+
+ SECItemArray* response = GetOCSPResponseForType(ORT, cert, arena,
+ extraCertname);
+ 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 000000000..6bf33a143
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp
@@ -0,0 +1,129 @@
+/* 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" },
+ { "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)) {
+ 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);
+ 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[])
+{
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <NSS DB directory>\n", argv[0]);
+ return 1;
+ }
+
+ return StartServer(argv[1], 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 000000000..6fdd0c35b
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/moz.build
@@ -0,0 +1,25 @@
+# -*- 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([
+ 'BadCertServer',
+ 'GenerateOCSPResponse',
+ 'OCSPStaplingServer',
+], linkage=None)
+
+LOCAL_INCLUDES += [
+ '../lib',
+]
+
+USE_LIBS += [
+ 'mozillapkix',
+ 'nspr',
+ 'nss',
+ 'pkixtestutil',
+ 'tlsserver',
+]
+
+CXXFLAGS += CONFIG['TK_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 000000000..3a9b8fa9b
--- /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 000000000..5c99edade
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
@@ -0,0 +1,215 @@
+/* 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 "pkixtestutil.h"
+#include "TLSServer.h"
+#include "secder.h"
+#include "secerr.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+// Ownership of privateKey is transfered.
+TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
+ const SECKEYPublicKey& publicKey,
+ SECKEYPrivateKey* privateKey);
+
+} } } // namespace mozilla::pkix::test
+
+using namespace mozilla;
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+using namespace mozilla::test;
+
+static TestKeyPair*
+CreateTestKeyPairFromCert(const UniqueCERTCertificate& cert)
+{
+ UniqueSECKEYPrivateKey privateKey(PK11_FindKeyByAnyCert(cert.get(), nullptr));
+ if (!privateKey) {
+ return nullptr;
+ }
+ UniqueSECKEYPublicKey publicKey(CERT_ExtractPublicKey(cert.get()));
+ if (!publicKey) {
+ return nullptr;
+ }
+ return CreateTestKeyPair(RSA_PKCS1(), *publicKey.get(), privateKey.release());
+}
+
+SECItemArray*
+GetOCSPResponseForType(OCSPResponseType aORT, const UniqueCERTCertificate& aCert,
+ const UniquePLArenaPool& aArena,
+ const char* aAdditionalCertName)
+{
+ 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);
+ 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 000000000..5a88d6e52
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h
@@ -0,0 +1,61 @@
+/* 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);
+
+#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 000000000..fcf6aa951
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp
@@ -0,0 +1,600 @@
+/* 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 <vector>
+
+#include "base64.h"
+#include "mozilla/Move.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"
+
+namespace mozilla { namespace test {
+
+static const uint16_t LISTEN_PORT = 8443;
+
+DebugLevel gDebugLevel = DEBUG_ERRORS;
+uint16_t gCallbackPort = 0;
+
+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);
+ }
+ }
+}
+
+template <size_t N>
+SECStatus
+ReadFileToBuffer(const char* basePath, const char* filename, char (&buf)[N])
+{
+ static_assert(N > 0, "input buffer too small for ReadFileToBuffer");
+ if (snprintf(buf, N - 1, "%s/%s", basePath, filename) == 0) {
+ PrintPRError("snprintf failed");
+ return SECFailure;
+ }
+ UniquePRFileDesc fd(PR_OpenFile(buf, PR_RDONLY, 0));
+ if (!fd) {
+ PrintPRError("PR_Open failed");
+ return SECFailure;
+ }
+ int32_t fileSize = PR_Available(fd.get());
+ if (fileSize < 0) {
+ PrintPRError("PR_Available failed");
+ return SECFailure;
+ }
+ if (static_cast<size_t>(fileSize) > N - 1) {
+ PR_fprintf(PR_STDERR, "file too large - not reading\n");
+ return SECFailure;
+ }
+ int32_t bytesRead = PR_Read(fd.get(), buf, fileSize);
+ if (bytesRead != fileSize) {
+ PrintPRError("PR_Read failed");
+ return SECFailure;
+ }
+ buf[bytesRead] = 0;
+ return SECSuccess;
+}
+
+SECStatus
+AddKeyFromFile(const char* basePath, const char* filename)
+{
+ const char* PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----";
+ const char* PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----";
+
+ char buf[16384] = { 0 };
+ SECStatus rv = ReadFileToBuffer(basePath, filename, buf);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ if (strncmp(buf, PRIVATE_KEY_HEADER, strlen(PRIVATE_KEY_HEADER)) != 0) {
+ PR_fprintf(PR_STDERR, "invalid key - not importing\n");
+ return SECFailure;
+ }
+ const char* bufPtr = buf + strlen(PRIVATE_KEY_HEADER);
+ size_t bufLen = strlen(buf);
+ char base64[16384] = { 0 };
+ char* base64Ptr = base64;
+ while (bufPtr < buf + bufLen) {
+ if (strncmp(bufPtr, PRIVATE_KEY_FOOTER, strlen(PRIVATE_KEY_FOOTER)) == 0) {
+ break;
+ }
+ if (*bufPtr != '\r' && *bufPtr != '\n') {
+ *base64Ptr = *bufPtr;
+ base64Ptr++;
+ }
+ bufPtr++;
+ }
+
+ unsigned int binLength;
+ UniquePORTString bin(
+ BitwiseCast<char*, unsigned char*>(ATOB_AsciiToData(base64, &binLength)));
+ if (!bin || binLength == 0) {
+ PrintPRError("ATOB_AsciiToData failed");
+ return SECFailure;
+ }
+ UniqueSECItem secitem(::SECITEM_AllocItem(nullptr, nullptr, binLength));
+ if (!secitem) {
+ PrintPRError("SECITEM_AllocItem failed");
+ return SECFailure;
+ }
+ PORT_Memcpy(secitem->data, bin.get(), binLength);
+ 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;
+ if (PK11_ImportDERPrivateKeyInfoAndReturnKey(slot.get(), secitem.get(),
+ nullptr, nullptr, true, false,
+ KU_ALL, &privateKey, nullptr)
+ != SECSuccess) {
+ PrintPRError("PK11_ImportDERPrivateKeyInfoAndReturnKey failed");
+ return SECFailure;
+ }
+ SECKEY_DestroyPrivateKey(privateKey);
+ return SECSuccess;
+}
+
+SECStatus
+DecodeCertCallback(void* arg, SECItem** certs, int numcerts)
+{
+ if (numcerts != 1) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ SECItem* certDEROut = static_cast<SECItem*>(arg);
+ return SECITEM_CopyItem(nullptr, certDEROut, *certs);
+}
+
+SECStatus
+AddCertificateFromFile(const char* basePath, const char* filename)
+{
+ char buf[16384] = { 0 };
+ SECStatus rv = ReadFileToBuffer(basePath, filename, buf);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ ScopedAutoSECItem certDER;
+ rv = CERT_DecodeCertPackage(buf, strlen(buf), DecodeCertCallback, &certDER);
+ if (rv != SECSuccess) {
+ PrintPRError("CERT_DecodeCertPackage failed");
+ return rv;
+ }
+ UniqueCERTCertificate cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+ &certDER, 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, strlen(filename) - 4);
+ 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;
+
+ SSL_OptionSet(sslSocket, SSL_SECURITY, true);
+ SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, false);
+ SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_SERVER, true);
+
+ 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)
+{
+ 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;
+ }
+
+ 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 (certOut) {
+ *certOut = Move(cert);
+ }
+
+ if (keaOut) {
+ *keaOut = certKEA;
+ }
+
+ SSL_OptionSet(fd, SSL_NO_CACHE, false);
+ SSL_OptionSet(fd, SSL_ENABLE_SESSION_TICKETS, true);
+
+ return SECSuccess;
+}
+
+int
+StartServer(const char *nssCertDBDir, SSLSNISocketConfig sniSocketConfig,
+ void *sniSocketConfigArg)
+{
+ 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;
+ }
+
+ 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.
+ if (ConfigSecureServerWithNamedCert(modelSocket.get(), DEFAULT_CERT_NICKNAME,
+ nullptr, nullptr) != SECSuccess) {
+ return 1;
+ }
+
+ if (gCallbackPort != 0) {
+ if (DoCallback()) {
+ return 1;
+ }
+ }
+
+ while (true) {
+ PRNetAddr clientAddr;
+ PRFileDesc* clientSocket = PR_Accept(serverSocket.get(), &clientAddr,
+ PR_INTERVAL_NO_TIMEOUT);
+ HandleConnection(clientSocket, modelSocket);
+ }
+
+ return 0;
+}
+
+} } // namespace mozilla::test
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 000000000..f1dc1569a
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.h
@@ -0,0 +1,89 @@
+/* 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 {
+
+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[];
+
+// 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);
+
+SECStatus
+InitializeNSS(const char* nssCertDBDir);
+
+int
+StartServer(const char *nssCertDBDir, SSLSNISocketConfig sniSocketConfig,
+ void *sniSocketConfigArg);
+
+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 mozilla::test
+
+#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 000000000..5b28e4a55
--- /dev/null
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/moz.build
@@ -0,0 +1,17 @@
+# -*- 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',
+]
+
+LOCAL_INCLUDES += [
+ '../../../../../../pkix/include',
+ '../../../../../../pkix/test/lib',
+]
+
+Library('tlsserver')
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 000000000..96fb65d06
--- /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']