From 7f70dbcad58eb7183d129860192d6968dd7063a1 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 12 Feb 2007 22:32:37 +0000 Subject: [PATCH] Rob Crittenden added support for NSS (Network Security Service) for the SSL/TLS layer. http://www.mozilla.org/projects/security/pki/nss/ --- CHANGES | 5 + RELEASE-NOTES | 4 +- configure.ac | 83 ++++- docs/FAQ | 10 +- docs/FEATURES | 4 +- docs/INSTALL | 3 + docs/LICENSE-MIXING | 10 + docs/TODO | 8 +- docs/curl.1 | 11 + docs/libcurl/curl_easy_setopt.3 | 23 +- lib/Makefile.inc | 7 +- lib/http.c | 12 + lib/nss.c | 605 ++++++++++++++++++++++++++++++++ lib/nssg.h | 57 +++ lib/setup.h | 4 +- lib/sslgen.c | 71 +++- lib/urldata.h | 7 + tests/runtests.pl | 12 +- 18 files changed, 907 insertions(+), 29 deletions(-) create mode 100644 lib/nss.c create mode 100644 lib/nssg.h diff --git a/CHANGES b/CHANGES index 2e19fd2c4..ab04f4c3b 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,11 @@ Changelog Daniel (12 February 2007) +- Rob Crittenden added support for NSS (Network Security Service) for the + SSL/TLS layer. http://www.mozilla.org/projects/security/pki/nss/ + + This is the fourth supported library for TLS/SSL that libcurl supports! + - Shmulik Regev fixed so that the final CRLF of HTTP response headers are sent to the debug callback. diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 28d6623e3..670ccf439 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -14,6 +14,7 @@ This release includes the following changes: o Added CURLOPT_TIMEOUT_MS and CURLOPT_CONNECTTIMEOUT_MS o Added CURLOPT_HTTP_CONTENT_DECODING, CURLOPT_HTTP_TRANSFER_DECODING and --raw + o Added support for using the NSS library for TLS/SSL This release includes the following bugfixes: @@ -36,6 +37,7 @@ New curl mirrors: This release would not have looked like this without help, code, reports and advice from friends like these: - Yang Tse, Manfred Schwarb, Michael Wallner, Jeff Pohlmeyer, Shmulik Regev + Yang Tse, Manfred Schwarb, Michael Wallner, Jeff Pohlmeyer, Shmulik Regev, + Rob Crittenden Thanks! (and sorry if I forgot to mention someone) diff --git a/configure.ac b/configure.ac index dcc53dc6d..983bf540f 100644 --- a/configure.ac +++ b/configure.ac @@ -1183,6 +1183,7 @@ if test "$OPENSSL_ENABLED" != "1"; then [ AC_DEFINE(USE_GNUTLS, 1, [if GnuTLS is enabled]) AC_SUBST(USE_GNUTLS, [1]) + GNUTLS_ENABLED = 1 USE_GNUTLS="yes" curl_ssl_msg="enabled (GnuTLS)" ], @@ -1208,13 +1209,85 @@ if test "$OPENSSL_ENABLED" != "1"; then fi dnl GNUTLS not disabled - if test X"$USE_GNUTLS" != "Xyes"; then - AC_MSG_WARN([SSL disabled, you will not be able to use HTTPS, FTPS, NTLM and more.]) - AC_MSG_WARN([Use --with-ssl or --with-gnutls to address this.]) - fi - fi dnl OPENSSL != 1 +dnl ---------------------------------------------------- +dnl NSS. Only check if GnuTLS and OpenSSL are not enabled +dnl ---------------------------------------------------- + +dnl Default to compiler & linker defaults for NSS files & libraries. +OPT_NSS=no + +AC_ARG_WITH(nss,dnl +AC_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the installation root (default: /usr/local/)]) +AC_HELP_STRING([--without-nss], [disable NSS detection]), + OPT_NSS=$withval) + +if test "$OPENSSL_ENABLED" != "1" -a "$GNUTLS_ENABLED" != "1"; then + + if test X"$OPT_NSS" != Xno; then + if test "x$OPT_NSS" = "xyes"; then + check=`pkg-config --version 2>/dev/null` + if test -n "$check"; then + addlib=`pkg-config --libs nss` + addcflags=`pkg-config --cflags nss` + version=`pkg-config --modversion nss` + nssprefix=`pkg-config --variable=prefix nss` + fi + else + # Without pkg-config, we'll kludge in some defaults + addlib="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 -lpthread -ldl" + addcflags="-I$OPT_NSS/include" + version="unknown" + gtlsprefix=$OPT_GNUTLS + fi + if test -n "$addlib"; then + + CLEANLIBS="$LIBS" + CLEANCPPFLAGS="$CPPFLAGS" + + LIBS="$LIBS $addlib" + if test "$addcflags" != "-I/usr/include"; then + CPPFLAGS="$CPPFLAGS $addcflags" + fi + + AC_CHECK_LIB(nss3, NSS_Initialize, + [ + AC_DEFINE(USE_NSS, 1, [if NSS is enabled]) + AC_SUBST(USE_NSS, [1]) + USE_NSS="yes" + NSS_ENABLED=1 + curl_ssl_msg="enabled (NSS)" + ], + [ + LIBS="$CLEANLIBS" + CPPFLAGS="$CLEANCPPFLAGS" + ]) + + if test "x$USE_NSS" = "xyes"; then + AC_MSG_NOTICE([detected NSS version $version]) + + dnl when shared libs were found in a path that the run-time + dnl linker doesn't search through, we need to add it to + dnl LD_LIBRARY_PATH to prevent further configure tests to fail + dnl due to this + + LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$nssprefix/lib$libsuff" + export LD_LIBRARY_PATH + AC_MSG_NOTICE([Added $nssprefix/lib$libsuff to LD_LIBRARY_PATH]) + fi + + fi + + fi dnl NSS not disabled + +fi dnl OPENSSL != 1 -a GNUTLS_ENABLED != 1 + +if test "x$OPENSSL_ENABLED$GNUTLS_ENABLED$NSS_ENABLED" = "x"; then + AC_MSG_WARN([SSL disabled, you will not be able to use HTTPS, FTPS, NTLM and more.]) + AC_MSG_WARN([Use --with-ssl, --with-gnutls or --with-nss to address this.]) +fi + dnl ********************************************************************** dnl Check for the CA bundle dnl ********************************************************************** diff --git a/docs/FAQ b/docs/FAQ index ea8b8ccdd..e2bf53ae0 100644 --- a/docs/FAQ +++ b/docs/FAQ @@ -358,10 +358,10 @@ FAQ 2.2 Does curl work/build with other SSL libraries? - Curl has been written to use OpenSSL, GnuTLS or yassl, although there should - not be many problems using a different library. If anyone does "port" curl - to use a different SSL library, we are of course very interested in getting - the patch! + Curl has been written to use OpenSSL, GnuTLS, yassl or NSS, although there + should not be many problems using a different library. If anyone does "port" + curl to use a different SSL library, we are of course very interested in + getting the patch! 2.3 Where can I find a copy of LIBEAY32.DLL? @@ -844,6 +844,8 @@ FAQ http://www.gnu.org/software/gnutls/manual/html_node/Multi_002dthreaded-applications.html + No special locking is needed with a NSS-powered libcurl. NSS is thread-safe. + 5.2 How can I receive all data into a large memory chunk? [ See also the examples/getinmemory.c source ] diff --git a/docs/FEATURES b/docs/FEATURES index e5331fc81..1bbbfef08 100644 --- a/docs/FEATURES +++ b/docs/FEATURES @@ -116,10 +116,10 @@ FILE FOOTNOTES ========= - *1 = requires OpenSSL or GnuTLS + *1 = requires OpenSSL, GnuTLS or NSS *2 = requires OpenLDAP *3 = requires a GSSAPI-compliant library, such as Heimdal or similar. *4 = requires FBopenssl *5 = requires a krb4 library, such as the MIT one or similar. *6 = requires c-ares - *7 = requires OpenSSL specificly, as GnuTLS only supports SSLv3 and TLSv1 + *7 = requires OpenSSL or NSS, as GnuTLS only supports SSLv3 and TLSv1 diff --git a/docs/INSTALL b/docs/INSTALL index 6da338055..7b98ab215 100644 --- a/docs/INSTALL +++ b/docs/INSTALL @@ -140,6 +140,9 @@ UNIX yassl with its OpenSSL emulation enabled and point to that directory root with configure --with-ssl. + To build with NSS support instead of OpenSSL for SSL/TLS, note that + you need to use both --without-ssl and --with-nss. + Win32 ===== diff --git a/docs/LICENSE-MIXING b/docs/LICENSE-MIXING index 60f5f934c..c5a158446 100644 --- a/docs/LICENSE-MIXING +++ b/docs/LICENSE-MIXING @@ -47,6 +47,14 @@ yassl http://www.yassl.com/ (May be used for SSL/TLS support) Uses the GPL[1] license. If this is a problem for you, consider using OpenSSL or GnuTLS instead. +NSS http://www.mozilla.org/projects/security/pki/nss/ + + (May be used for SSL/TLS support) Is covered by the MPL[4] license, + the GPL[1] license and the LGPL[3] license. You may choose to license + the code under MPL terms, GPL terms, or LGPL terms. These licenses + grant you different permissions and impose different obligations. You + should select the license that best meets your needs. + c-ares http://daniel.haxx.se/projects/c-ares/license.html (Used for asynchronous name resolves) Uses an MIT license that is very @@ -110,3 +118,5 @@ OpenLDAP http://www.openldap.org/software/release/license.html how to write such an exception to the GPL [3] = LGPL - GNU Lesser General Public License: http://www.gnu.org/licenses/lgpl.html +[4] = MPL - Mozilla Public License: + http://www.mozilla.org/MPL/ diff --git a/docs/TODO b/docs/TODO index a19e47d94..53176a4e0 100644 --- a/docs/TODO +++ b/docs/TODO @@ -157,16 +157,14 @@ TODO Clark) * Make curl's SSL layer capable of using other free SSL libraries. Such as - Mozilla Security Services - (http://www.mozilla.org/projects/security/pki/nss/) or MatrixSSL - (http://www.matrixssl.org/). + MatrixSSL (http://www.matrixssl.org/). * Peter Sylvester's patch for SRP on the TLS layer. Awaits OpenSSL support for this, no need to support this in libcurl before there's an OpenSSL release that does it. - * make the configure --with-ssl option first check for OpenSSL and then for - GnuTLS if OpenSSL wasn't detected. + * make the configure --with-ssl option first check for OpenSSL, then GnuTLS, + then NSS... GnuTLS diff --git a/docs/curl.1 b/docs/curl.1 index cd69f08ff..881cd77a2 100644 --- a/docs/curl.1 +++ b/docs/curl.1 @@ -167,6 +167,10 @@ difference. must be using valid ciphers. Read up on SSL cipher list details on this URL: \fIhttp://www.openssl.org/docs/apps/ciphers.html\fP +NSS ciphers are done differently than OpenSSL and GnuTLS. The full list of +NSS ciphers is in the NSSCipherSuite entry at this URL: +\fIhttp://directory.fedora.redhat.com/docs/mod_nss.html#Directives\fP + If this option is used several times, the last one will override the others. .IP "--compressed" (HTTP) Request a compressed response using one of the algorithms libcurl @@ -323,6 +327,10 @@ this option assumes a \&"certificate" file that is the private key and the private certificate concatenated! See \fI--cert\fP and \fI--key\fP to specify them independently. +If curl is built against the NSS SSL library then this option tells +curl the nickname of the certificate to use within the NSS database defined +by --cacert. + If this option is used several times, the last one will be used. .IP "--cert-type " (SSL) Tells curl what certificate type the provided certificate is in. PEM, @@ -342,6 +350,9 @@ The windows version of curl will automatically look for a CA certs file named \'curl-ca-bundle.crt\', either in the same directory as curl.exe, or in the Current Working Directory, or in any folder along your PATH. +If curl is built against the NSS SSL library then this option tells +curl the directory that the NSS certificate database resides in. + If this option is used several times, the last one will be used. .IP "--capath " (SSL) Tells curl to use the specified certificate directory to verify the diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 0c55e1400..66ca11f34 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -1178,6 +1178,9 @@ transfers. (Added in 7.15.2) Pass a pointer to a zero terminated string as parameter. The string should be the file name of your certificate. The default format is "PEM" and can be changed with \fICURLOPT_SSLCERTTYPE\fP. + +With NSS this is the nickname of the certificate you wish to authenticate +with. .IP CURLOPT_SSLCERTTYPE Pass a pointer to a zero terminated string as parameter. The string should be the format of your certificate. Supported formats are "PEM" and "DER". (Added @@ -1222,8 +1225,8 @@ Pass a long as parameter to control what version of SSL/TLS to attempt to use. The available options are: .RS .IP CURL_SSLVERSION_DEFAULT -The default action. When libcurl built with OpenSSL, this will attempt to -figure out the remote SSL protocol version. Unfortunately there are a lot of +The default action. When libcurl built with OpenSSL or NSS, this will attempt +to figure out the remote SSL protocol version. Unfortunately there are a lot of ancient and broken servers in use which cannot handle this technique and will fail to connect. When libcurl is built with GnuTLS, this will mean SSLv3. .IP CURL_SSLVERSION_TLSv1 @@ -1266,6 +1269,9 @@ even indicate an accessible file. Note that option is by default set to the system path where libcurl's cacert bundle is assumed to be stored, as established at build time. + +When built against NSS this is the directory that the NSS certificate +database resides in. .IP CURLOPT_CAPATH Pass a char * to a zero terminated string naming a directory holding multiple CA certificates to verify the peer with. The certificate directory must be @@ -1315,12 +1321,23 @@ Pass a char *, pointing to a zero terminated string holding the list of ciphers to use for the SSL connection. The list must be syntactically correct, it consists of one or more cipher strings separated by colons. Commas or spaces are also acceptable separators but colons are normally used, \!, \- and \+ can -be used as operators. Valid examples of cipher lists include 'RC4-SHA', +be used as operators. + +For OpenSSL and GnuTLS valid examples of cipher lists include 'RC4-SHA', \'SHA1+DES\', 'TLSv1' and 'DEFAULT'. The default list is normally set when you compile OpenSSL. You'll find more details about cipher lists on this URL: \fIhttp://www.openssl.org/docs/apps/ciphers.html\fP + +For NSS valid examples of cipher lists include 'rsa_rc4_128_md5', +\'rsa_aes_128_sha\', etc. With NSS you don't add/remove ciphers. If one uses +this option then all known ciphers are disabled and only those passed in +are enabled. + +You'll find more details about the NSS cipher lists on this URL: +\fIhttp://directory.fedora.redhat.com/docs/mod_nss.html#Directives\fP + .IP CURLOPT_SSL_SESSIONID_CACHE Pass a long set to 0 to disable libcurl's use of SSL session-ID caching. Set this to 1 to enable it. By default all transfers are done using the diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 69e2d420f..3985cefbf 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -8,7 +8,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ content_encoding.c share.c http_digest.c md5.c http_negotiate.c \ http_ntlm.c inet_pton.c strtoofft.c strerror.c hostares.c hostasyn.c \ hostip4.c hostip6.c hostsyn.c hostthre.c inet_ntop.c parsedate.c \ - select.c gtls.c sslgen.c tftp.c splay.c strdup.c socks.c ssh.c + select.c gtls.c sslgen.c tftp.c splay.c strdup.c socks.c ssh.c nss.c HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ @@ -18,6 +18,5 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ share.h md5.h http_digest.h http_negotiate.h http_ntlm.h ca-bundle.h \ inet_pton.h strtoofft.h strerror.h inet_ntop.h curlx.h memory.h \ setup.h transfer.h select.h easyif.h multiif.h parsedate.h sslgen.h \ - gtls.h tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h ssh.h - - + gtls.h tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h ssh.h \ + nssg.h diff --git a/lib/http.c b/lib/http.c index bca1197d8..8b9e5665c 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1502,6 +1502,18 @@ int Curl_https_getsock(struct connectdata *conn, (void)numsocks; return GETSOCK_BLANK; } +#else +#ifdef USE_NSS +int Curl_https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + (void)conn; + (void)socks; + (void)numsocks; + return GETSOCK_BLANK; +} +#endif #endif #endif diff --git a/lib/nss.c b/lib/nss.c new file mode 100644 index 000000000..82c218d0a --- /dev/null +++ b/lib/nss.c @@ -0,0 +1,605 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +/* + * Source file for all NSS-specific code for the TLS/SSL layer. No code + * but sslgen.c should ever call or use these functions. + */ + +#include "setup.h" + +#include +#include +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#include "urldata.h" +#include "sendf.h" +#include "formdata.h" /* for the boundary function */ +#include "url.h" /* for the ssl config check function */ +#include "connect.h" /* Curl_sockerrno() proto */ +#include "strequal.h" +#include "select.h" +#include "sslgen.h" + +#define _MPRINTF_REPLACE /* use the internal *printf() functions */ +#include + +#ifdef USE_NSS + +#include "nssg.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "memory.h" +#include "easyif.h" /* for Curl_convert_from_utf8 prototype */ + +/* The last #include file should be: */ +#include "memdebug.h" + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +PRFileDesc *PR_ImportTCPSocket(PRInt32 osfd); + +static int initialized = 0; +static int noverify = 0; + +typedef struct { + PRInt32 retryCount; + struct SessionHandle *data; +} pphrase_arg_t; + +typedef struct { + const char *name; + int num; + PRInt32 version; /* protocol version valid for this cipher */ +} cipher_s; + +/* the table itself is defined in nss_engine_init.c */ +#ifdef NSS_ENABLE_ECC +#define ciphernum 48 +#else +#define ciphernum 23 +#endif + +enum sslversion { SSL2 = 1, SSL3 = 2, TLS = 4 }; + +cipher_s cipherlist[ciphernum] = { + /* SSL2 cipher suites */ + {"rc4", SSL_EN_RC4_128_WITH_MD5, SSL2}, + {"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5, SSL2}, + {"rc2", SSL_EN_RC2_128_CBC_WITH_MD5, SSL2}, + {"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, SSL2}, + {"des", SSL_EN_DES_64_CBC_WITH_MD5, SSL2}, + {"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5, SSL2}, + /* SSL3/TLS cipher suites */ + {"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5, SSL3 | TLS}, + {"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA, SSL3 | TLS}, + {"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL3 | TLS}, + {"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA, SSL3 | TLS}, + {"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL3 | TLS}, + {"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, SSL3 | TLS}, + {"rsa_null_md5", SSL_RSA_WITH_NULL_MD5, SSL3 | TLS}, + {"rsa_null_sha", SSL_RSA_WITH_NULL_SHA, SSL3 | TLS}, + {"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, SSL3 | TLS}, + {"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA, SSL3 | TLS}, + {"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, SSL3 | TLS}, + {"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA, SSL3 | TLS}, + {"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA, SSL3 | TLS}, + /* TLS 1.0: Exportable 56-bit Cipher Suites. */ + {"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, SSL3 | TLS}, + {"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, SSL3 | TLS}, + /* AES ciphers. */ + {"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA, SSL3 | TLS}, + {"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA, SSL3 | TLS}, +#ifdef NSS_ENABLE_ECC + /* ECC ciphers. */ + {"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA, TLS}, + {"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA, TLS}, + {"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS}, + {"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS}, + {"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS}, + {"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA, TLS}, + {"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS}, + {"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS}, + {"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS}, + {"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS}, + {"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA, TLS}, + {"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA, TLS}, + {"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, TLS}, + {"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS}, + {"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS}, + {"echde_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA, TLS}, + {"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS}, + {"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS}, + {"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS}, + {"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS}, + {"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA, TLS}, + {"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA, TLS}, + {"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, TLS}, + {"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA, TLS}, + {"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA, TLS}, +#endif +}; + +static SECStatus set_ciphers(struct SessionHandle *data, PRFileDesc * model, + char *cipher_list) +{ + int i; + PRBool cipher_state[ciphernum]; + PRBool found; + char *cipher; + SECStatus rv; + + /* First disable all ciphers. This uses a different max value in case + * NSS adds more ciphers later we don't want them available by + * accident + */ + for(i=0; idata->set.key_passwd) + return (char *)PORT_Strdup((char *)parg->data->set.key_passwd); + else + return NULL; +} + +static SECStatus nss_Init_Tokens(struct connectdata * conn) +{ + PK11SlotList *slotList; + PK11SlotListElement *listEntry; + SECStatus ret, status = SECSuccess; + pphrase_arg_t *parg; + + parg = (pphrase_arg_t *) malloc(sizeof(*parg)); + parg->retryCount = 0; + parg->data = conn->data; + + PK11_SetPasswordFunc(nss_get_password); + + slotList = + PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE, NULL); + + for(listEntry = PK11_GetFirstSafe(slotList); + listEntry; listEntry = listEntry->next) { + PK11SlotInfo *slot = listEntry->slot; + + if(PK11_NeedLogin(slot) && PK11_NeedUserInit(slot)) { + if(slot == PK11_GetInternalKeySlot()) { + failf(conn->data, "The NSS database has not been initialized.\n"); + } + else { + failf(conn->data, "The token %s has not been initialized.", + PK11_GetTokenName(slot)); + } + PK11_FreeSlot(slot); + continue; + } + + ret = PK11_Authenticate(slot, PR_TRUE, parg); + if(SECSuccess != ret) { + status = SECFailure; + break; + } + parg->retryCount = 0; /* reset counter to 0 for the next token */ + PK11_FreeSlot(slot); + } + + free(parg); + return status; +} + +static SECStatus BadCertHandler(void *arg, PRFileDesc * socket) +{ + SECStatus success = SECSuccess; + (void)arg; + (void)socket; + + return success; +} + +/** + * Inform the application that the handshake is complete. + */ +static SECStatus HandshakeCallback(PRFileDesc * socket, void *arg) +{ + (void)socket; + (void)arg; + return SECSuccess; +} + +/** + * + * Callback to pick the SSL client certificate. + */ +static SECStatus SelectClientCert(void *arg, PRFileDesc * socket, + struct CERTDistNamesStr * caNames, + struct CERTCertificateStr ** pRetCert, + struct SECKEYPrivateKeyStr ** pRetKey) +{ + CERTCertificate *cert; + SECKEYPrivateKey *privKey; + char *nickname = (char *)arg; + void *proto_win = NULL; + SECStatus secStatus = SECFailure; + (void)caNames; + + proto_win = SSL_RevealPinArg(socket); + + cert = PK11_FindCertFromNickname(nickname, proto_win); + if(cert) { + privKey = PK11_FindKeyByAnyCert(cert, proto_win); + if(privKey) { + secStatus = SECSuccess; + } + else { + CERT_DestroyCertificate(cert); + } + } + + if(secStatus == SECSuccess) { + *pRetCert = cert; + *pRetKey = privKey; + } + + return secStatus; +} + +/** + * Global SSL init + * + * @retval 0 error initializing SSL + * @retval 1 SSL initialized successfully + */ +int Curl_nss_init(void) +{ + if(!initialized) + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 256); + + /* We will actually initialize NSS later */ + + return 1; +} + +/* Global cleanup */ +void Curl_nss_cleanup(void) +{ + NSS_Shutdown(); + initialized = 0; +} + +/* + * This function uses SSL_peek to determine connection status. + * + * Return codes: + * 1 means the connection is still in place + * 0 means the connection has been closed + * -1 means the connection status is unknown + */ +int +Curl_nss_check_cxn(struct connectdata *conn) +{ + int rc; + char buf; + + rc = + PR_Recv(conn->ssl[FIRSTSOCKET].handle, (void *)&buf, 1, PR_MSG_PEEK, + PR_SecondsToInterval(1)); + if(rc > 0) + return 1; /* connection still in place */ + + if(rc == 0) + return 0; /* connection has been closed */ + + return -1; /* connection status unknown */ +} + +/* + * This function is called when an SSL connection is closed. + */ +void Curl_nss_close(struct connectdata *conn) +{ + int i; + + for(i=0; i<2; i++) { + struct ssl_connect_data *connssl = &conn->ssl[i]; + + if(connssl->handle) { + PR_Close(connssl->handle); + connssl->handle = NULL; + } + connssl->use = FALSE; /* get back to ordinary socket usage */ + } +} + +/* + * This function is called when the 'data' struct is going away. Close + * down everything and free all resources! + */ +int Curl_nss_close_all(struct SessionHandle *data) +{ + (void)data; + return 0; +} + +CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) +{ + PRInt32 err; + PRFileDesc *model = NULL; + PRBool ssl2, ssl3, tlsv1; + struct SessionHandle *data = conn->data; + curl_socket_t sockfd = conn->sock[sockindex]; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + SECStatus rv; + int curlerr = CURLE_SSL_CONNECT_ERROR; + + /* FIXME. NSS doesn't support multiple databases open at the same time. */ + if(!initialized) { + if(!data->set.ssl.CAfile) { + if(data->set.ssl.verifypeer) { + failf(data, "No NSS cacert database specified."); + return CURLE_SSL_CACERT_BADFILE; + } + else { + rv = NSS_NoDB_Init(NULL); + noverify = 1; + } + } + else { + rv = NSS_Initialize(data->set.ssl.CAfile, NULL, NULL, "secmod.db", + NSS_INIT_READONLY); + } + if(rv != SECSuccess) { + curlerr = CURLE_SSL_CACERT_BADFILE; + goto error; + } + } + + NSS_SetDomesticPolicy(); + + model = PR_NewTCPSocket(); + if(!model) + goto error; + model = SSL_ImportFD(NULL, model); + + if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) + goto error; + + ssl2 = ssl3 = tlsv1 = PR_FALSE; + + switch (data->set.ssl.version) { + default: + case CURL_SSLVERSION_DEFAULT: + ssl2 = ssl3 = tlsv1 = PR_TRUE; + break; + case CURL_SSLVERSION_TLSv1: + tlsv1 = PR_TRUE; + break; + case CURL_SSLVERSION_SSLv2: + ssl2 = PR_TRUE; + break; + case CURL_SSLVERSION_SSLv3: + ssl3 = PR_TRUE; + break; + } + + if(SSL_OptionSet(model, SSL_ENABLE_SSL2, ssl2) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_ENABLE_SSL3, ssl3) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_ENABLE_TLS, tlsv1) != SECSuccess) + goto error; + + if(data->set.ssl.cipher_list) { + if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) + goto error; + } + + if(SSL_BadCertHook(model, (SSLBadCertHandler) BadCertHandler, NULL) + != SECSuccess) + goto error; + if(SSL_HandshakeCallback(model, (SSLHandshakeCallback) HandshakeCallback, + NULL) != SECSuccess) + goto error; + + if(data->set.cert) { + if(SSL_GetClientAuthDataHook(model, + (SSLGetClientAuthData) SelectClientCert, + (void *)data->set.cert) != SECSuccess) { + curlerr = CURLE_SSL_CERTPROBLEM; + goto error; + } + if(nss_Init_Tokens(conn) != SECSuccess) + goto error; + } + + /* Import our model socket onto the existing file descriptor */ + connssl->handle = PR_ImportTCPSocket(sockfd); + connssl->handle = SSL_ImportFD(model, connssl->handle); + if(!connssl->handle) + goto error; + + /* Force handshake on next I/O */ + SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE); + + SSL_SetURL(connssl->handle, conn->host.name); + + return CURLE_OK; + +error: + err = PR_GetError(); + failf(data, "NSS error %d", err); + if(model) + PR_Close(model); + return curlerr; +} + +/* return number of sent (non-SSL) bytes */ +int Curl_nss_send(struct connectdata *conn, /* connection data */ + int sockindex, /* socketindex */ + void *mem, /* send this data */ + size_t len) /* amount to write */ +{ + PRInt32 err; + struct SessionHandle *data = conn->data; + PRInt32 timeout; + int rc; + + if(data->set.timeout) + timeout = PR_MillisecondsToInterval(data->set.timeout); + else + timeout = PR_MillisecondsToInterval(DEFAULT_CONNECT_TIMEOUT); + + rc = PR_Send(conn->ssl[sockindex].handle, mem, (int)len, 0, timeout); + + if(rc < 0) { + err = PR_GetError(); + + if(err == PR_IO_TIMEOUT_ERROR) { + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEOUTED; + } + + failf(conn->data, "SSL write: error %d\n", err); + return -1; + } + return rc; /* number of bytes */ +} + +/* + * If the read would block we return -1 and set 'wouldblock' to TRUE. + * Otherwise we return the amount of data read. Other errors should return -1 + * and set 'wouldblock' to FALSE. + */ +ssize_t Curl_nss_recv(struct connectdata * conn, /* connection data */ + int num, /* socketindex */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + bool * wouldblock) +{ + ssize_t nread; + struct SessionHandle *data = conn->data; + PRInt32 timeout; + + if(data->set.timeout) + timeout = PR_SecondsToInterval(data->set.timeout); + else + timeout = PR_MillisecondsToInterval(DEFAULT_CONNECT_TIMEOUT); + + nread = PR_Recv(conn->ssl[num].handle, buf, (int)buffersize, 0, timeout); + *wouldblock = FALSE; + if(nread < 0) { + /* failed SSL read */ + PRInt32 err = PR_GetError(); + + if(err == PR_WOULD_BLOCK_ERROR) { + *wouldblock = TRUE; + return -1; /* basically EWOULDBLOCK */ + } + if(err == PR_IO_TIMEOUT_ERROR) { + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEOUTED; + } + failf(conn->data, "SSL read: errno %d", err); + return -1; + } + return nread; +} + +size_t Curl_nss_version(char *buffer, size_t size) +{ + return snprintf(buffer, size, " NSS/%s", NSS_VERSION); +} +#endif /* USE_NSS */ diff --git a/lib/nssg.h b/lib/nssg.h new file mode 100644 index 000000000..3774c0fb3 --- /dev/null +++ b/lib/nssg.h @@ -0,0 +1,57 @@ +#ifndef __NSSG_H +#define __NSSG_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +/* + * This header should only be needed to get included by sslgen.c and nss.c + */ + +#include "urldata.h" +CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex); +CURLcode Curl_nss_connect_nonblocking(struct connectdata *conn, + int sockindex, + bool *done); +void Curl_nss_close(struct connectdata *conn); /* close a SSL connection */ +/* tell NSS to close down all open information regarding connections (and + thus session ID caching etc) */ +int Curl_nss_close_all(struct SessionHandle *data); + +int Curl_nss_init(void); +void Curl_nss_cleanup(void); + +int Curl_nss_send(struct connectdata *conn, + int sockindex, + void *mem, + size_t len); +ssize_t Curl_nss_recv(struct connectdata *conn, /* connection data */ + int num, /* socketindex */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + bool *wouldblock); + +size_t Curl_nss_version(char *buffer, size_t size); +int Curl_nss_check_cxn(struct connectdata *cxn); +int Curl_nss_seed(struct SessionHandle *data); + +#endif diff --git a/lib/setup.h b/lib/setup.h index 813ef07bf..ca8155b3a 100644 --- a/lib/setup.h +++ b/lib/setup.h @@ -348,8 +348,8 @@ int fileno( FILE *stream); #define HAVE_INET_NTOA_R_2_ARGS 1 #endif -#if defined(USE_GNUTLS) || defined(USE_SSLEAY) -#define USE_SSL /* Either OpenSSL || GnuTLS */ +#if defined(USE_GNUTLS) || defined(USE_SSLEAY) || defined(USE_NSS) +#define USE_SSL /* Either OpenSSL || GnuTLS || NSS */ #endif #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_NTLM) diff --git a/lib/sslgen.c b/lib/sslgen.c index 5b75c3807..9043cee29 100644 --- a/lib/sslgen.c +++ b/lib/sslgen.c @@ -31,6 +31,7 @@ Curl_ssl_ - prefix for generic ones Curl_ossl_ - prefix for OpenSSL ones Curl_gtls_ - prefix for GnuTLS ones + Curl_nss_ - prefix for NSS ones "SSL/TLS Strong Encryption: An Introduction" http://httpd.apache.org/docs-2.0/ssl/ssl_intro.html @@ -52,6 +53,7 @@ #include "sslgen.h" /* generic SSL protos etc */ #include "ssluse.h" /* OpenSSL versions */ #include "gtls.h" /* GnuTLS versions */ +#include "nssg.h" /* NSS versions */ #include "sendf.h" #include "strequal.h" #include "url.h" @@ -168,9 +170,13 @@ int Curl_ssl_init(void) #else #ifdef USE_GNUTLS return Curl_gtls_init(); +#else +#ifdef USE_NSS + return Curl_nss_init(); #else /* no SSL support */ return 1; +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ } @@ -186,6 +192,9 @@ void Curl_ssl_cleanup(void) #else #ifdef USE_GNUTLS Curl_gtls_cleanup(); +#ifdef USE_NSS + Curl_nss_cleanup(); +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ init_ssl = FALSE; @@ -204,6 +213,10 @@ Curl_ssl_connect(struct connectdata *conn, int sockindex) #else #ifdef USE_GNUTLS return Curl_gtls_connect(conn, sockindex); +#else +#ifdef USE_NSS + return Curl_nss_connect(conn, sockindex); +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ @@ -224,12 +237,17 @@ Curl_ssl_connect_nonblocking(struct connectdata *conn, int sockindex, conn->ssl[sockindex].use = TRUE; return Curl_ossl_connect_nonblocking(conn, sockindex, done); +#else +#ifdef USE_NSS + *done = TRUE; /* fallback to BLOCKING */ + return Curl_nss_connect(conn, sockindex); #else /* not implemented! fallback to BLOCKING call. */ *done = TRUE; return Curl_ssl_connect(conn, sockindex); -#endif +#endif /* USE_NSS */ +#endif /* USE_SSLEAY */ } #ifdef USE_SSL @@ -283,8 +301,14 @@ static int kill_session(struct curl_ssl_session *session) #ifdef USE_SSLEAY Curl_ossl_session_free(session->sessionid); #else +#ifdef USE_GNUTLS Curl_gtls_session_free(session->sessionid); -#endif +#else +#ifdef USE_NSS + /* NSS has its own session ID cache */ +#endif /* USE_NSS */ +#endif /* USE_GNUTLS */ +#endif /* USE_SSLEAY */ session->sessionid=NULL; session->age = 0; /* fresh */ @@ -375,6 +399,10 @@ void Curl_ssl_close_all(struct SessionHandle *data) #else #ifdef USE_GNUTLS Curl_gtls_close_all(data); +#else +#ifdef USE_NSS + Curl_nss_close_all(data); +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ #else /* USE_SSL */ @@ -390,8 +418,12 @@ void Curl_ssl_close(struct connectdata *conn) #else #ifdef USE_GNUTLS Curl_gtls_close(conn); +#else +#ifdef USE_GNUTLS + Curl_nss_close(conn); #else (void)conn; +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ } @@ -428,11 +460,18 @@ CURLcode Curl_ssl_set_engine(struct SessionHandle *data, const char *engine) (void)data; (void)engine; return CURLE_FAILED_INIT; +#else +#ifdef USE_NSS + /* NSS doesn't set an engine this way */ + (void)data; + (void)engine; + return CURLE_FAILED_INIT; #else /* no SSL layer */ (void)data; (void)engine; return CURLE_FAILED_INIT; +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ } @@ -448,10 +487,16 @@ CURLcode Curl_ssl_set_engine_default(struct SessionHandle *data) /* FIX: add code here */ (void)data; return CURLE_FAILED_INIT; +#else +#ifdef USE_NSS + /* A no-op for NSS */ + (void)data; + return CURLE_FAILED_INIT; #else /* No SSL layer */ (void)data; return CURLE_FAILED_INIT; +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ } @@ -467,8 +512,14 @@ struct curl_slist *Curl_ssl_engines_list(struct SessionHandle *data) (void)data; return NULL; #else +#ifdef USE_NSS + /* In theory we could return the PKCS#11 modules loaded but that + * would just confuse things */ (void)data; return NULL; + (void)data; + return NULL; +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ } @@ -484,12 +535,16 @@ ssize_t Curl_ssl_send(struct connectdata *conn, #else #ifdef USE_GNUTLS return Curl_gtls_send(conn, sockindex, mem, len); +#else +#ifdef USE_NSS + return Curl_nss_send(conn, sockindex, mem, len); #else (void)conn; (void)sockindex; (void)mem; (void)len; return 0; +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ } @@ -514,6 +569,10 @@ ssize_t Curl_ssl_recv(struct connectdata *conn, /* connection data */ #else #ifdef USE_GNUTLS nread = Curl_gtls_recv(conn, sockindex, mem, len, &block); +#else +#ifdef USE_NSS + nread = Curl_nss_recv(conn, sockindex, mem, len, &block); +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ if(nread == -1) { @@ -573,10 +632,14 @@ size_t Curl_ssl_version(char *buffer, size_t size) #else #ifdef USE_GNUTLS return Curl_gtls_version(buffer, size); +#else +#ifdef USE_NSS + return Curl_nss_version(buffer, size); #else (void)buffer; (void)size; return 0; /* no SSL support */ +#endif /* USE_NSS */ #endif /* USE_GNUTLS */ #endif /* USE_SSLEAY */ } @@ -594,10 +657,14 @@ int Curl_ssl_check_cxn(struct connectdata *conn) { #ifdef USE_SSLEAY return Curl_ossl_check_cxn(conn); +#else +#ifdef USE_NSS + return Curl_nss_check_cxn(conn); #else (void)conn; /* TODO: we lack implementation of this for GnuTLS */ return -1; /* connection status unknown */ +#endif /* USE_NSS */ #endif /* USE_SSLEAY */ } diff --git a/lib/urldata.h b/lib/urldata.h index 3ba7fcacd..853ea44c1 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -78,6 +78,10 @@ #include #endif +#ifdef USE_NSS +#include +#endif + #ifdef HAVE_NETINET_IN_H #include #endif @@ -169,6 +173,9 @@ struct ssl_connect_data { gnutls_session session; gnutls_certificate_credentials cred; #endif /* USE_GNUTLS */ +#ifdef USE_NSS + PRFileDesc *handle; +#endif /* USE_NSS */ }; struct ssl_config_data { diff --git a/tests/runtests.pl b/tests/runtests.pl index 5ed2298b1..ad288de37 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -143,6 +143,7 @@ my $has_getrlimit; # set if system has getrlimit() my $has_ntlm; # set if libcurl is built with NTLM support my $has_openssl; # set if libcurl is built with OpenSSL my $has_gnutls; # set if libcurl is built with GnuTLS +my $has_nss; # set if libcurl is built with NSS my $has_textaware; # set if running on a system that has a text mode concept # on files. Windows for example @@ -955,6 +956,10 @@ sub checksystem { # GnuTLS in use $has_gnutls=1; } + elsif ($libcurl =~ /nss/i) { + # NSS in use + $has_nss=1; + } } elsif($_ =~ /^Protocols: (.*)/i) { # these are the supported protocols, we don't use this knowledge @@ -1083,7 +1088,7 @@ sub checksystem { if($ssl_version) { logmsg sprintf("* SSL library: %s\n", - $has_gnutls?"GnuTLS":($has_openssl?"OpenSSL":"")); + $has_gnutls?"GnuTLS":($has_openssl?"OpenSSL":($has_nss?"NSS":""))); } $has_textaware = ($^O eq 'MSWin32') || ($^O eq 'msys'); @@ -1179,6 +1184,11 @@ sub singletest { next; } } + elsif($f eq "NSS") { + if($has_nss) { + next; + } + } elsif($f eq "netrc_debug") { if($curl_debug) { next;