summaryrefslogtreecommitdiffstats
path: root/security/nss/cmd/tstclnt
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /security/nss/cmd/tstclnt
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/cmd/tstclnt')
-rw-r--r--security/nss/cmd/tstclnt/Makefile46
-rw-r--r--security/nss/cmd/tstclnt/manifest.mn23
-rw-r--r--security/nss/cmd/tstclnt/tstclnt.c2437
-rw-r--r--security/nss/cmd/tstclnt/tstclnt.gyp31
4 files changed, 2537 insertions, 0 deletions
diff --git a/security/nss/cmd/tstclnt/Makefile b/security/nss/cmd/tstclnt/Makefile
new file mode 100644
index 0000000000..aae7b445cb
--- /dev/null
+++ b/security/nss/cmd/tstclnt/Makefile
@@ -0,0 +1,46 @@
+#! gmake
+#
+# 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/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY). #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL) #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL) #
+#######################################################################
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL). #
+#######################################################################
+
+include ../platlibs.mk
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL) #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL) #
+#######################################################################
+
+#include ../platlibs.mk
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL). #
+#######################################################################
+
+include ../platrules.mk
+
diff --git a/security/nss/cmd/tstclnt/manifest.mn b/security/nss/cmd/tstclnt/manifest.mn
new file mode 100644
index 0000000000..5c1e4f6114
--- /dev/null
+++ b/security/nss/cmd/tstclnt/manifest.mn
@@ -0,0 +1,23 @@
+#
+# 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/.
+
+CORE_DEPTH = ../..
+
+# MODULE public and private header directories are implicitly REQUIRED.
+MODULE = nss
+
+# This next line is used by .mk files
+# and gets translated into $LINCS in manifest.mnw
+# The MODULE is always implicitly required.
+# Listing it here in REQUIRES makes it appear twice in the cc command line.
+REQUIRES = seccmd dbm
+
+# DIRS =
+
+CSRCS = tstclnt.c
+DEFINES += -DDLL_PREFIX=\"$(DLL_PREFIX)\" -DDLL_SUFFIX=\"$(DLL_SUFFIX)\"
+
+PROGRAM = tstclnt
+
diff --git a/security/nss/cmd/tstclnt/tstclnt.c b/security/nss/cmd/tstclnt/tstclnt.c
new file mode 100644
index 0000000000..cbf824ec18
--- /dev/null
+++ b/security/nss/cmd/tstclnt/tstclnt.c
@@ -0,0 +1,2437 @@
+/* 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/. */
+
+/*
+**
+** Sample client side test program that uses SSL and NSS
+**
+*/
+
+#include "secutil.h"
+#include "basicutil.h"
+
+#if defined(XP_UNIX)
+#include <unistd.h>
+#else
+#include <ctype.h> /* for isalpha() */
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+
+#include "nspr.h"
+#include "prio.h"
+#include "prnetdb.h"
+#include "nss.h"
+#include "nssb64.h"
+#include "ocsp.h"
+#include "ssl.h"
+#include "sslproto.h"
+#include "sslexp.h"
+#include "pk11func.h"
+#include "secmod.h"
+#include "plgetopt.h"
+#include "plstr.h"
+
+#if defined(WIN32)
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+#define PRINTF \
+ if (verbose) \
+ printf
+#define FPRINTF \
+ if (verbose) \
+ fprintf
+
+#define MAX_WAIT_FOR_SERVER 600
+#define WAIT_INTERVAL 100
+#define ZERO_RTT_MAX (2 << 16)
+
+#define EXIT_CODE_HANDSHAKE_FAILED 254
+
+#define EXIT_CODE_SIDECHANNELTEST_GOOD 0
+#define EXIT_CODE_SIDECHANNELTEST_BADCERT 1
+#define EXIT_CODE_SIDECHANNELTEST_NODATA 2
+#define EXIT_CODE_SIDECHANNELTEST_REVOKED 3
+
+PRIntervalTime maxInterval = PR_INTERVAL_NO_TIMEOUT;
+
+int ssl3CipherSuites[] = {
+ -1, /* SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA* a */
+ -1, /* SSL_FORTEZZA_DMS_WITH_RC4_128_SHA * b */
+ TLS_RSA_WITH_RC4_128_MD5, /* c */
+ TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* d */
+ TLS_RSA_WITH_DES_CBC_SHA, /* e */
+ -1, /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 * f */
+ -1, /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 * g */
+ -1, /* SSL_FORTEZZA_DMS_WITH_NULL_SHA * h */
+ TLS_RSA_WITH_NULL_MD5, /* i */
+ -1, /* SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA * j */
+ -1, /* SSL_RSA_FIPS_WITH_DES_CBC_SHA * k */
+ -1, /* TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA * l */
+ -1, /* TLS_RSA_EXPORT1024_WITH_RC4_56_SHA * m */
+ TLS_RSA_WITH_RC4_128_SHA, /* n */
+ TLS_DHE_DSS_WITH_RC4_128_SHA, /* o */
+ TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* p */
+ TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* q */
+ TLS_DHE_RSA_WITH_DES_CBC_SHA, /* r */
+ TLS_DHE_DSS_WITH_DES_CBC_SHA, /* s */
+ TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* t */
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* u */
+ TLS_RSA_WITH_AES_128_CBC_SHA, /* v */
+ TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* w */
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* x */
+ TLS_RSA_WITH_AES_256_CBC_SHA, /* y */
+ TLS_RSA_WITH_NULL_SHA, /* z */
+ 0
+};
+
+unsigned long __cmp_umuls;
+PRBool verbose;
+int dumpServerChain = 0;
+int renegotiationsToDo = 0;
+int renegotiationsDone = 0;
+PRBool initializedServerSessionCache = PR_FALSE;
+
+static char *progName;
+static const char *requestFile;
+
+secuPWData pwdata = { PW_NONE, 0 };
+
+SSLNamedGroup *enabledGroups = NULL;
+unsigned int enabledGroupsCount = 0;
+const SSLSignatureScheme *enabledSigSchemes = NULL;
+unsigned int enabledSigSchemeCount = 0;
+SECItem psk = { siBuffer, NULL, 0 };
+SECItem pskLabel = { siBuffer, NULL, 0 };
+
+const char *
+signatureSchemeName(SSLSignatureScheme scheme)
+{
+ switch (scheme) {
+#define strcase(x) \
+ case ssl_sig_##x: \
+ return #x
+ strcase(none);
+ strcase(rsa_pkcs1_sha1);
+ strcase(rsa_pkcs1_sha256);
+ strcase(rsa_pkcs1_sha384);
+ strcase(rsa_pkcs1_sha512);
+ strcase(ecdsa_sha1);
+ strcase(ecdsa_secp256r1_sha256);
+ strcase(ecdsa_secp384r1_sha384);
+ strcase(ecdsa_secp521r1_sha512);
+ strcase(rsa_pss_rsae_sha256);
+ strcase(rsa_pss_rsae_sha384);
+ strcase(rsa_pss_rsae_sha512);
+ strcase(ed25519);
+ strcase(ed448);
+ strcase(rsa_pss_pss_sha256);
+ strcase(rsa_pss_pss_sha384);
+ strcase(rsa_pss_pss_sha512);
+ strcase(dsa_sha1);
+ strcase(dsa_sha256);
+ strcase(dsa_sha384);
+ strcase(dsa_sha512);
+#undef strcase
+ case ssl_sig_rsa_pkcs1_sha1md5:
+ return "RSA PKCS#1 SHA1+MD5";
+ default:
+ break;
+ }
+ return "Unknown Scheme";
+}
+
+void
+printSecurityInfo(PRFileDesc *fd)
+{
+ CERTCertificate *cert;
+ const SECItemArray *csa;
+ const SECItem *scts;
+ SSL3Statistics *ssl3stats = SSL_GetStatistics();
+ SECStatus result;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ result = SSL_GetChannelInfo(fd, &channel, sizeof channel);
+ if (result == SECSuccess &&
+ channel.length == sizeof channel &&
+ channel.cipherSuite) {
+ result = SSL_GetCipherSuiteInfo(channel.cipherSuite,
+ &suite, sizeof suite);
+ if (result == SECSuccess) {
+ FPRINTF(stderr,
+ "tstclnt: SSL version %d.%d using %d-bit %s with %d-bit %s MAC%s\n",
+ channel.protocolVersion >> 8, channel.protocolVersion & 0xff,
+ suite.effectiveKeyBits, suite.symCipherName,
+ suite.macBits, suite.macAlgorithmName,
+ channel.isFIPS ? " FIPS" : "");
+ FPRINTF(stderr,
+ "tstclnt: Server Auth: %d-bit %s, Key Exchange: %d-bit %s\n"
+ " Compression: %s, Extended Master Secret: %s\n"
+ " Signature Scheme: %s\n",
+ channel.authKeyBits, suite.authAlgorithmName,
+ channel.keaKeyBits, suite.keaTypeName,
+ channel.compressionMethodName,
+ channel.extendedMasterSecretUsed ? "Yes" : "No",
+ signatureSchemeName(channel.signatureScheme));
+ }
+ }
+ cert = SSL_RevealCert(fd);
+ if (cert) {
+ char *ip = CERT_NameToAscii(&cert->issuer);
+ char *sp = CERT_NameToAscii(&cert->subject);
+ if (sp) {
+ fprintf(stderr, "subject DN: %s\n", sp);
+ PORT_Free(sp);
+ }
+ if (ip) {
+ fprintf(stderr, "issuer DN: %s\n", ip);
+ PORT_Free(ip);
+ }
+ CERT_DestroyCertificate(cert);
+ cert = NULL;
+ }
+ fprintf(stderr,
+ "%ld cache hits; %ld cache misses, %ld cache not reusable\n"
+ "%ld stateless resumes\n",
+ ssl3stats->hsh_sid_cache_hits, ssl3stats->hsh_sid_cache_misses,
+ ssl3stats->hsh_sid_cache_not_ok, ssl3stats->hsh_sid_stateless_resumes);
+
+ csa = SSL_PeerStapledOCSPResponses(fd);
+ if (csa) {
+ fprintf(stderr, "Received %d Cert Status items (OCSP stapled data)\n",
+ csa->len);
+ }
+ scts = SSL_PeerSignedCertTimestamps(fd);
+ if (scts && scts->len) {
+ fprintf(stderr, "Received a Signed Certificate Timestamp of length"
+ " %u\n",
+ scts->len);
+ }
+ if (channel.peerDelegCred) {
+ fprintf(stderr, "Received a Delegated Credential\n");
+ }
+}
+
+static void
+PrintUsageHeader()
+{
+ fprintf(stderr,
+ "Usage: %s -h host [-a 1st_hs_name ] [-a 2nd_hs_name ] [-p port]\n"
+ " [-D | -d certdir] [-C] [-b | -R root-module] \n"
+ " [-n nickname] [-Bafosvx] [-c ciphers] [-Y] [-Z] [-E]\n"
+ " [-V [min-version]:[max-version]] [-K] [-T] [-U]\n"
+ " [-r N] [-w passwd] [-W pwfile] [-q [-t seconds]]\n"
+ " [-I groups] [-J signatureschemes]\n"
+ " [-A requestfile] [-L totalconnections] [-P {client,server}]\n"
+ " [-N echConfigs] [-Q] [-z externalPsk]\n"
+ " [-i echGreaseSize]\n"
+ "\n",
+ progName);
+}
+
+static void
+PrintParameterUsage()
+{
+ fprintf(stderr, "%-20s Send different SNI name. 1st_hs_name - at first\n"
+ "%-20s handshake, 2nd_hs_name - at second handshake.\n"
+ "%-20s Default is host from the -h argument.\n",
+ "-a name",
+ "", "");
+ fprintf(stderr, "%-20s Hostname to connect with\n", "-h host");
+ fprintf(stderr, "%-20s Port number for SSL server\n", "-p port");
+ fprintf(stderr,
+ "%-20s Directory with cert database (default is ~/.netscape)\n",
+ "-d certdir");
+ fprintf(stderr, "%-20s Run without a cert database\n", "-D");
+ fprintf(stderr, "%-20s Load the default \"builtins\" root CA module\n", "-b");
+ fprintf(stderr, "%-20s Load the given root CA module\n", "-R");
+ fprintf(stderr, "%-20s Print certificate chain information\n", "-C");
+ fprintf(stderr, "%-20s (use -C twice to print more certificate details)\n", "");
+ fprintf(stderr, "%-20s (use -C three times to include PEM format certificate dumps)\n", "");
+ fprintf(stderr, "%-20s Nickname of key and cert\n",
+ "-n nickname");
+ fprintf(stderr,
+ "%-20s Restricts the set of enabled SSL/TLS protocols versions.\n"
+ "%-20s All versions are enabled by default.\n"
+ "%-20s Possible values for min/max: ssl3 tls1.0 tls1.1 tls1.2 tls1.3\n"
+ "%-20s Example: \"-V ssl3:\" enables SSL 3 and newer.\n",
+ "-V [min]:[max]", "", "", "");
+ fprintf(stderr, "%-20s Send TLS_FALLBACK_SCSV\n", "-K");
+ fprintf(stderr, "%-20s Prints only payload data. Skips HTTP header.\n", "-S");
+ fprintf(stderr, "%-20s Client speaks first. \n", "-f");
+ fprintf(stderr, "%-20s Use synchronous certificate selection & validation\n", "-O");
+ fprintf(stderr, "%-20s Override bad server cert. Make it OK.\n", "-o");
+ fprintf(stderr, "%-20s Disable SSL socket locking.\n", "-s");
+ fprintf(stderr, "%-20s Verbose progress reporting.\n", "-v");
+ fprintf(stderr, "%-20s Ping the server and then exit.\n", "-q");
+ fprintf(stderr, "%-20s Timeout for server ping (default: no timeout).\n", "-t seconds");
+ fprintf(stderr, "%-20s Renegotiate N times (resuming session if N>1).\n", "-r N");
+ fprintf(stderr, "%-20s Enable the session ticket extension.\n", "-u");
+ fprintf(stderr, "%-20s Enable false start.\n", "-g");
+ fprintf(stderr, "%-20s Enable the cert_status extension (OCSP stapling).\n", "-T");
+ fprintf(stderr, "%-20s Enable the signed_certificate_timestamp extension.\n", "-U");
+ fprintf(stderr, "%-20s Enable the delegated credentials extension.\n", "-B");
+ fprintf(stderr, "%-20s Require fresh revocation info from side channel.\n"
+ "%-20s -F once means: require for server cert only\n"
+ "%-20s -F twice means: require for intermediates, too\n"
+ "%-20s (Connect, handshake with server, disable dynamic download\n"
+ "%-20s of OCSP/CRL, verify cert using CERT_PKIXVerifyCert.)\n"
+ "%-20s Exit code:\n"
+ "%-20s 0: have fresh and valid revocation data, status good\n"
+ "%-20s 1: cert failed to verify, prior to revocation checking\n"
+ "%-20s 2: missing, old or invalid revocation data\n"
+ "%-20s 3: have fresh and valid revocation data, status revoked\n",
+ "-F", "", "", "", "", "", "", "", "", "");
+ fprintf(stderr, "%-20s Test -F allows 0=any (default), 1=only OCSP, 2=only CRL\n", "-M");
+ fprintf(stderr, "%-20s Restrict ciphers\n", "-c ciphers");
+ fprintf(stderr, "%-20s Print cipher values allowed for parameter -c and exit\n", "-Y");
+ fprintf(stderr, "%-20s Enforce using an IPv4 destination address\n", "-4");
+ fprintf(stderr, "%-20s Enforce using an IPv6 destination address\n", "-6");
+ fprintf(stderr, "%-20s (Options -4 and -6 cannot be combined.)\n", "");
+ fprintf(stderr, "%-20s Enable the extended master secret extension [RFC7627]\n", "-G");
+ fprintf(stderr, "%-20s Require the use of FFDHE supported groups [RFC7919]\n", "-H");
+ fprintf(stderr, "%-20s Read from a file instead of stdin\n", "-A");
+ fprintf(stderr, "%-20s Allow 0-RTT data (TLS 1.3 only)\n", "-Z");
+ fprintf(stderr, "%-20s Disconnect and reconnect up to N times total\n", "-L");
+ fprintf(stderr, "%-20s Comma separated list of enabled groups for TLS key exchange.\n"
+ "%-20s The following values are valid:\n"
+ "%-20s P256, P384, P521, x25519, FF2048, FF3072, FF4096, FF6144, FF8192\n",
+ "-I", "", "");
+ fprintf(stderr, "%-20s Comma separated list of signature schemes in preference order.\n"
+ "%-20s The following values are valid:\n"
+ "%-20s rsa_pkcs1_sha1, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512,\n"
+ "%-20s ecdsa_sha1, ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384,\n"
+ "%-20s ecdsa_secp521r1_sha512,\n"
+ "%-20s rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512,\n"
+ "%-20s rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512,\n"
+ "%-20s dsa_sha1, dsa_sha256, dsa_sha384, dsa_sha512\n",
+ "-J", "", "", "", "", "", "", "");
+ fprintf(stderr, "%-20s Use DTLS\n", "-P {client, server}");
+ fprintf(stderr, "%-20s Exit after handshake\n", "-Q");
+ fprintf(stderr, "%-20s Use Encrypted Client Hello with the given Base64-encoded ECHConfigs\n", "-N");
+ fprintf(stderr, "%-20s Enable Encrypted Client Hello GREASEing with the given padding size (0-255) \n", "-i");
+ fprintf(stderr, "%-20s Enable post-handshake authentication\n"
+ "%-20s for TLS 1.3; need to specify -n\n",
+ "-E", "");
+ fprintf(stderr, "%-20s Export and print keying material after successful handshake.\n"
+ "%-20s The argument is a comma separated list of exporters in the form:\n"
+ "%-20s LABEL[:OUTPUT-LENGTH[:CONTEXT]]\n"
+ "%-20s where LABEL and CONTEXT can be either a free-form string or\n"
+ "%-20s a hex string if it is preceded by \"0x\"; OUTPUT-LENGTH\n"
+ "%-20s is a decimal integer.\n",
+ "-x", "", "", "", "", "");
+ fprintf(stderr,
+ "%-20s Configure a TLS 1.3 External PSK with the given hex string for a key\n"
+ "%-20s To specify a label, use ':' as a delimiter. For example\n"
+ "%-20s 0xAAAABBBBCCCCDDDD:mylabel. Otherwise, the default label of\n"
+ "%-20s 'Client_identity' will be used.\n",
+ "-z externalPsk", "", "", "");
+ fprintf(stderr, "%-20s Enable middlebox compatibility mode (TLS 1.3 only)\n", "-e");
+}
+
+static void
+Usage()
+{
+ PrintUsageHeader();
+ PrintParameterUsage();
+ exit(1);
+}
+
+static void
+PrintCipherUsage()
+{
+ PrintUsageHeader();
+ fprintf(stderr, "%-20s Letter(s) chosen from the following list\n",
+ "-c ciphers");
+ fprintf(stderr,
+ "c SSL3 RSA WITH RC4 128 MD5\n"
+ "d SSL3 RSA WITH 3DES EDE CBC SHA\n"
+ "e SSL3 RSA WITH DES CBC SHA\n"
+ "i SSL3 RSA WITH NULL MD5\n"
+ "n SSL3 RSA WITH RC4 128 SHA\n"
+ "o SSL3 DHE DSS WITH RC4 128 SHA\n"
+ "p SSL3 DHE RSA WITH 3DES EDE CBC SHA\n"
+ "q SSL3 DHE DSS WITH 3DES EDE CBC SHA\n"
+ "r SSL3 DHE RSA WITH DES CBC SHA\n"
+ "s SSL3 DHE DSS WITH DES CBC SHA\n"
+ "t SSL3 DHE DSS WITH AES 128 CBC SHA\n"
+ "u SSL3 DHE RSA WITH AES 128 CBC SHA\n"
+ "v SSL3 RSA WITH AES 128 CBC SHA\n"
+ "w SSL3 DHE DSS WITH AES 256 CBC SHA\n"
+ "x SSL3 DHE RSA WITH AES 256 CBC SHA\n"
+ "y SSL3 RSA WITH AES 256 CBC SHA\n"
+ "z SSL3 RSA WITH NULL SHA\n"
+ "\n"
+ ":WXYZ Use cipher with hex code { 0xWX , 0xYZ } in TLS\n");
+ exit(1);
+}
+
+void
+milliPause(PRUint32 milli)
+{
+ PRIntervalTime ticks = PR_MillisecondsToInterval(milli);
+ PR_Sleep(ticks);
+}
+
+void
+disableAllSSLCiphers()
+{
+ const PRUint16 *cipherSuites = SSL_GetImplementedCiphers();
+ int i = SSL_GetNumImplementedCiphers();
+ SECStatus rv;
+
+ /* disable all the SSL3 cipher suites */
+ while (--i >= 0) {
+ PRUint16 suite = cipherSuites[i];
+ rv = SSL_CipherPrefSetDefault(suite, PR_FALSE);
+ if (rv != SECSuccess) {
+ PRErrorCode err = PR_GetError();
+ fprintf(stderr,
+ "SSL_CipherPrefSet didn't like value 0x%04x (i = %d): %s\n",
+ suite, i, SECU_Strerror(err));
+ exit(2);
+ }
+ }
+}
+
+typedef struct
+{
+ PRBool shouldPause; /* PR_TRUE if we should use asynchronous peer cert
+ * authentication */
+ PRBool isPaused; /* PR_TRUE if libssl is waiting for us to validate the
+ * peer's certificate and restart the handshake. */
+ void *dbHandle; /* Certificate database handle to use while
+ * authenticating the peer's certificate. */
+ PRBool testFreshStatusFromSideChannel;
+ PRErrorCode sideChannelRevocationTestResultCode;
+ PRBool requireDataForIntermediates;
+ PRBool allowOCSPSideChannelData;
+ PRBool allowCRLSideChannelData;
+} ServerCertAuth;
+
+/*
+ * Callback is called when incoming certificate is not valid.
+ * Returns SECSuccess to accept the cert anyway, SECFailure to reject.
+ */
+static SECStatus
+ownBadCertHandler(void *arg, PRFileDesc *socket)
+{
+ PRErrorCode err = PR_GetError();
+ /* can log invalid cert here */
+ fprintf(stderr, "Bad server certificate: %d, %s\n", err,
+ SECU_Strerror(err));
+ return SECSuccess; /* override, say it's OK. */
+}
+
+#define EXIT_CODE_SIDECHANNELTEST_GOOD 0
+#define EXIT_CODE_SIDECHANNELTEST_BADCERT 1
+#define EXIT_CODE_SIDECHANNELTEST_NODATA 2
+#define EXIT_CODE_SIDECHANNELTEST_REVOKED 3
+
+static void
+verifyFromSideChannel(CERTCertificate *cert, ServerCertAuth *sca)
+{
+ PRUint64 revDoNotUse =
+ CERT_REV_M_DO_NOT_TEST_USING_THIS_METHOD;
+
+ PRUint64 revUseLocalOnlyAndSoftFail =
+ CERT_REV_M_TEST_USING_THIS_METHOD |
+ CERT_REV_M_FORBID_NETWORK_FETCHING |
+ CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE |
+ CERT_REV_M_IGNORE_MISSING_FRESH_INFO |
+ CERT_REV_M_STOP_TESTING_ON_FRESH_INFO;
+
+ PRUint64 revUseLocalOnlyAndHardFail =
+ CERT_REV_M_TEST_USING_THIS_METHOD |
+ CERT_REV_M_FORBID_NETWORK_FETCHING |
+ CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE |
+ CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO |
+ CERT_REV_M_STOP_TESTING_ON_FRESH_INFO;
+
+ PRUint64 methodFlagsDoNotUse[2];
+ PRUint64 methodFlagsCheckSoftFail[2];
+ PRUint64 methodFlagsCheckHardFail[2];
+ CERTRevocationTests revTestsDoNotCheck;
+ CERTRevocationTests revTestsOverallSoftFail;
+ CERTRevocationTests revTestsOverallHardFail;
+ CERTRevocationFlags rev;
+ CERTValInParam cvin[2];
+ CERTValOutParam cvout[1];
+ SECStatus rv;
+
+ methodFlagsDoNotUse[cert_revocation_method_crl] = revDoNotUse;
+ methodFlagsDoNotUse[cert_revocation_method_ocsp] = revDoNotUse;
+
+ methodFlagsCheckSoftFail[cert_revocation_method_crl] =
+ sca->allowCRLSideChannelData ? revUseLocalOnlyAndSoftFail : revDoNotUse;
+ methodFlagsCheckSoftFail[cert_revocation_method_ocsp] =
+ sca->allowOCSPSideChannelData ? revUseLocalOnlyAndSoftFail : revDoNotUse;
+
+ methodFlagsCheckHardFail[cert_revocation_method_crl] =
+ sca->allowCRLSideChannelData ? revUseLocalOnlyAndHardFail : revDoNotUse;
+ methodFlagsCheckHardFail[cert_revocation_method_ocsp] =
+ sca->allowOCSPSideChannelData ? revUseLocalOnlyAndHardFail : revDoNotUse;
+
+ revTestsDoNotCheck.cert_rev_flags_per_method = methodFlagsDoNotUse;
+ revTestsDoNotCheck.number_of_defined_methods = 2;
+ revTestsDoNotCheck.number_of_preferred_methods = 0;
+ revTestsDoNotCheck.cert_rev_method_independent_flags =
+ CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST |
+ CERT_REV_MI_NO_OVERALL_INFO_REQUIREMENT;
+
+ revTestsOverallSoftFail.cert_rev_flags_per_method = 0; /* must define later */
+ revTestsOverallSoftFail.number_of_defined_methods = 2;
+ revTestsOverallSoftFail.number_of_preferred_methods = 0;
+ revTestsOverallSoftFail.cert_rev_method_independent_flags =
+ CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST |
+ CERT_REV_MI_NO_OVERALL_INFO_REQUIREMENT;
+
+ revTestsOverallHardFail.cert_rev_flags_per_method = 0; /* must define later */
+ revTestsOverallHardFail.number_of_defined_methods = 2;
+ revTestsOverallHardFail.number_of_preferred_methods = 0;
+ revTestsOverallHardFail.cert_rev_method_independent_flags =
+ CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST |
+ CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE;
+
+ rev.chainTests = revTestsDoNotCheck;
+ rev.leafTests = revTestsDoNotCheck;
+
+ cvin[0].type = cert_pi_revocationFlags;
+ cvin[0].value.pointer.revocation = &rev;
+ cvin[1].type = cert_pi_end;
+
+ cvout[0].type = cert_po_end;
+
+ /* Strategy:
+ *
+ * Verify with revocation checking disabled.
+ * On failure return 1.
+ *
+ * if result if "good", then continue testing.
+ *
+ * Verify with CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO.
+ * If result is good, return 0.
+ *
+ * On failure continue testing, find out why it failed.
+ *
+ * Verify with CERT_REV_M_IGNORE_MISSING_FRESH_INFO
+ *
+ * If result is "good", then our previous test failed,
+ * because we don't have fresh revocation info, return 2.
+ *
+ * If result is still bad, we do have revocation info,
+ * and it says "revoked" or something equivalent, return 3.
+ */
+
+ /* revocation checking disabled */
+ rv = CERT_PKIXVerifyCert(cert, certificateUsageSSLServer,
+ cvin, cvout, NULL);
+ if (rv != SECSuccess) {
+ sca->sideChannelRevocationTestResultCode =
+ EXIT_CODE_SIDECHANNELTEST_BADCERT;
+ return;
+ }
+
+ /* revocation checking, hard fail */
+ if (sca->allowOCSPSideChannelData && sca->allowCRLSideChannelData) {
+ /* any method is allowed. use soft fail on individual checks,
+ * but use hard fail on the overall check
+ */
+ revTestsOverallHardFail.cert_rev_flags_per_method = methodFlagsCheckSoftFail;
+ } else {
+ /* only one method is allowed. use hard fail on the individual checks.
+ * hard/soft fail is irrelevant on overall flags.
+ */
+ revTestsOverallHardFail.cert_rev_flags_per_method = methodFlagsCheckHardFail;
+ }
+ rev.leafTests = revTestsOverallHardFail;
+ rev.chainTests =
+ sca->requireDataForIntermediates ? revTestsOverallHardFail : revTestsDoNotCheck;
+ rv = CERT_PKIXVerifyCert(cert, certificateUsageSSLServer,
+ cvin, cvout, NULL);
+ if (rv == SECSuccess) {
+ sca->sideChannelRevocationTestResultCode =
+ EXIT_CODE_SIDECHANNELTEST_GOOD;
+ return;
+ }
+
+ /* revocation checking, soft fail */
+ revTestsOverallSoftFail.cert_rev_flags_per_method = methodFlagsCheckSoftFail;
+ rev.leafTests = revTestsOverallSoftFail;
+ rev.chainTests =
+ sca->requireDataForIntermediates ? revTestsOverallSoftFail : revTestsDoNotCheck;
+ rv = CERT_PKIXVerifyCert(cert, certificateUsageSSLServer,
+ cvin, cvout, NULL);
+ if (rv == SECSuccess) {
+ sca->sideChannelRevocationTestResultCode =
+ EXIT_CODE_SIDECHANNELTEST_NODATA;
+ return;
+ }
+
+ sca->sideChannelRevocationTestResultCode =
+ EXIT_CODE_SIDECHANNELTEST_REVOKED;
+}
+
+static void
+dumpCertificatePEM(CERTCertificate *cert)
+{
+ SECItem data;
+ data.data = cert->derCert.data;
+ data.len = cert->derCert.len;
+ fprintf(stderr, "%s\n%s\n%s\n", NS_CERT_HEADER,
+ BTOA_DataToAscii(data.data, data.len), NS_CERT_TRAILER);
+}
+
+static void
+dumpServerCertificateChain(PRFileDesc *fd)
+{
+ CERTCertList *peerCertChain = NULL;
+ CERTCertListNode *node = NULL;
+ CERTCertificate *peerCert = NULL;
+ CERTCertificateList *foundChain = NULL;
+ SECU_PPFunc dumpFunction = NULL;
+ PRBool dumpCertPEM = PR_FALSE;
+
+ if (!dumpServerChain) {
+ return;
+ } else if (dumpServerChain == 1) {
+ dumpFunction = (SECU_PPFunc)SECU_PrintCertificateBasicInfo;
+ } else {
+ dumpFunction = (SECU_PPFunc)SECU_PrintCertificate;
+ if (dumpServerChain > 2) {
+ dumpCertPEM = PR_TRUE;
+ }
+ }
+
+ SECU_EnableWrap(PR_FALSE);
+
+ fprintf(stderr, "==== certificate(s) sent by server: ====\n");
+ peerCertChain = SSL_PeerCertificateChain(fd);
+ if (peerCertChain) {
+ node = CERT_LIST_HEAD(peerCertChain);
+ while (!CERT_LIST_END(node, peerCertChain)) {
+ CERTCertificate *cert = node->cert;
+ SECU_PrintSignedContent(stderr, &cert->derCert, "Certificate", 0,
+ dumpFunction);
+ if (dumpCertPEM) {
+ dumpCertificatePEM(cert);
+ }
+ node = CERT_LIST_NEXT(node);
+ }
+ }
+
+ if (peerCertChain) {
+ peerCert = SSL_RevealCert(fd);
+ if (peerCert) {
+ foundChain = CERT_CertChainFromCert(peerCert, certificateUsageSSLServer,
+ PR_TRUE);
+ }
+ if (foundChain) {
+ unsigned int count = 0;
+ fprintf(stderr, "==== locally found issuer certificate(s): ====\n");
+ for (count = 0; count < (unsigned int)foundChain->len; count++) {
+ CERTCertificate *c;
+ PRBool wasSentByServer = PR_FALSE;
+ c = CERT_FindCertByDERCert(CERT_GetDefaultCertDB(), &foundChain->certs[count]);
+
+ node = CERT_LIST_HEAD(peerCertChain);
+ while (!CERT_LIST_END(node, peerCertChain)) {
+ CERTCertificate *cert = node->cert;
+ if (CERT_CompareCerts(cert, c)) {
+ wasSentByServer = PR_TRUE;
+ break;
+ }
+ node = CERT_LIST_NEXT(node);
+ }
+
+ if (!wasSentByServer) {
+ SECU_PrintSignedContent(stderr, &c->derCert, "Certificate", 0,
+ dumpFunction);
+ if (dumpCertPEM) {
+ dumpCertificatePEM(c);
+ }
+ }
+ CERT_DestroyCertificate(c);
+ }
+ CERT_DestroyCertificateList(foundChain);
+ }
+ if (peerCert) {
+ CERT_DestroyCertificate(peerCert);
+ }
+
+ CERT_DestroyCertList(peerCertChain);
+ peerCertChain = NULL;
+ }
+
+ fprintf(stderr, "==== end of certificate chain information ====\n");
+ fflush(stderr);
+}
+
+static SECStatus
+ownAuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig,
+ PRBool isServer)
+{
+ ServerCertAuth *serverCertAuth = (ServerCertAuth *)arg;
+
+ if (dumpServerChain) {
+ dumpServerCertificateChain(fd);
+ }
+
+ if (!serverCertAuth->shouldPause) {
+ CERTCertificate *cert;
+ unsigned int i;
+ const SECItemArray *csa;
+
+ if (!serverCertAuth->testFreshStatusFromSideChannel) {
+ return SSL_AuthCertificate(serverCertAuth->dbHandle,
+ fd, checkSig, isServer);
+ }
+
+ /* No verification attempt must have happened before now,
+ * to ensure revocation data has been actively retrieved yet,
+ * or our test will produce incorrect results.
+ */
+
+ cert = SSL_RevealCert(fd);
+ if (!cert) {
+ exit(254);
+ }
+
+ csa = SSL_PeerStapledOCSPResponses(fd);
+ if (csa) {
+ for (i = 0; i < csa->len; ++i) {
+ PORT_SetError(0);
+ if (CERT_CacheOCSPResponseFromSideChannel(
+ serverCertAuth->dbHandle, cert, PR_Now(),
+ &csa->items[i], arg) != SECSuccess) {
+ PORT_Assert(PR_GetError() != 0);
+ }
+ }
+ }
+
+ verifyFromSideChannel(cert, serverCertAuth);
+ CERT_DestroyCertificate(cert);
+ /* return success to ensure our caller will continue and we will
+ * reach the code that handles
+ * serverCertAuth->sideChannelRevocationTestResultCode
+ */
+ return SECSuccess;
+ }
+
+ FPRINTF(stderr, "%s: using asynchronous certificate validation\n",
+ progName);
+
+ PORT_Assert(!serverCertAuth->isPaused);
+ serverCertAuth->isPaused = PR_TRUE;
+ return SECWouldBlock;
+}
+
+struct clientCertAsyncParamsStr {
+ void *arg; /* The nickname used for selection, not owned */
+ struct CERTDistNamesStr *caNames; /* CA Names specified by Server, owned. */
+};
+
+/* tstclnt can only have a single handshake in progress at any instant. */
+PRBool clientCertAsyncSelect = PR_TRUE; /* Async by default */
+PRBool clientCertIsBlocked = PR_FALSE; /* Whether we waiting to finish ClientAuth */
+struct clientCertAsyncParamsStr *clientCertParams = NULL;
+
+SECStatus
+own_CompleteClientAuthData(PRFileDesc *fd, struct clientCertAsyncParamsStr *args)
+{
+ SECStatus rv;
+ CERTCertificate *pRetCert = NULL;
+ SECKEYPrivateKey *pRetKey = NULL;
+ rv = NSS_GetClientAuthData(args->arg, fd, args->caNames, &pRetCert, &pRetKey);
+ if (rv != SECSuccess) {
+ fprintf(stderr, "Failed to load a suitable client certificate \n");
+ }
+ return SSL_ClientCertCallbackComplete(fd, rv, pRetKey, pRetCert);
+}
+
+SECStatus
+restartHandshakeAfterClientCertIfNeeded(PRFileDesc *fd)
+{
+ if (!clientCertIsBlocked) {
+ return SECFailure;
+ }
+ clientCertIsBlocked = PR_FALSE;
+ own_CompleteClientAuthData(fd, clientCertParams);
+ CERT_FreeDistNames(clientCertParams->caNames);
+ PORT_Free(clientCertParams);
+ clientCertParams = NULL;
+ return SECSuccess;
+}
+
+SECStatus
+own_GetClientAuthData(void *arg,
+ PRFileDesc *socket,
+ struct CERTDistNamesStr *caNames,
+ struct CERTCertificateStr **pRetCert,
+ struct SECKEYPrivateKeyStr **pRetKey)
+{
+ if (clientCertAsyncSelect) {
+ PR_ASSERT(!clientCertIsBlocked);
+ PR_ASSERT(!clientCertParams);
+
+ clientCertIsBlocked = PR_TRUE;
+ clientCertParams = PORT_Alloc(sizeof(struct clientCertAsyncParamsStr));
+ if (!clientCertParams) {
+ fprintf(stderr, "Unable to allocate buffer for client cert callback\n");
+ exit(1);
+ }
+
+ clientCertParams->arg = arg;
+ clientCertParams->caNames = caNames ? CERT_DupDistNames(caNames) : NULL;
+ if (caNames && !clientCertParams->caNames) {
+ fprintf(stderr, "Unable to allocate buffer for client cert callback\n");
+ exit(1);
+ }
+ return SECWouldBlock;
+ }
+
+ if (verbose > 1) {
+ SECStatus rv;
+ fprintf(stderr, "Server requested Client Authentication\n");
+ if (caNames && caNames->nnames > 0) {
+ PLArenaPool *arena = caNames->arena;
+ if (!arena)
+ arena = PORT_NewArena(2048);
+ if (arena) {
+ int i;
+ for (i = 0; i < caNames->nnames; ++i) {
+ char *nameString;
+ CERTName dn;
+ rv = SEC_QuickDERDecodeItem(arena,
+ &dn,
+ SEC_ASN1_GET(CERT_NameTemplate),
+ caNames->names + i);
+ if (rv != SECSuccess)
+ continue;
+ nameString = CERT_NameToAscii(&dn);
+ if (!nameString)
+ continue;
+ fprintf(stderr, "CA[%d]: %s\n", i + 1, nameString);
+ PORT_Free(nameString);
+ }
+ if (!caNames->arena) {
+ PORT_FreeArena(arena, PR_FALSE);
+ }
+ }
+ }
+ rv = NSS_GetClientAuthData(arg, socket, caNames, pRetCert, pRetKey);
+ if (rv == SECSuccess && *pRetCert) {
+ char *nameString = CERT_NameToAscii(&((*pRetCert)->subject));
+ if (nameString) {
+ fprintf(stderr, "sent cert: %s\n", nameString);
+ PORT_Free(nameString);
+ }
+ } else {
+ fprintf(stderr, "send no cert\n");
+ }
+ return rv;
+ }
+ return NSS_GetClientAuthData(arg, socket, caNames, pRetCert, pRetKey);
+}
+
+#if defined(WIN32) || defined(OS2)
+void
+thread_main(void *arg)
+{
+ PRFileDesc *ps = (PRFileDesc *)arg;
+ PRFileDesc *std_in;
+ int wc, rc;
+ char buf[256];
+
+ if (requestFile) {
+ std_in = PR_Open(requestFile, PR_RDONLY, 0);
+ } else {
+ std_in = PR_GetSpecialFD(PR_StandardInput);
+ }
+
+#ifdef WIN32
+ if (!requestFile) {
+ /* Put stdin into O_BINARY mode
+ ** or else incoming \r\n's will become \n's.
+ */
+ int smrv = _setmode(_fileno(stdin), _O_BINARY);
+ if (smrv == -1) {
+ fprintf(stderr,
+ "%s: Cannot change stdin to binary mode. Use -i option instead.\n",
+ progName);
+ /* plow ahead anyway */
+ }
+ }
+#endif
+
+ do {
+ rc = PR_Read(std_in, buf, sizeof buf);
+ if (rc <= 0)
+ break;
+ wc = PR_Send(ps, buf, rc, 0, maxInterval);
+ } while (wc == rc);
+ PR_Close(ps);
+ if (requestFile) {
+ PR_Close(std_in);
+ }
+}
+
+#endif
+
+static void
+printHostNameAndAddr(const char *host, const PRNetAddr *addr)
+{
+ PRUint16 port = PR_NetAddrInetPort(addr);
+ char addrBuf[80];
+ PRStatus st = PR_NetAddrToString(addr, addrBuf, sizeof addrBuf);
+
+ if (st == PR_SUCCESS) {
+ port = PR_ntohs(port);
+ FPRINTF(stderr, "%s: connecting to %s:%hu (address=%s)\n",
+ progName, host, port, addrBuf);
+ }
+}
+
+/*
+ * Prints output according to skipProtoHeader flag. If skipProtoHeader
+ * is not set, prints without any changes, otherwise looking
+ * for \n\r\n(empty line sequence: HTTP header separator) and
+ * prints everything after it.
+ */
+static void
+separateReqHeader(const PRFileDesc *outFd, const char *buf, const int nb,
+ PRBool *wrStarted, int *ptrnMatched)
+{
+
+ /* it is sufficient to look for only "\n\r\n". Hopping that
+ * HTTP response format satisfies the standard */
+ char *ptrnStr = "\n\r\n";
+ char *resPtr;
+
+ if (nb == 0) {
+ return;
+ }
+
+ if (*ptrnMatched > 0) {
+ /* Get here only if previous separateReqHeader call found
+ * only a fragment of "\n\r\n" in previous buffer. */
+ PORT_Assert(*ptrnMatched < 3);
+
+ /* the size of fragment of "\n\r\n" what we want to find in this
+ * buffer is equal to *ptrnMatched */
+ if (*ptrnMatched <= nb) {
+ /* move the pointer to the beginning of the fragment */
+ int strSize = *ptrnMatched;
+ char *tmpPtrn = ptrnStr + (3 - strSize);
+ if (PL_strncmp(buf, tmpPtrn, strSize) == 0) {
+ /* print the rest of the buffer(without the fragment) */
+ PR_Write((void *)outFd, buf + strSize, nb - strSize);
+ *wrStarted = PR_TRUE;
+ return;
+ }
+ } else {
+ /* we are here only when nb == 1 && *ptrnMatched == 2 */
+ if (*buf == '\r') {
+ *ptrnMatched = 1;
+ } else {
+ *ptrnMatched = 0;
+ }
+ return;
+ }
+ }
+ resPtr = PL_strnstr(buf, ptrnStr, nb);
+ if (resPtr != NULL) {
+ /* if "\n\r\n" was found in the buffer, calculate offset
+ * and print the rest of the buffer */
+ int newBn = nb - (resPtr - buf + 3); /* 3 is the length of "\n\r\n" */
+
+ PR_Write((void *)outFd, resPtr + 3, newBn);
+ *wrStarted = PR_TRUE;
+ return;
+ } else {
+ /* try to find a fragment of "\n\r\n" at the end of the buffer.
+ * if found, set *ptrnMatched to the number of chars left to find
+ * in the next buffer.*/
+ int i;
+ for (i = 1; i < 3; i++) {
+ char *bufPrt;
+ int strSize = 3 - i;
+
+ if (strSize > nb) {
+ continue;
+ }
+ bufPrt = (char *)(buf + nb - strSize);
+
+ if (PL_strncmp(bufPrt, ptrnStr, strSize) == 0) {
+ *ptrnMatched = i;
+ return;
+ }
+ }
+ }
+}
+
+#define SSOCK_FD 0
+#define STDIN_FD 1
+
+#define HEXCHAR_TO_INT(c, i) \
+ if (((c) >= '0') && ((c) <= '9')) { \
+ i = (c) - '0'; \
+ } else if (((c) >= 'a') && ((c) <= 'f')) { \
+ i = (c) - 'a' + 10; \
+ } else if (((c) >= 'A') && ((c) <= 'F')) { \
+ i = (c) - 'A' + 10; \
+ } else { \
+ Usage(); \
+ }
+
+static SECStatus
+restartHandshakeAfterServerCertIfNeeded(PRFileDesc *fd,
+ ServerCertAuth *serverCertAuth,
+ PRBool override)
+{
+ SECStatus rv;
+ PRErrorCode error = 0;
+
+ if (!serverCertAuth->isPaused || clientCertIsBlocked)
+ return SECSuccess;
+
+ FPRINTF(stderr, "%s: handshake was paused by auth certificate hook\n",
+ progName);
+
+ serverCertAuth->isPaused = PR_FALSE;
+ rv = SSL_AuthCertificate(serverCertAuth->dbHandle, fd, PR_TRUE, PR_FALSE);
+ if (rv != SECSuccess) {
+ error = PR_GetError();
+ if (error == 0) {
+ PR_NOT_REACHED("SSL_AuthCertificate return SECFailure without "
+ "setting error code.");
+ error = PR_INVALID_STATE_ERROR;
+ } else if (override) {
+ rv = ownBadCertHandler(NULL, fd);
+ }
+ }
+ if (rv == SECSuccess) {
+ error = 0;
+ }
+
+ if (SSL_AuthCertificateComplete(fd, error) != SECSuccess) {
+ rv = SECFailure;
+ } else {
+ /* restore the original error code, which could be reset by
+ * SSL_AuthCertificateComplete */
+ PORT_SetError(error);
+ }
+
+ return rv;
+}
+
+char *host = NULL;
+char *nickname = NULL;
+char *cipherString = NULL;
+int multiplier = 0;
+SSLVersionRange enabledVersions;
+int disableLocking = 0;
+int enableSessionTickets = 0;
+int enableFalseStart = 0;
+int enableCertStatus = 0;
+int enableSignedCertTimestamps = 0;
+int forceFallbackSCSV = 0;
+int enableExtendedMasterSecret = 0;
+PRBool requireDHNamedGroups = 0;
+PRBool middleboxCompatMode = 0;
+PRSocketOptionData opt;
+PRNetAddr addr;
+PRBool allowIPv4 = PR_TRUE;
+PRBool allowIPv6 = PR_TRUE;
+PRBool pingServerFirst = PR_FALSE;
+int pingTimeoutSeconds = -1;
+PRBool clientSpeaksFirst = PR_FALSE;
+PRBool skipProtoHeader = PR_FALSE;
+ServerCertAuth serverCertAuth;
+char *hs1SniHostName = NULL;
+char *hs2SniHostName = NULL;
+PRUint16 portno = 443;
+int override = 0;
+PRBool enableZeroRtt = PR_FALSE;
+PRUint8 *zeroRttData;
+unsigned int zeroRttLen = 0;
+PRBool enableAltServerHello = PR_FALSE;
+PRBool useDTLS = PR_FALSE;
+PRBool actAsServer = PR_FALSE;
+PRBool stopAfterHandshake = PR_FALSE;
+PRBool requestToExit = PR_FALSE;
+char *versionString = NULL;
+PRBool handshakeComplete = PR_FALSE;
+char *echConfigs = NULL;
+PRUint16 echGreaseSize = 0;
+PRBool enablePostHandshakeAuth = PR_FALSE;
+PRBool enableDelegatedCredentials = PR_FALSE;
+const secuExporter *enabledExporters = NULL;
+unsigned int enabledExporterCount = 0;
+
+static int
+writeBytesToServer(PRFileDesc *s, const PRUint8 *buf, int nb)
+{
+ SECStatus rv;
+ const PRUint8 *bufp = buf;
+ PRPollDesc pollDesc;
+
+ pollDesc.in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pollDesc.out_flags = 0;
+ pollDesc.fd = s;
+
+ FPRINTF(stderr, "%s: Writing %d bytes to server\n",
+ progName, nb);
+ do {
+ PRInt32 cc = PR_Send(s, bufp, nb, 0, maxInterval);
+ if (cc < 0) {
+ PRErrorCode err = PR_GetError();
+ if (err != PR_WOULD_BLOCK_ERROR) {
+ SECU_PrintError(progName, "write to SSL socket failed");
+ return 254;
+ }
+ cc = 0;
+ }
+ FPRINTF(stderr, "%s: %d bytes written\n", progName, cc);
+ if (enableZeroRtt && !handshakeComplete) {
+ if (zeroRttLen + cc > ZERO_RTT_MAX) {
+ SECU_PrintError(progName, "too much early data to save");
+ return -1;
+ }
+ PORT_Memcpy(zeroRttData + zeroRttLen, bufp, cc);
+ zeroRttLen += cc;
+ }
+ bufp += cc;
+ nb -= cc;
+ if (nb <= 0)
+ break;
+
+ rv = restartHandshakeAfterServerCertIfNeeded(s,
+ &serverCertAuth, override);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "authentication of server cert failed");
+ return EXIT_CODE_HANDSHAKE_FAILED;
+ }
+
+ rv = restartHandshakeAfterClientCertIfNeeded(s);
+ if (rv == SECSuccess) {
+ continue;
+ }
+
+ pollDesc.in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pollDesc.out_flags = 0;
+ FPRINTF(stderr,
+ "%s: about to call PR_Poll on writable socket !\n",
+ progName);
+ cc = PR_Poll(&pollDesc, 1, PR_INTERVAL_NO_TIMEOUT);
+ if (cc < 0) {
+ SECU_PrintError(progName, "PR_Poll failed");
+ return -1;
+ }
+ FPRINTF(stderr,
+ "%s: PR_Poll returned with writable socket !\n",
+ progName);
+ } while (1);
+
+ return 0;
+}
+
+void
+handshakeCallback(PRFileDesc *fd, void *client_data)
+{
+ const char *secondHandshakeName = (char *)client_data;
+ if (secondHandshakeName) {
+ SSL_SetURL(fd, secondHandshakeName);
+ }
+ printSecurityInfo(fd);
+ if (renegotiationsDone < renegotiationsToDo) {
+ SSL_ReHandshake(fd, (renegotiationsToDo < 2));
+ ++renegotiationsDone;
+ }
+ if (zeroRttLen) {
+ /* This data was sent in 0-RTT. */
+ SSLChannelInfo info;
+ SECStatus rv;
+
+ rv = SSL_GetChannelInfo(fd, &info, sizeof(info));
+ if (rv != SECSuccess)
+ return;
+
+ if (!info.earlyDataAccepted) {
+ FPRINTF(stderr, "Early data rejected. Re-sending %d bytes\n",
+ zeroRttLen);
+ writeBytesToServer(fd, zeroRttData, zeroRttLen);
+ zeroRttLen = 0;
+ }
+ }
+ if (stopAfterHandshake) {
+ requestToExit = PR_TRUE;
+ }
+ handshakeComplete = PR_TRUE;
+
+ if (enabledExporters) {
+ SECStatus rv;
+
+ rv = exportKeyingMaterials(fd, enabledExporters, enabledExporterCount);
+ if (rv != SECSuccess) {
+ PRErrorCode err = PR_GetError();
+ FPRINTF(stderr,
+ "couldn't export keying material: %s\n",
+ SECU_Strerror(err));
+ }
+ }
+}
+
+static SECStatus
+installServerCertificate(PRFileDesc *s, char *nick)
+{
+ CERTCertificate *cert;
+ SECKEYPrivateKey *privKey = NULL;
+
+ if (!nick) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ cert = PK11_FindCertFromNickname(nick, &pwdata);
+ if (cert == NULL) {
+ return SECFailure;
+ }
+
+ privKey = PK11_FindKeyByAnyCert(cert, &pwdata);
+ if (privKey == NULL) {
+ return SECFailure;
+ }
+ if (SSL_ConfigServerCert(s, cert, privKey, NULL, 0) != SECSuccess) {
+ return SECFailure;
+ }
+ SECKEY_DestroyPrivateKey(privKey);
+ CERT_DestroyCertificate(cert);
+
+ return SECSuccess;
+}
+
+static SECStatus
+bindToClient(PRFileDesc *s)
+{
+ PRStatus status;
+ status = PR_Bind(s, &addr);
+ if (status != PR_SUCCESS) {
+ return SECFailure;
+ }
+
+ for (;;) {
+ /* Bind the remote address on first packet. This must happen
+ * before we SSL-ize the socket because we need to get the
+ * peer's address before SSLizing. Recvfrom gives us that
+ * while not consuming any data. */
+ unsigned char tmp;
+ PRNetAddr remote;
+ int nb;
+
+ nb = PR_RecvFrom(s, &tmp, 1, PR_MSG_PEEK,
+ &remote, PR_INTERVAL_NO_TIMEOUT);
+ if (nb != 1)
+ continue;
+
+ status = PR_Connect(s, &remote, PR_INTERVAL_NO_TIMEOUT);
+ if (status != PR_SUCCESS) {
+ SECU_PrintError(progName, "server bind to remote end failed");
+ return SECFailure;
+ }
+ return SECSuccess;
+ }
+
+ /* Unreachable. */
+}
+
+static SECStatus
+connectToServer(PRFileDesc *s, PRPollDesc *pollset)
+{
+ PRStatus status;
+ PRInt32 filesReady;
+
+ status = PR_Connect(s, &addr, PR_INTERVAL_NO_TIMEOUT);
+ if (status != PR_SUCCESS) {
+ if (PR_GetError() == PR_IN_PROGRESS_ERROR) {
+ if (verbose)
+ SECU_PrintError(progName, "connect");
+ milliPause(50 * multiplier);
+ pollset[SSOCK_FD].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pollset[SSOCK_FD].out_flags = 0;
+ pollset[SSOCK_FD].fd = s;
+ while (1) {
+ FPRINTF(stderr,
+ "%s: about to call PR_Poll for connect completion!\n",
+ progName);
+ filesReady = PR_Poll(pollset, 1, PR_INTERVAL_NO_TIMEOUT);
+ if (filesReady < 0) {
+ SECU_PrintError(progName, "unable to connect (poll)");
+ return SECFailure;
+ }
+ FPRINTF(stderr,
+ "%s: PR_Poll returned 0x%02x for socket out_flags.\n",
+ progName, pollset[SSOCK_FD].out_flags);
+ if (filesReady == 0) { /* shouldn't happen! */
+ SECU_PrintError(progName, "%s: PR_Poll returned zero!\n");
+ return SECFailure;
+ }
+ status = PR_GetConnectStatus(pollset);
+ if (status == PR_SUCCESS) {
+ break;
+ }
+ if (PR_GetError() != PR_IN_PROGRESS_ERROR) {
+ SECU_PrintError(progName, "unable to connect (poll)");
+ return SECFailure;
+ }
+ SECU_PrintError(progName, "poll");
+ milliPause(50 * multiplier);
+ }
+ } else {
+ SECU_PrintError(progName, "unable to connect");
+ return SECFailure;
+ }
+ }
+
+ return SECSuccess;
+}
+
+static SECStatus
+importPsk(PRFileDesc *s)
+{
+ SECU_PrintAsHex(stdout, &psk, "Using External PSK", 0);
+ PK11SlotInfo *slot = NULL;
+ PK11SymKey *symKey = NULL;
+ slot = PK11_GetInternalSlot();
+ if (!slot) {
+ SECU_PrintError(progName, "PK11_GetInternalSlot failed");
+ return SECFailure;
+ }
+ symKey = PK11_ImportSymKey(slot, CKM_HKDF_KEY_GEN, PK11_OriginUnwrap,
+ CKA_DERIVE, &psk, NULL);
+ PK11_FreeSlot(slot);
+ if (!symKey) {
+ SECU_PrintError(progName, "PK11_ImportSymKey failed");
+ return SECFailure;
+ }
+
+ SECStatus rv = SSL_AddExternalPsk(s, symKey, (const PRUint8 *)pskLabel.data,
+ pskLabel.len, ssl_hash_sha256);
+ PK11_FreeSymKey(symKey);
+ return rv;
+}
+
+static SECStatus
+printEchRetryConfigs(PRFileDesc *s)
+{
+ if (PORT_GetError() == SSL_ERROR_ECH_RETRY_WITH_ECH) {
+ SECItem retries = { siBuffer, NULL, 0 };
+ SECStatus rv = SSL_GetEchRetryConfigs(s, &retries);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "SSL_GetEchRetryConfigs failed");
+ return SECFailure;
+ }
+ char *retriesBase64 = NSSBase64_EncodeItem(NULL, NULL, 0, &retries);
+ if (!retriesBase64) {
+ SECU_PrintError(progName, "NSSBase64_EncodeItem on retry_configs failed");
+ SECITEM_FreeItem(&retries, PR_FALSE);
+ return SECFailure;
+ }
+
+ // Remove the newline characters that NSSBase64_EncodeItem unhelpfully inserts.
+ char *newline = strstr(retriesBase64, "\r\n");
+ if (newline) {
+ memmove(newline, newline + 2, strlen(newline + 2) + 1);
+ }
+ fprintf(stderr, "Received ECH retry_configs: \n%s\n", retriesBase64);
+ PORT_Free(retriesBase64);
+ SECITEM_FreeItem(&retries, PR_FALSE);
+ }
+ return SECSuccess;
+}
+
+static int
+run()
+{
+ int headerSeparatorPtrnId = 0;
+ int error = 0;
+ SECStatus rv;
+ PRStatus status;
+ PRInt32 filesReady;
+ PRFileDesc *s = NULL;
+ PRFileDesc *std_out;
+ PRPollDesc pollset[2] = { { 0 }, { 0 } };
+ PRBool wrStarted = PR_FALSE;
+
+ handshakeComplete = PR_FALSE;
+
+ /* Create socket */
+ if (useDTLS) {
+ s = PR_OpenUDPSocket(addr.raw.family);
+ } else {
+ s = PR_OpenTCPSocket(addr.raw.family);
+ }
+
+ if (s == NULL) {
+ SECU_PrintError(progName, "error creating socket");
+ error = 1;
+ goto done;
+ }
+
+ if (actAsServer) {
+ if (bindToClient(s) != SECSuccess) {
+ return 1;
+ }
+ }
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = PR_TRUE; /* default */
+ if (serverCertAuth.testFreshStatusFromSideChannel) {
+ opt.value.non_blocking = PR_FALSE;
+ }
+ status = PR_SetSocketOption(s, &opt);
+ if (status != PR_SUCCESS) {
+ SECU_PrintError(progName, "error setting socket options");
+ error = 1;
+ goto done;
+ }
+
+ if (useDTLS) {
+ s = DTLS_ImportFD(NULL, s);
+ } else {
+ s = SSL_ImportFD(NULL, s);
+ }
+ if (s == NULL) {
+ SECU_PrintError(progName, "error importing socket");
+ error = 1;
+ goto done;
+ }
+ SSL_SetPKCS11PinArg(s, &pwdata);
+
+ rv = SSL_OptionSet(s, SSL_SECURITY, 1);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling socket");
+ error = 1;
+ goto done;
+ }
+
+ rv = SSL_OptionSet(s, actAsServer ? SSL_HANDSHAKE_AS_SERVER : SSL_HANDSHAKE_AS_CLIENT, 1);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling client handshake");
+ error = 1;
+ goto done;
+ }
+
+ /* all SSL3 cipher suites are enabled by default. */
+ if (cipherString) {
+ char *cstringSaved = cipherString;
+ int ndx;
+
+ while (0 != (ndx = *cipherString++)) {
+ int cipher = 0;
+
+ if (ndx == ':') {
+ int ctmp = 0;
+
+ HEXCHAR_TO_INT(*cipherString, ctmp)
+ cipher |= (ctmp << 12);
+ cipherString++;
+ HEXCHAR_TO_INT(*cipherString, ctmp)
+ cipher |= (ctmp << 8);
+ cipherString++;
+ HEXCHAR_TO_INT(*cipherString, ctmp)
+ cipher |= (ctmp << 4);
+ cipherString++;
+ HEXCHAR_TO_INT(*cipherString, ctmp)
+ cipher |= ctmp;
+ cipherString++;
+ } else {
+ if (!isalpha(ndx))
+ Usage();
+ ndx = tolower(ndx) - 'a';
+ if (ndx < PR_ARRAY_SIZE(ssl3CipherSuites)) {
+ cipher = ssl3CipherSuites[ndx];
+ }
+ }
+ if (cipher > 0) {
+ rv = SSL_CipherPrefSet(s, cipher, SSL_ALLOWED);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "SSL_CipherPrefSet()");
+ error = 1;
+ goto done;
+ }
+ } else {
+ Usage();
+ }
+ }
+ PORT_Free(cstringSaved);
+ }
+
+ rv = SSL_VersionRangeSet(s, &enabledVersions);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error setting SSL/TLS version range ");
+ error = 1;
+ goto done;
+ }
+
+ /* disable SSL socket locking */
+ rv = SSL_OptionSet(s, SSL_NO_LOCKS, disableLocking);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error disabling SSL socket locking");
+ error = 1;
+ goto done;
+ }
+
+ /* enable Session Ticket extension. */
+ rv = SSL_OptionSet(s, SSL_ENABLE_SESSION_TICKETS, enableSessionTickets);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling Session Ticket extension");
+ error = 1;
+ goto done;
+ }
+
+ /* enable false start. */
+ rv = SSL_OptionSet(s, SSL_ENABLE_FALSE_START, enableFalseStart);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling false start");
+ error = 1;
+ goto done;
+ }
+
+ if (forceFallbackSCSV) {
+ rv = SSL_OptionSet(s, SSL_ENABLE_FALLBACK_SCSV, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error forcing fallback scsv");
+ error = 1;
+ goto done;
+ }
+ }
+
+ /* enable cert status (OCSP stapling). */
+ rv = SSL_OptionSet(s, SSL_ENABLE_OCSP_STAPLING, enableCertStatus);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling cert status (OCSP stapling)");
+ error = 1;
+ goto done;
+ }
+
+ /* enable negotiation of delegated credentials (draft-ietf-tls-subcerts) */
+ rv = SSL_OptionSet(s, SSL_ENABLE_DELEGATED_CREDENTIALS, enableDelegatedCredentials);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling delegated credentials");
+ error = 1;
+ goto done;
+ }
+
+ /* enable extended master secret mode */
+ if (enableExtendedMasterSecret) {
+ rv = SSL_OptionSet(s, SSL_ENABLE_EXTENDED_MASTER_SECRET, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling extended master secret");
+ error = 1;
+ goto done;
+ }
+ }
+
+ /* enable 0-RTT (TLS 1.3 only) */
+ if (enableZeroRtt) {
+ rv = SSL_OptionSet(s, SSL_ENABLE_0RTT_DATA, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling 0-RTT");
+ error = 1;
+ goto done;
+ }
+ }
+
+ /* Alternate ServerHello content type (TLS 1.3 only) */
+ if (enableAltServerHello) {
+ rv = SSL_UseAltServerHelloType(s, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling alternate ServerHello type");
+ error = 1;
+ goto done;
+ }
+ }
+
+ /* Middlebox compatibility mode (TLS 1.3 only) */
+ if (middleboxCompatMode) {
+ rv = SSL_OptionSet(s, SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling middlebox compatibility mode");
+ error = 1;
+ goto done;
+ }
+ }
+
+ /* require the use of fixed finite-field DH groups */
+ if (requireDHNamedGroups) {
+ rv = SSL_OptionSet(s, SSL_REQUIRE_DH_NAMED_GROUPS, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error in requiring the use of fixed finite-field DH groups");
+ error = 1;
+ goto done;
+ }
+ }
+
+ /* enable Signed Certificate Timestamps. */
+ rv = SSL_OptionSet(s, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
+ enableSignedCertTimestamps);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling signed cert timestamps");
+ error = 1;
+ goto done;
+ }
+
+ if (enablePostHandshakeAuth) {
+ rv = SSL_OptionSet(s, SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling post-handshake auth");
+ error = 1;
+ goto done;
+ }
+ }
+
+ if (enabledGroups) {
+ rv = SSL_NamedGroupConfig(s, enabledGroups, enabledGroupsCount);
+ if (rv < 0) {
+ SECU_PrintError(progName, "SSL_NamedGroupConfig failed");
+ error = 1;
+ goto done;
+ }
+ }
+
+ if (enabledSigSchemes) {
+ rv = SSL_SignatureSchemePrefSet(s, enabledSigSchemes, enabledSigSchemeCount);
+ if (rv < 0) {
+ SECU_PrintError(progName, "SSL_SignatureSchemePrefSet failed");
+ error = 1;
+ goto done;
+ }
+ }
+
+ if (echConfigs) {
+ SECItem echConfigsBin = { siBuffer, NULL, 0 };
+
+ if (!NSSBase64_DecodeBuffer(NULL, &echConfigsBin, echConfigs,
+ strlen(echConfigs))) {
+ SECU_PrintError(progName, "ECHConfigs record is invalid base64");
+ error = 1;
+ goto done;
+ }
+
+ rv = SSL_SetClientEchConfigs(s, echConfigsBin.data, echConfigsBin.len);
+ SECITEM_FreeItem(&echConfigsBin, PR_FALSE);
+ if (rv < 0) {
+ SECU_PrintError(progName, "SSL_SetClientEchConfigs failed");
+ error = 1;
+ goto done;
+ }
+ }
+
+ if (echGreaseSize) {
+ rv = SSL_EnableTls13GreaseEch(s, PR_TRUE);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "SSL_EnableTls13GreaseEch failed");
+ error = 1;
+ goto done;
+ }
+ rv = SSL_SetTls13GreaseEchSize(s, echGreaseSize);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "SSL_SetTls13GreaseEchSize failed");
+ error = 1;
+ goto done;
+ }
+ }
+
+ if (psk.data) {
+ rv = importPsk(s);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "importPsk failed");
+ error = 1;
+ goto done;
+ }
+ }
+
+ serverCertAuth.dbHandle = CERT_GetDefaultCertDB();
+
+ SSL_AuthCertificateHook(s, ownAuthCertificate, &serverCertAuth);
+ if (override) {
+ SSL_BadCertHook(s, ownBadCertHandler, NULL);
+ }
+ if (actAsServer) {
+ rv = installServerCertificate(s, nickname);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error installing server cert");
+ return 1;
+ }
+ rv = SSL_ConfigServerSessionIDCache(1024, 0, 0, ".");
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error configuring session cache");
+ return 1;
+ }
+ initializedServerSessionCache = PR_TRUE;
+ } else {
+ SSL_GetClientAuthDataHook(s, own_GetClientAuthData, (void *)nickname);
+ }
+ SSL_HandshakeCallback(s, handshakeCallback, hs2SniHostName);
+ if (hs1SniHostName) {
+ SSL_SetURL(s, hs1SniHostName);
+ } else {
+ SSL_SetURL(s, host);
+ }
+
+ if (actAsServer) {
+ rv = SSL_ResetHandshake(s, PR_TRUE /* server */);
+ if (rv != SECSuccess) {
+ return 1;
+ }
+ } else {
+ /* Try to connect to the server */
+ rv = connectToServer(s, pollset);
+ if (rv != SECSuccess) {
+ error = 1;
+ goto done;
+ }
+ }
+
+ pollset[SSOCK_FD].fd = s;
+ pollset[SSOCK_FD].in_flags = PR_POLL_EXCEPT;
+ if (!actAsServer)
+ pollset[SSOCK_FD].in_flags |= (clientSpeaksFirst ? 0 : PR_POLL_READ);
+ else
+ pollset[SSOCK_FD].in_flags |= PR_POLL_READ;
+ if (requestFile) {
+ pollset[STDIN_FD].fd = PR_Open(requestFile, PR_RDONLY, 0);
+ if (!pollset[STDIN_FD].fd) {
+ fprintf(stderr, "%s: unable to open input file: %s\n",
+ progName, requestFile);
+ error = 1;
+ goto done;
+ }
+ } else {
+ pollset[STDIN_FD].fd = PR_GetSpecialFD(PR_StandardInput);
+ }
+ pollset[STDIN_FD].in_flags = PR_POLL_READ;
+ std_out = PR_GetSpecialFD(PR_StandardOutput);
+
+#if defined(WIN32) || defined(OS2)
+ /* PR_Poll cannot be used with stdin on Windows or OS/2. (sigh).
+ ** But use of PR_Poll and non-blocking sockets is a major feature
+ ** of this program. So, we simulate a pollable stdin with a
+ ** TCP socket pair and a thread that reads stdin and writes to
+ ** that socket pair.
+ */
+ {
+ PRFileDesc *fds[2];
+ PRThread *thread;
+
+ int nspr_rv = PR_NewTCPSocketPair(fds);
+ if (nspr_rv != PR_SUCCESS) {
+ SECU_PrintError(progName, "PR_NewTCPSocketPair failed");
+ error = 1;
+ goto done;
+ }
+ pollset[STDIN_FD].fd = fds[1];
+
+ thread = PR_CreateThread(PR_USER_THREAD, thread_main, fds[0],
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, 0);
+ if (!thread) {
+ SECU_PrintError(progName, "PR_CreateThread failed");
+ error = 1;
+ goto done;
+ }
+ }
+#endif
+
+ if (serverCertAuth.testFreshStatusFromSideChannel) {
+ SSL_ForceHandshake(s);
+ error = serverCertAuth.sideChannelRevocationTestResultCode;
+ goto done;
+ }
+
+ /*
+ ** Select on stdin and on the socket. Write data from stdin to
+ ** socket, read data from socket and write to stdout.
+ */
+ requestToExit = PR_FALSE;
+ FPRINTF(stderr, "%s: ready...\n", progName);
+ while (!requestToExit &&
+ (pollset[SSOCK_FD].in_flags || pollset[STDIN_FD].in_flags)) {
+ PRUint8 buf[4000]; /* buffer for stdin */
+ int nb; /* num bytes read from stdin. */
+
+ rv = restartHandshakeAfterServerCertIfNeeded(s, &serverCertAuth,
+ override);
+ if (rv != SECSuccess) {
+ error = EXIT_CODE_HANDSHAKE_FAILED;
+ SECU_PrintError(progName, "authentication of server cert failed");
+ goto done;
+ }
+
+ rv = restartHandshakeAfterClientCertIfNeeded(s);
+ if (rv == SECSuccess) {
+ continue;
+ }
+
+ pollset[SSOCK_FD].out_flags = 0;
+ pollset[STDIN_FD].out_flags = 0;
+
+ FPRINTF(stderr, "%s: about to call PR_Poll !\n", progName);
+ filesReady = PR_Poll(pollset, PR_ARRAY_SIZE(pollset),
+ PR_INTERVAL_NO_TIMEOUT);
+ if (filesReady < 0) {
+ SECU_PrintError(progName, "select failed");
+ error = 1;
+ goto done;
+ }
+ if (filesReady == 0) { /* shouldn't happen! */
+ FPRINTF(stderr, "%s: PR_Poll returned zero!\n", progName);
+ error = 1;
+ goto done;
+ }
+ FPRINTF(stderr, "%s: PR_Poll returned!\n", progName);
+ if (pollset[STDIN_FD].in_flags) {
+ FPRINTF(stderr,
+ "%s: PR_Poll returned 0x%02x for stdin out_flags.\n",
+ progName, pollset[STDIN_FD].out_flags);
+ }
+ if (pollset[SSOCK_FD].in_flags) {
+ FPRINTF(stderr,
+ "%s: PR_Poll returned 0x%02x for socket out_flags.\n",
+ progName, pollset[SSOCK_FD].out_flags);
+ }
+ if (pollset[STDIN_FD].out_flags & PR_POLL_READ) {
+ /* Read from stdin and write to socket */
+ nb = PR_Read(pollset[STDIN_FD].fd, buf, sizeof(buf));
+ FPRINTF(stderr, "%s: stdin read %d bytes\n", progName, nb);
+ if (nb < 0) {
+ if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+ SECU_PrintError(progName, "read from stdin failed");
+ error = 1;
+ break;
+ }
+ } else if (nb == 0) {
+ /* EOF on stdin, stop polling stdin for read. */
+ pollset[STDIN_FD].in_flags = 0;
+ if (actAsServer)
+ requestToExit = PR_TRUE;
+ } else {
+ error = writeBytesToServer(s, buf, nb);
+ if (error) {
+ if (echConfigs) {
+ (void)printEchRetryConfigs(s);
+ }
+ goto done;
+ }
+ pollset[SSOCK_FD].in_flags = PR_POLL_READ;
+ }
+ }
+
+ if (pollset[SSOCK_FD].in_flags) {
+ FPRINTF(stderr,
+ "%s: PR_Poll returned 0x%02x for socket out_flags.\n",
+ progName, pollset[SSOCK_FD].out_flags);
+ }
+#ifdef PR_POLL_HUP
+#define POLL_RECV_FLAGS (PR_POLL_READ | PR_POLL_ERR | PR_POLL_HUP)
+#else
+#define POLL_RECV_FLAGS (PR_POLL_READ | PR_POLL_ERR)
+#endif
+ if (pollset[SSOCK_FD].out_flags & POLL_RECV_FLAGS) {
+ /* Read from socket and write to stdout */
+ nb = PR_Recv(pollset[SSOCK_FD].fd, buf, sizeof buf, 0, maxInterval);
+ FPRINTF(stderr, "%s: Read from server %d bytes\n", progName, nb);
+ if (nb < 0) {
+ if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+ SECU_PrintError(progName, "read from socket failed");
+ error = 1;
+ goto done;
+ }
+ } else if (nb == 0) {
+ /* EOF from socket... stop polling socket for read */
+ pollset[SSOCK_FD].in_flags = 0;
+ } else {
+ if (skipProtoHeader != PR_TRUE || wrStarted == PR_TRUE) {
+ PR_Write(std_out, buf, nb);
+ } else {
+ separateReqHeader(std_out, (char *)buf, nb, &wrStarted,
+ &headerSeparatorPtrnId);
+ }
+ if (verbose)
+ fputs("\n\n", stderr);
+ }
+ }
+ milliPause(50 * multiplier);
+ }
+
+done:
+ if (s) {
+ PR_Close(s);
+ }
+ if (requestFile && pollset[STDIN_FD].fd) {
+ PR_Close(pollset[STDIN_FD].fd);
+ }
+ return error;
+}
+
+int
+main(int argc, char **argv)
+{
+ PLOptState *optstate;
+ PLOptStatus optstatus;
+ PRStatus status;
+ PRStatus prStatus;
+ int error = 0;
+ char *tmp;
+ SECStatus rv;
+ char *certDir = NULL;
+ PRBool openDB = PR_TRUE;
+ PRBool loadDefaultRootCAs = PR_FALSE;
+ char *rootModule = NULL;
+ int numConnections = 1;
+ PRFileDesc *s = NULL;
+
+ serverCertAuth.shouldPause = PR_TRUE;
+ serverCertAuth.isPaused = PR_FALSE;
+ serverCertAuth.dbHandle = NULL;
+ serverCertAuth.testFreshStatusFromSideChannel = PR_FALSE;
+ serverCertAuth.sideChannelRevocationTestResultCode = EXIT_CODE_HANDSHAKE_FAILED;
+ serverCertAuth.requireDataForIntermediates = PR_FALSE;
+ serverCertAuth.allowOCSPSideChannelData = PR_TRUE;
+ serverCertAuth.allowCRLSideChannelData = PR_TRUE;
+
+ progName = strrchr(argv[0], '/');
+ if (!progName)
+ progName = strrchr(argv[0], '\\');
+ progName = progName ? progName + 1 : argv[0];
+
+ tmp = PR_GetEnvSecure("NSS_DEBUG_TIMEOUT");
+ if (tmp && tmp[0]) {
+ int sec = PORT_Atoi(tmp);
+ if (sec > 0) {
+ maxInterval = PR_SecondsToInterval(sec);
+ }
+ }
+
+ optstate = PL_CreateOptState(argc, argv,
+ "46A:BCDEFGHI:J:KL:M:N:OP:QR:STUV:W:X:YZa:bc:d:efgh:i:m:n:op:qr:st:uvw:x:z:");
+ while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
+ switch (optstate->option) {
+ case '?':
+ default:
+ Usage();
+ break;
+
+ case '4':
+ allowIPv6 = PR_FALSE;
+ if (!allowIPv4)
+ Usage();
+ break;
+ case '6':
+ allowIPv4 = PR_FALSE;
+ if (!allowIPv6)
+ Usage();
+ break;
+
+ case 'A':
+ requestFile = PORT_Strdup(optstate->value);
+ break;
+
+ case 'B':
+ enableDelegatedCredentials = PR_TRUE;
+ break;
+
+ case 'C':
+ ++dumpServerChain;
+ break;
+
+ case 'D':
+ openDB = PR_FALSE;
+ break;
+
+ case 'E':
+ enablePostHandshakeAuth = PR_TRUE;
+ break;
+
+ case 'F':
+ if (serverCertAuth.testFreshStatusFromSideChannel) {
+ /* parameter given twice or more */
+ serverCertAuth.requireDataForIntermediates = PR_TRUE;
+ }
+ serverCertAuth.testFreshStatusFromSideChannel = PR_TRUE;
+ break;
+
+ case 'G':
+ enableExtendedMasterSecret = PR_TRUE;
+ break;
+
+ case 'H':
+ requireDHNamedGroups = PR_TRUE;
+ break;
+
+ case 'O':
+ clientCertAsyncSelect = PR_FALSE;
+ serverCertAuth.shouldPause = PR_FALSE;
+ break;
+
+ case 'K':
+ forceFallbackSCSV = PR_TRUE;
+ break;
+
+ case 'L':
+ numConnections = atoi(optstate->value);
+ break;
+
+ case 'M':
+ switch (atoi(optstate->value)) {
+ case 1:
+ serverCertAuth.allowOCSPSideChannelData = PR_TRUE;
+ serverCertAuth.allowCRLSideChannelData = PR_FALSE;
+ break;
+ case 2:
+ serverCertAuth.allowOCSPSideChannelData = PR_FALSE;
+ serverCertAuth.allowCRLSideChannelData = PR_TRUE;
+ break;
+ case 0:
+ default:
+ serverCertAuth.allowOCSPSideChannelData = PR_TRUE;
+ serverCertAuth.allowCRLSideChannelData = PR_TRUE;
+ break;
+ };
+ break;
+
+ case 'N':
+ echConfigs = PORT_Strdup(optstate->value);
+ break;
+
+ case 'i':
+ echGreaseSize = PORT_Atoi(optstate->value);
+ if (!echGreaseSize || echGreaseSize > 255) {
+ fprintf(stderr, "ECH Grease size must be within 1..255 (inclusive).\n");
+ exit(-1);
+ }
+ break;
+
+ case 'P':
+ useDTLS = PR_TRUE;
+ if (!strcmp(optstate->value, "server")) {
+ actAsServer = 1;
+ } else {
+ if (strcmp(optstate->value, "client")) {
+ Usage();
+ }
+ }
+ break;
+
+ case 'Q':
+ stopAfterHandshake = PR_TRUE;
+ break;
+
+ case 'R':
+ rootModule = PORT_Strdup(optstate->value);
+ break;
+
+ case 'S':
+ skipProtoHeader = PR_TRUE;
+ break;
+
+ case 'T':
+ enableCertStatus = 1;
+ break;
+
+ case 'U':
+ enableSignedCertTimestamps = 1;
+ break;
+
+ case 'V':
+ versionString = PORT_Strdup(optstate->value);
+ break;
+
+ case 'X':
+ if (!strcmp(optstate->value, "alt-server-hello")) {
+ enableAltServerHello = PR_TRUE;
+ } else {
+ Usage();
+ }
+ break;
+ case 'Y':
+ PrintCipherUsage();
+ exit(0);
+ break;
+
+ case 'Z':
+ enableZeroRtt = PR_TRUE;
+ zeroRttData = PORT_ZAlloc(ZERO_RTT_MAX);
+ if (!zeroRttData) {
+ fprintf(stderr, "Unable to allocate buffer for 0-RTT\n");
+ exit(1);
+ }
+ break;
+
+ case 'a':
+ if (!hs1SniHostName) {
+ hs1SniHostName = PORT_Strdup(optstate->value);
+ } else if (!hs2SniHostName) {
+ hs2SniHostName = PORT_Strdup(optstate->value);
+ } else {
+ Usage();
+ }
+ break;
+
+ case 'b':
+ loadDefaultRootCAs = PR_TRUE;
+ break;
+
+ case 'c':
+ cipherString = PORT_Strdup(optstate->value);
+ break;
+
+ case 'g':
+ enableFalseStart = 1;
+ break;
+
+ case 'd':
+ certDir = PORT_Strdup(optstate->value);
+ break;
+
+ case 'e':
+ middleboxCompatMode = PR_TRUE;
+ break;
+
+ case 'f':
+ clientSpeaksFirst = PR_TRUE;
+ break;
+
+ case 'h':
+ host = PORT_Strdup(optstate->value);
+ break;
+
+ case 'm':
+ multiplier = atoi(optstate->value);
+ if (multiplier < 0)
+ multiplier = 0;
+ break;
+
+ case 'n':
+ nickname = PORT_Strdup(optstate->value);
+ break;
+
+ case 'o':
+ override = 1;
+ break;
+
+ case 'p':
+ portno = (PRUint16)atoi(optstate->value);
+ break;
+
+ case 'q':
+ pingServerFirst = PR_TRUE;
+ break;
+
+ case 's':
+ disableLocking = 1;
+ break;
+
+ case 't':
+ pingTimeoutSeconds = atoi(optstate->value);
+ break;
+
+ case 'u':
+ enableSessionTickets = PR_TRUE;
+ break;
+
+ case 'v':
+ verbose++;
+ break;
+
+ case 'r':
+ renegotiationsToDo = atoi(optstate->value);
+ break;
+
+ case 'w':
+ pwdata.source = PW_PLAINTEXT;
+ pwdata.data = PORT_Strdup(optstate->value);
+ break;
+
+ case 'W':
+ pwdata.source = PW_FROMFILE;
+ pwdata.data = PORT_Strdup(optstate->value);
+ break;
+
+ case 'I':
+ rv = parseGroupList(optstate->value, &enabledGroups, &enabledGroupsCount);
+ if (rv != SECSuccess) {
+ PL_DestroyOptState(optstate);
+ fprintf(stderr, "Bad group specified.\n");
+ Usage();
+ }
+ break;
+
+ case 'J':
+ rv = parseSigSchemeList(optstate->value, &enabledSigSchemes, &enabledSigSchemeCount);
+ if (rv != SECSuccess) {
+ PL_DestroyOptState(optstate);
+ fprintf(stderr, "Bad signature scheme specified.\n");
+ Usage();
+ }
+ break;
+
+ case 'x':
+ rv = parseExporters(optstate->value,
+ &enabledExporters,
+ &enabledExporterCount);
+ if (rv != SECSuccess) {
+ PL_DestroyOptState(optstate);
+ fprintf(stderr, "Bad exporter specified.\n");
+ Usage();
+ }
+ break;
+
+ case 'z':
+ rv = readPSK(optstate->value, &psk, &pskLabel);
+ if (rv != SECSuccess) {
+ PL_DestroyOptState(optstate);
+ fprintf(stderr, "Bad PSK specified.\n");
+ Usage();
+ }
+ break;
+ }
+ }
+ PL_DestroyOptState(optstate);
+
+ SSL_VersionRangeGetSupported(useDTLS ? ssl_variant_datagram : ssl_variant_stream, &enabledVersions);
+
+ if (versionString) {
+ if (SECU_ParseSSLVersionRangeString(versionString,
+ enabledVersions, &enabledVersions) !=
+ SECSuccess) {
+ fprintf(stderr, "Bad version specified.\n");
+ Usage();
+ }
+ PORT_Free(versionString);
+ }
+
+ if (optstatus == PL_OPT_BAD) {
+ Usage();
+ }
+
+ if (!host || !portno) {
+ fprintf(stderr, "%s: parameters -h and -p are mandatory\n", progName);
+ Usage();
+ }
+
+ if (serverCertAuth.testFreshStatusFromSideChannel &&
+ serverCertAuth.shouldPause) {
+ fprintf(stderr, "%s: -F requires the use of -O\n", progName);
+ exit(1);
+ }
+
+ if (certDir && !openDB) {
+ fprintf(stderr, "%s: Cannot combine parameters -D and -d\n", progName);
+ exit(1);
+ }
+
+ if (rootModule && loadDefaultRootCAs) {
+ fprintf(stderr, "%s: Cannot combine parameters -b and -R\n", progName);
+ exit(1);
+ }
+
+ if (enablePostHandshakeAuth && !nickname) {
+ fprintf(stderr, "%s: -E requires the use of -n\n", progName);
+ exit(1);
+ }
+
+ PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
+
+ PK11_SetPasswordFunc(SECU_GetModulePassword);
+ memset(&addr, 0, sizeof(addr));
+ status = PR_StringToNetAddr(host, &addr);
+ if (status == PR_SUCCESS) {
+ addr.inet.port = PR_htons(portno);
+ } else {
+ PRBool gotLoopbackIP = PR_FALSE;
+ if ((!strcmp(host, "localhost") || !strcmp(host, "localhost.localdomain"))
+ /* only check for preference if both types are allowed */
+ && allowIPv4 && allowIPv6) {
+ /* make a decision which IP to prefer */
+ status = PR_GetPrefLoopbackAddrInfo(&addr, portno);
+ if (status != PR_FAILURE) {
+ gotLoopbackIP = PR_TRUE;
+ }
+ }
+
+ if (!gotLoopbackIP) {
+ /* Lookup host */
+ PRAddrInfo *addrInfo;
+ void *enumPtr = NULL;
+
+ addrInfo = PR_GetAddrInfoByName(host, PR_AF_UNSPEC,
+ PR_AI_ADDRCONFIG | PR_AI_NOCANONNAME);
+ if (!addrInfo) {
+ fprintf(stderr, "HOSTNAME=%s\n", host);
+ SECU_PrintError(progName, "error looking up host");
+ error = 1;
+ goto done;
+ }
+ for (;;) {
+ enumPtr = PR_EnumerateAddrInfo(enumPtr, addrInfo, portno, &addr);
+ if (enumPtr == NULL)
+ break;
+ if (addr.raw.family == PR_AF_INET && allowIPv4)
+ break;
+ if (addr.raw.family == PR_AF_INET6 && allowIPv6)
+ break;
+ }
+ PR_FreeAddrInfo(addrInfo);
+ if (enumPtr == NULL) {
+ SECU_PrintError(progName, "error looking up host address");
+ error = 1;
+ goto done;
+ }
+ }
+ }
+
+ printHostNameAndAddr(host, &addr);
+
+ if (!certDir) {
+ certDir = SECU_DefaultSSLDir(); /* Look in $SSL_DIR */
+ certDir = SECU_ConfigDirectory(certDir);
+ } else {
+ char *certDirTmp = certDir;
+ certDir = SECU_ConfigDirectory(certDirTmp);
+ PORT_Free(certDirTmp);
+ }
+
+ if (pingServerFirst) {
+ int iter = 0;
+ PRErrorCode err;
+
+ int max_attempts = MAX_WAIT_FOR_SERVER;
+ if (pingTimeoutSeconds >= 0) {
+ /* If caller requested a timeout, let's try just twice. */
+ max_attempts = 2;
+ }
+ do {
+ PRIntervalTime timeoutInterval = PR_INTERVAL_NO_TIMEOUT;
+ s = PR_OpenTCPSocket(addr.raw.family);
+ if (s == NULL) {
+ SECU_PrintError(progName, "Failed to create a TCP socket");
+ error = 1;
+ goto done;
+ }
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = PR_FALSE;
+ prStatus = PR_SetSocketOption(s, &opt);
+ if (prStatus != PR_SUCCESS) {
+ SECU_PrintError(progName,
+ "Failed to set blocking socket option");
+ error = 1;
+ goto done;
+ }
+ if (pingTimeoutSeconds >= 0) {
+ timeoutInterval = PR_SecondsToInterval(pingTimeoutSeconds);
+ }
+ prStatus = PR_Connect(s, &addr, timeoutInterval);
+ if (prStatus == PR_SUCCESS) {
+ PR_Shutdown(s, PR_SHUTDOWN_BOTH);
+ goto done;
+ }
+ err = PR_GetError();
+ if ((err != PR_CONNECT_REFUSED_ERROR) &&
+ (err != PR_CONNECT_RESET_ERROR)) {
+ SECU_PrintError(progName, "TCP Connection failed");
+ error = 1;
+ goto done;
+ }
+ PR_Close(s);
+ s = NULL;
+ PR_Sleep(PR_MillisecondsToInterval(WAIT_INTERVAL));
+ } while (++iter < max_attempts);
+ SECU_PrintError(progName,
+ "Client timed out while waiting for connection to server");
+ error = 1;
+ goto done;
+ }
+
+ /* open the cert DB, the key DB, and the secmod DB. */
+ if (openDB) {
+ rv = NSS_Init(certDir);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "unable to open cert database");
+ error = 1;
+ goto done;
+ }
+ } else {
+ rv = NSS_NoDB_Init(NULL);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "failed to initialize NSS");
+ error = 1;
+ goto done;
+ }
+ }
+
+ if (loadDefaultRootCAs) {
+ SECMOD_AddNewModule("Builtins",
+ DLL_PREFIX "nssckbi." DLL_SUFFIX, 0, 0);
+ } else if (rootModule) {
+ SECMOD_AddNewModule("Builtins", rootModule, 0, 0);
+ }
+
+ /* all SSL3 cipher suites are enabled by default. */
+ if (cipherString) {
+ /* disable all the ciphers, then enable the ones we want. */
+ disableAllSSLCiphers();
+ }
+
+ while (numConnections--) {
+ error = run();
+ if (error) {
+ goto done;
+ }
+ }
+
+done:
+ if (s) {
+ PR_Close(s);
+ }
+
+ PORT_Free((void *)requestFile);
+ PORT_Free(hs1SniHostName);
+ PORT_Free(hs2SniHostName);
+ PORT_Free(nickname);
+ PORT_Free(pwdata.data);
+ PORT_Free(host);
+ PORT_Free(zeroRttData);
+ PORT_Free(echConfigs);
+ SECITEM_ZfreeItem(&psk, PR_FALSE);
+ SECITEM_ZfreeItem(&pskLabel, PR_FALSE);
+
+ if (enabledGroups) {
+ PORT_Free(enabledGroups);
+ }
+ if (NSS_IsInitialized()) {
+ SSL_ClearSessionCache();
+ if (initializedServerSessionCache) {
+ if (SSL_ShutdownServerSessionIDCache() != SECSuccess) {
+ error = 1;
+ }
+ }
+
+ if (NSS_Shutdown() != SECSuccess) {
+ error = 1;
+ }
+ }
+
+ FPRINTF(stderr, "tstclnt: exiting with return code %d\n", error);
+ PR_Cleanup();
+ return error;
+}
diff --git a/security/nss/cmd/tstclnt/tstclnt.gyp b/security/nss/cmd/tstclnt/tstclnt.gyp
new file mode 100644
index 0000000000..81cbff3ed0
--- /dev/null
+++ b/security/nss/cmd/tstclnt/tstclnt.gyp
@@ -0,0 +1,31 @@
+# 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/.
+{
+ 'includes': [
+ '../../coreconf/config.gypi',
+ '../../cmd/platlibs.gypi'
+ ],
+ 'targets': [
+ {
+ 'target_name': 'tstclnt',
+ 'type': 'executable',
+ 'sources': [
+ 'tstclnt.c'
+ ],
+ 'dependencies': [
+ '<(DEPTH)/exports.gyp:dbm_exports',
+ '<(DEPTH)/exports.gyp:nss_exports'
+ ]
+ }
+ ],
+ 'target_defaults': {
+ 'defines': [
+ 'DLL_PREFIX=\"<(dll_prefix)\"',
+ 'DLL_SUFFIX=\"<(dll_suffix)\"'
+ ]
+ },
+ 'variables': {
+ 'module': 'nss'
+ }
+} \ No newline at end of file