* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
#include <cert.h>
#include <prerror.h>
+#define NSSVERNUM ((NSS_VMAJOR<<16)|(NSS_VMINOR<<8)|NSS_VPATCH)
+
+#if NSSVERNUM >= 0x030f00 /* 3.15.0 */
+#include <ocsp.h>
+#endif
+
#include "curl_memory.h"
#include "rawstr.h"
#include "warnless.h"
CK_BBOOL ckfalse = CK_FALSE;
CK_ATTRIBUTE attrs[/* max count of attributes */ 4];
int attr_cnt = 0;
- CURLcode err = (cacert)
+ CURLcode result = (cacert)
? CURLE_SSL_CACERT_BADFILE
: CURLE_SSL_CERTPROBLEM;
slot = PK11_FindSlotByName(slot_name);
free(slot_name);
if(!slot)
- return err;
+ return result;
PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class));
PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL));
obj = PK11_CreateGenericObject(slot, attrs, attr_cnt, PR_FALSE);
PK11_FreeSlot(slot);
if(!obj)
- return err;
+ return result;
if(!Curl_llist_insert_next(ssl->obj_list, ssl->obj_list->tail, obj)) {
PK11_DestroyGenericObject(obj);
static CURLcode nss_load_cert(struct ssl_connect_data *ssl,
const char *filename, PRBool cacert)
{
- CURLcode err = (cacert)
+ CURLcode result = (cacert)
? CURLE_SSL_CACERT_BADFILE
: CURLE_SSL_CERTPROBLEM;
/* libnsspem.so leaks memory if the requested file does not exist. For more
* details, go to <https://bugzilla.redhat.com/734760>. */
if(is_file(filename))
- err = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert);
+ result = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert);
- if(CURLE_OK == err && !cacert) {
+ if(!result && !cacert) {
/* we have successfully loaded a client certificate */
CERTCertificate *cert;
char *nickname = NULL;
}
}
- return err;
+ return result;
}
/* add given CRL to cache if it is not already there */
/* CRL already cached */
SEC_DestroyCrl(crl);
SECITEM_FreeItem(crl_der, PR_TRUE);
- return CURLE_SSL_CRL_BADFILE;
+ return CURLE_OK;
}
/* acquire lock before call of CERT_CacheCRL() and accessing nss_crl_list */
{
PK11SlotInfo *slot;
SECStatus status;
- CURLcode rv;
+ CURLcode result;
struct ssl_connect_data *ssl = conn->ssl;
+
(void)sockindex; /* unused */
- rv = nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE);
- if(CURLE_OK != rv) {
+ result = nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE);
+ if(result) {
PR_SetError(SEC_ERROR_BAD_KEY, 0);
- return rv;
+ return result;
}
slot = PK11_FindSlotByName("PEM Token #1");
status = PK11_Authenticate(slot, PR_TRUE,
conn->data->set.str[STRING_KEY_PASSWD]);
PK11_FreeSlot(slot);
- return (SECSuccess == status)
- ? CURLE_OK
- : CURLE_SSL_CERTPROBLEM;
+
+ return (SECSuccess == status) ? CURLE_OK : CURLE_SSL_CERTPROBLEM;
}
static int display_error(struct connectdata *conn, PRInt32 err,
char *cert_file, char *key_file)
{
struct SessionHandle *data = conn->data;
- CURLcode rv;
+ CURLcode result;
if(cert_file) {
- rv = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE);
- if(CURLE_OK != rv) {
+ result = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE);
+ if(result) {
const PRErrorCode err = PR_GetError();
if(!display_error(conn, err, cert_file)) {
const char *err_name = nss_error_to_name(err);
failf(data, "unable to load client cert: %d (%s)", err, err_name);
}
- return rv;
+ return result;
}
}
if(key_file || (is_file(cert_file))) {
if(key_file)
- rv = nss_load_key(conn, sockindex, key_file);
+ result = nss_load_key(conn, sockindex, key_file);
else
/* In case the cert file also has the key */
- rv = nss_load_key(conn, sockindex, cert_file);
- if(CURLE_OK != rv) {
+ result = nss_load_key(conn, sockindex, cert_file);
+ if(result) {
const PRErrorCode err = PR_GetError();
if(!display_error(conn, err, key_file)) {
const char *err_name = nss_error_to_name(err);
failf(data, "unable to load client key: %d (%s)", err, err_name);
}
- return rv;
+ return result;
}
}
static char * nss_get_password(PK11SlotInfo * slot, PRBool retry, void *arg)
{
(void)slot; /* unused */
+
if(retry || NULL == arg)
return NULL;
else
PRBool isServer)
{
struct connectdata *conn = (struct connectdata *)arg;
+
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ if(conn->data->set.ssl.verifystatus) {
+ SECStatus cacheResult;
+
+ const SECItemArray *csa = SSL_PeerStapledOCSPResponses(fd);
+ if(!csa) {
+ failf(conn->data, "Invalid OCSP response");
+ return SECFailure;
+ }
+
+ if(csa->len == 0) {
+ failf(conn->data, "No OCSP response received");
+ return SECFailure;
+ }
+
+ cacheResult = CERT_CacheOCSPResponseFromSideChannel(
+ CERT_GetDefaultCertDB(), SSL_PeerCertificate(fd),
+ PR_Now(), &csa->items[0], arg
+ );
+
+ if(cacheResult != SECSuccess) {
+ failf(conn->data, "Invalid OCSP response");
+ return cacheResult;
+ }
+ }
+#endif
+
if(!conn->data->set.ssl.verifypeer) {
infof(conn->data, "skipping SSL peer certificate verification\n");
return SECSuccess;
unsigned int buflen;
SSLNextProtoState state;
+ struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET];
+
if(!conn->data->set.ssl_enable_npn && !conn->data->set.ssl_enable_alpn) {
return;
}
if(SSL_GetNextProto(sock, &state, buf, &buflen, buflenmax) == SECSuccess) {
switch(state) {
- case SSL_NEXT_PROTO_NO_SUPPORT:
- case SSL_NEXT_PROTO_NO_OVERLAP:
+ case SSL_NEXT_PROTO_NO_SUPPORT:
+ case SSL_NEXT_PROTO_NO_OVERLAP:
+ if(connssl->asked_for_h2)
infof(conn->data, "TLS, neither ALPN nor NPN succeeded\n");
- return;
+ return;
#ifdef SSL_ENABLE_ALPN
- case SSL_NEXT_PROTO_SELECTED:
- infof(conn->data, "ALPN, server accepted to use %.*s\n", buflen, buf);
- break;
+ case SSL_NEXT_PROTO_SELECTED:
+ infof(conn->data, "ALPN, server accepted to use %.*s\n", buflen, buf);
+ break;
#endif
- case SSL_NEXT_PROTO_NEGOTIATED:
- infof(conn->data, "NPN, server accepted to use %.*s\n", buflen, buf);
- break;
+ case SSL_NEXT_PROTO_NEGOTIATED:
+ infof(conn->data, "NPN, server accepted to use %.*s\n", buflen, buf);
+ break;
}
if(buflen == NGHTTP2_PROTO_VERSION_ID_LEN &&
PR_Free(common_name);
}
-static void display_conn_info(struct connectdata *conn, PRFileDesc *sock)
+static CURLcode display_conn_info(struct connectdata *conn, PRFileDesc *sock)
{
+ CURLcode result = CURLE_OK;
SSLChannelInfo channel;
SSLCipherSuiteInfo suite;
CERTCertificate *cert;
}
cert = SSL_PeerCertificate(sock);
-
if(cert) {
infof(conn->data, "Server certificate:\n");
cert2 = cert3;
}
}
- Curl_ssl_init_certinfo(conn->data, i);
- for(i = 0; cert; cert = cert2) {
- Curl_extract_certinfo(conn, i++, (char *)cert->derCert.data,
- (char *)cert->derCert.data + cert->derCert.len);
- if(cert->isRoot) {
+
+ result = Curl_ssl_init_certinfo(conn->data, i);
+ if(!result) {
+ for(i = 0; cert; cert = cert2) {
+ result = Curl_extract_certinfo(conn, i++, (char *)cert->derCert.data,
+ (char *)cert->derCert.data +
+ cert->derCert.len);
+ if(result)
+ break;
+
+ if(cert->isRoot) {
+ CERT_DestroyCertificate(cert);
+ break;
+ }
+
+ cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA);
CERT_DestroyCertificate(cert);
- break;
}
- cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA);
- CERT_DestroyCertificate(cert);
}
}
}
- return;
+ return result;
}
static SECStatus BadCertHandler(void *arg, PRFileDesc *sock)
return SECSuccess;
}
-/* This function is supposed to decide, which error codes should be used
- * to conclude server is TLS intolerant.
- *
- * taken from xulrunner - nsNSSIOLayer.cpp
- */
-static PRBool
-isTLSIntoleranceError(PRInt32 err)
-{
- switch (err) {
- case SSL_ERROR_BAD_MAC_ALERT:
- case SSL_ERROR_BAD_MAC_READ:
- case SSL_ERROR_HANDSHAKE_FAILURE_ALERT:
- case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT:
- case SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE:
- case SSL_ERROR_ILLEGAL_PARAMETER_ALERT:
- case SSL_ERROR_NO_CYPHER_OVERLAP:
- case SSL_ERROR_BAD_SERVER:
- case SSL_ERROR_BAD_BLOCK_PADDING:
- case SSL_ERROR_UNSUPPORTED_VERSION:
- case SSL_ERROR_PROTOCOL_VERSION_ALERT:
- case SSL_ERROR_RX_MALFORMED_FINISHED:
- case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE:
- case SSL_ERROR_DECODE_ERROR_ALERT:
- case SSL_ERROR_RX_UNKNOWN_ALERT:
- return PR_TRUE;
- default:
- return PR_FALSE;
- }
-}
-
/* update blocking direction in case of PR_WOULD_BLOCK_ERROR */
static void nss_update_connecting_state(ssl_connect_state state, void *secret)
{
initparams.length = sizeof(initparams);
if(cert_dir) {
- const bool use_sql = NSS_VersionCheck("3.12.0");
- char *certpath = aprintf("%s%s", use_sql ? "sql:" : "", cert_dir);
+ char *certpath = aprintf("sql:%s", cert_dir);
if(!certpath)
return CURLE_OUT_OF_MEMORY;
{
char *cert_dir;
struct_stat st;
- CURLcode rv;
+ CURLcode result;
if(initialized)
return CURLE_OK;
nspr_io_methods.close = nspr_io_close;
}
- rv = nss_init_core(data, cert_dir);
- if(rv)
- return rv;
+ result = nss_init_core(data, cert_dir);
+ if(result)
+ return result;
if(num_enabled_ciphers() == 0)
NSS_SetDomesticPolicy();
initialized = 1;
+
return CURLE_OK;
}
CURLcode Curl_nss_force_init(struct SessionHandle *data)
{
- CURLcode rv;
+ CURLcode result;
if(!nss_initlock) {
- failf(data,
- "unable to initialize NSS, curl_global_init() should have been "
- "called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL");
+ failf(data, "unable to initialize NSS, curl_global_init() should have "
+ "been called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL");
return CURLE_FAILED_INIT;
}
PR_Lock(nss_initlock);
- rv = nss_init(data);
+ result = nss_init(data);
PR_Unlock(nss_initlock);
- return rv;
+
+ return result;
}
/* Global cleanup */
* 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 Curl_nss_close_all(struct SessionHandle *data)
{
(void)data;
- return 0;
}
/* return true if NSS can provide error code (and possibly msg) for the
const char *capath = data->set.ssl.CApath;
if(cafile) {
- CURLcode rv = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE);
- if(CURLE_OK != rv)
- return rv;
+ CURLcode result = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE);
+ if(result)
+ return result;
}
if(capath) {
static CURLcode nss_init_sslver(SSLVersionRange *sslver,
struct SessionHandle *data)
{
- switch (data->set.ssl.version) {
+ switch(data->set.ssl.version) {
default:
case CURL_SSLVERSION_DEFAULT:
- sslver->min = SSL_LIBRARY_VERSION_3_0;
- if(data->state.ssl_connect_retry) {
- infof(data, "TLS disabled due to previous handshake failure\n");
- sslver->max = SSL_LIBRARY_VERSION_3_0;
- return CURLE_OK;
- }
- /* intentional fall-through to default to highest TLS version if possible */
-
case CURL_SSLVERSION_TLSv1:
+ sslver->min = SSL_LIBRARY_VERSION_TLS_1_0;
#ifdef SSL_LIBRARY_VERSION_TLS_1_2
sslver->max = SSL_LIBRARY_VERSION_TLS_1_2;
#elif defined SSL_LIBRARY_VERSION_TLS_1_1
struct SessionHandle *data,
CURLcode curlerr)
{
- SSLVersionRange sslver;
PRErrorCode err = 0;
- /* reset the flag to avoid an infinite loop */
- data->state.ssl_connect_retry = FALSE;
-
if(is_nss_error(curlerr)) {
/* read NSPR error code */
err = PR_GetError();
Curl_llist_destroy(connssl->obj_list, NULL);
connssl->obj_list = NULL;
- if(connssl->handle
- && (SSL_VersionRangeGet(connssl->handle, &sslver) == SECSuccess)
- && (sslver.min == SSL_LIBRARY_VERSION_3_0)
- && (sslver.max != SSL_LIBRARY_VERSION_3_0)
- && isTLSIntoleranceError(err)) {
- /* schedule reconnect through Curl_retry_request() */
- data->state.ssl_connect_retry = TRUE;
- infof(data, "Error in TLS handshake, trying SSLv3...\n");
- return CURLE_OK;
- }
-
return curlerr;
}
struct SessionHandle *data = conn->data;
curl_socket_t sockfd = conn->sock[sockindex];
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
- CURLcode curlerr;
+ CURLcode result;
SSLVersionRange sslver = {
SSL_LIBRARY_VERSION_TLS_1_0, /* min */
#endif
#endif
-
- if(connssl->state == ssl_connection_complete)
- return CURLE_OK;
-
connssl->data = data;
/* list of all NSS objects we need to destroy in Curl_nss_close() */
/* FIXME. NSS doesn't support multiple databases open at the same time. */
PR_Lock(nss_initlock);
- curlerr = nss_init(conn->data);
- if(CURLE_OK != curlerr) {
+ result = nss_init(conn->data);
+ if(result) {
PR_Unlock(nss_initlock);
goto error;
}
- curlerr = CURLE_SSL_CONNECT_ERROR;
+ result = CURLE_SSL_CONNECT_ERROR;
if(!mod) {
char *configstring = aprintf("library=%s name=PEM", pem_library);
mod = NULL;
}
infof(data, "WARNING: failed to load NSS PEM library %s. Using "
- "OpenSSL PEM certificates will not work.\n", pem_library);
+ "OpenSSL PEM certificates will not work.\n", pem_library);
}
}
infof(data, "warning: support for SSL_CBC_RANDOM_IV not compiled in\n");
#endif
- /* reset the flag to avoid an infinite loop */
- data->state.ssl_connect_retry = FALSE;
-
if(data->set.ssl.cipher_list) {
if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) {
- curlerr = CURLE_SSL_CIPHER;
+ result = CURLE_SSL_CIPHER;
goto error;
}
}
if(data->set.ssl.verifypeer) {
const CURLcode rv = nss_load_ca_certificates(conn, sockindex);
- if(CURLE_OK != rv) {
- curlerr = rv;
+ if(rv) {
+ result = rv;
goto error;
}
}
if(data->set.ssl.CRLfile) {
const CURLcode rv = nss_load_crl(data->set.ssl.CRLfile);
- if(CURLE_OK != rv) {
- curlerr = rv;
+ if(rv) {
+ result = rv;
goto error;
}
infof(data, " CRLfile: %s\n", data->set.ssl.CRLfile);
else {
CURLcode rv = cert_stuff(conn, sockindex, data->set.str[STRING_CERT],
data->set.str[STRING_KEY]);
- if(CURLE_OK != rv) {
+ if(rv) {
/* failf() is already done in cert_stuff() */
- curlerr = rv;
+ result = rv;
goto error;
}
}
if(SSL_GetClientAuthDataHook(model, SelectClientCert,
(void *)connssl) != SECSuccess) {
- curlerr = CURLE_SSL_CERTPROBLEM;
+ result = CURLE_SSL_CERTPROBLEM;
goto error;
}
SSL_SetPKCS11PinArg(connssl->handle, data->set.str[STRING_KEY_PASSWD]);
}
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ if(data->set.ssl.verifystatus) {
+ if(SSL_OptionSet(connssl->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE)
+ != SECSuccess)
+ goto error;
+ }
+#endif
+
#ifdef USE_NGHTTP2
if(data->set.httpversion == CURL_HTTP_VERSION_2_0) {
#ifdef SSL_ENABLE_NPN
if(SSL_SetNextProtoNego(connssl->handle, alpn_protos, alpn_protos_len)
!= SECSuccess)
goto error;
+ connssl->asked_for_h2 = TRUE;
}
else {
infof(data, "SSL, can't negotiate HTTP/2.0 with neither NPN nor ALPN\n");
if(model)
PR_Close(model);
- return nss_fail_connect(connssl, data, curlerr);
+ return nss_fail_connect(connssl, data, result);
}
static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
{
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
struct SessionHandle *data = conn->data;
- CURLcode curlerr = CURLE_SSL_CONNECT_ERROR;
+ CURLcode result = CURLE_SSL_CONNECT_ERROR;
PRUint32 timeout;
/* check timeout situation */
const long time_left = Curl_timeleft(data, NULL, TRUE);
if(time_left < 0L) {
failf(data, "timed out before SSL handshake");
- curlerr = CURLE_OPERATION_TIMEDOUT;
+ result = CURLE_OPERATION_TIMEDOUT;
goto error;
}
/* blocking direction is updated by nss_update_connecting_state() */
return CURLE_AGAIN;
else if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN)
- curlerr = CURLE_PEER_FAILED_VERIFICATION;
+ result = CURLE_PEER_FAILED_VERIFICATION;
else if(conn->data->set.ssl.certverifyresult!=0)
- curlerr = CURLE_SSL_CACERT;
+ result = CURLE_SSL_CACERT;
goto error;
}
- connssl->state = ssl_connection_complete;
- conn->recv[sockindex] = nss_recv;
- conn->send[sockindex] = nss_send;
-
- display_conn_info(conn, connssl->handle);
+ result = display_conn_info(conn, connssl->handle);
+ if(result)
+ goto error;
if(data->set.str[STRING_SSL_ISSUERCERT]) {
SECStatus ret = SECFailure;
if(SECFailure == ret) {
infof(data,"SSL certificate issuer check failed\n");
- curlerr = CURLE_SSL_ISSUER_ERROR;
+ result = CURLE_SSL_ISSUER_ERROR;
goto error;
}
else {
return CURLE_OK;
error:
- return nss_fail_connect(connssl, data, curlerr);
+ return nss_fail_connect(connssl, data, result);
}
static CURLcode nss_connect_common(struct connectdata *conn, int sockindex,
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
struct SessionHandle *data = conn->data;
const bool blocking = (done == NULL);
- CURLcode rv;
+ CURLcode result;
+
+ if(connssl->state == ssl_connection_complete)
+ return CURLE_OK;
if(connssl->connecting_state == ssl_connect_1) {
- rv = nss_setup_connect(conn, sockindex);
- if(rv)
+ result = nss_setup_connect(conn, sockindex);
+ if(result)
/* we do not expect CURLE_AGAIN from nss_setup_connect() */
- return rv;
+ return result;
if(!blocking) {
/* in non-blocking mode, set NSS non-blocking mode before handshake */
- rv = nss_set_nonblock(connssl, data);
- if(rv)
- return rv;
+ result = nss_set_nonblock(connssl, data);
+ if(result)
+ return result;
}
connssl->connecting_state = ssl_connect_2;
}
- rv = nss_do_connect(conn, sockindex);
- switch(rv) {
+ result = nss_do_connect(conn, sockindex);
+ switch(result) {
case CURLE_OK:
break;
case CURLE_AGAIN:
return CURLE_OK;
/* fall through */
default:
- return rv;
+ return result;
}
if(blocking) {
/* in blocking mode, set NSS non-blocking mode _after_ SSL handshake */
- rv = nss_set_nonblock(connssl, data);
- if(rv)
- return rv;
+ result = nss_set_nonblock(connssl, data);
+ if(result)
+ return result;
}
else
/* signal completed SSL handshake */
*done = TRUE;
- connssl->connecting_state = ssl_connect_done;
+ connssl->state = ssl_connection_complete;
+ conn->recv[sockindex] = nss_recv;
+ conn->send[sockindex] = nss_send;
+
+ /* ssl_connect_done is never used outside, go back to the initial state */
+ connssl->connecting_state = ssl_connect_1;
+
return CURLE_OK;
}
? CURLE_SSL_CERTPROBLEM
: CURLE_SEND_ERROR;
}
+
return -1;
}
+
return rc; /* number of bytes */
}
? CURLE_SSL_CERTPROBLEM
: CURLE_RECV_ERROR;
}
+
return -1;
}
+
return nread;
}
return !!Curl_nss_force_init(data);
}
-void Curl_nss_random(struct SessionHandle *data,
- unsigned char *entropy,
- size_t length)
+/* data might be NULL */
+int Curl_nss_random(struct SessionHandle *data,
+ unsigned char *entropy,
+ size_t length)
{
- Curl_nss_seed(data); /* Initiate the seed if not already done */
+ if(data)
+ Curl_nss_seed(data); /* Initiate the seed if not already done */
+
if(SECSuccess != PK11_GenerateRandom(entropy, curlx_uztosi(length))) {
/* no way to signal a failure from here, we have to abort */
failf(data, "PK11_GenerateRandom() failed, calling abort()...");
abort();
}
+
+ return 0;
}
void Curl_nss_md5sum(unsigned char *tmp, /* input */
{
PK11Context *MD5pw = PK11_CreateDigestContext(SEC_OID_MD5);
unsigned int MD5out;
+
PK11_DigestOp(MD5pw, tmp, curlx_uztoui(tmplen));
PK11_DigestFinal(MD5pw, md5sum, &MD5out, curlx_uztoui(md5len));
PK11_DestroyContext(MD5pw, PR_TRUE);
}
+bool Curl_nss_cert_status_request(void)
+{
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
#endif /* USE_NSS */