Newer
Older
nss_crllock = PR_NewLock();
Daniel Stenberg
committed
}
/* We will actually initialize NSS later */
return 1;
}
CURLcode Curl_nss_force_init(struct SessionHandle *data)
{
CURLcode rv;
if(!nss_initlock) {
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;
rv = nss_init(data);
PR_Unlock(nss_initlock);
return rv;
}
/* Global cleanup */
void Curl_nss_cleanup(void)
{
Daniel Stenberg
committed
/* This function isn't required to be threadsafe and this is only done
* as a safety feature.
*/
PR_Lock(nss_initlock);
if(initialized) {
/* Free references to client certificates held in the SSL session cache.
* Omitting this hampers destruction of the security module owning
* the certificates. */
SSL_ClearSessionCache();
if(mod && SECSuccess == SECMOD_UnloadUserModule(mod)) {
SECMOD_DestroyModule(mod);
mod = NULL;
}
#ifdef HAVE_NSS_INITCONTEXT
NSS_ShutdownContext(nss_context);
nss_context = NULL;
#else /* HAVE_NSS_INITCONTEXT */
Daniel Stenberg
committed
NSS_Shutdown();
}
Daniel Stenberg
committed
PR_Unlock(nss_initlock);
PR_DestroyLock(nss_initlock);
PR_DestroyLock(nss_crllock);
Daniel Stenberg
committed
nss_initlock = NULL;
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
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.
*/
Daniel Stenberg
committed
void Curl_nss_close(struct connectdata *conn, int sockindex)
{
Daniel Stenberg
committed
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
Daniel Stenberg
committed
if(connssl->handle) {
/* NSS closes the socket we previously handed to it, so we must mark it
as closed to avoid double close */
Daniel Stenberg
committed
fake_sclose(conn->sock[sockindex]);
conn->sock[sockindex] = CURL_SOCKET_BAD;
if((connssl->client_nickname != NULL) || (connssl->obj_clicert != NULL))
/* A server might require different authentication based on the
* particular path being requested by the client. To support this
* scenario, we must ensure that a connection will never reuse the
* authentication data from a previous connection. */
SSL_InvalidateSession(connssl->handle);
if(connssl->client_nickname != NULL) {
free(connssl->client_nickname);
connssl->client_nickname = NULL;
}
/* destroy all NSS objects in order to avoid failure of NSS shutdown */
Curl_llist_destroy(connssl->obj_list, NULL);
connssl->obj_list = NULL;
PR_Close(connssl->handle);
Daniel Stenberg
committed
connssl->handle = NULL;
}
}
/*
* 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;
}
/* return true if NSS can provide error code (and possibly msg) for the
error */
static bool is_nss_error(CURLcode err)
case CURLE_PEER_FAILED_VERIFICATION:
case CURLE_SSL_CACERT:
case CURLE_SSL_CERTPROBLEM:
case CURLE_SSL_CONNECT_ERROR:
case CURLE_SSL_ISSUER_ERROR:
default:
return false;
}
}
/* return true if the given error code is related to a client certificate */
static bool is_cc_error(PRInt32 err)
{
switch(err) {
case SSL_ERROR_BAD_CERT_ALERT:
case SSL_ERROR_EXPIRED_CERT_ALERT:
case SSL_ERROR_REVOKED_CERT_ALERT:
return true;
default:
return false;
}
}
static Curl_recv nss_recv;
static Curl_send nss_send;
static CURLcode nss_load_ca_certificates(struct connectdata *conn,
int sockindex)
{
struct SessionHandle *data = conn->data;
const char *cafile = data->set.ssl.CAfile;
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;
}
if(capath) {
struct_stat st;
if(stat(capath, &st) == -1)
return CURLE_SSL_CACERT_BADFILE;
if(S_ISDIR(st.st_mode)) {
PRDirEntry *entry;
PRDir *dir = PR_OpenDir(capath);
if(!dir)
return CURLE_SSL_CACERT_BADFILE;
while((entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN))) {
char *fullpath = aprintf("%s/%s", capath, entry->name);
if(!fullpath) {
PR_CloseDir(dir);
return CURLE_OUT_OF_MEMORY;
}
if(CURLE_OK != nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE))
/* This is purposefully tolerant of errors so non-PEM files can
* be in the same directory */
infof(data, "failed to load '%s' from CURLOPT_CAPATH\n", fullpath);
free(fullpath);
}
PR_CloseDir(dir);
}
else
infof(data, "warning: CURLOPT_CAPATH not a directory (%s)\n", capath);
}
infof(data, " CAfile: %s\n CApath: %s\n",
cafile ? cafile : "none",
capath ? capath : "none");
return CURLE_OK;
}
static CURLcode nss_init_sslver(SSLVersionRange *sslver,
struct SessionHandle *data)
{
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;
}
else
sslver->max = SSL_LIBRARY_VERSION_TLS_1_0;
return CURLE_OK;
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
sslver->max = SSL_LIBRARY_VERSION_TLS_1_1;
#else
sslver->max = SSL_LIBRARY_VERSION_TLS_1_0;
return CURLE_OK;
case CURL_SSLVERSION_SSLv2:
sslver->min = SSL_LIBRARY_VERSION_2;
sslver->max = SSL_LIBRARY_VERSION_2;
return CURLE_OK;
case CURL_SSLVERSION_SSLv3:
sslver->min = SSL_LIBRARY_VERSION_3_0;
sslver->max = SSL_LIBRARY_VERSION_3_0;
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_0:
sslver->min = SSL_LIBRARY_VERSION_TLS_1_0;
sslver->max = SSL_LIBRARY_VERSION_TLS_1_0;
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_1:
#ifdef SSL_LIBRARY_VERSION_TLS_1_1
sslver->min = SSL_LIBRARY_VERSION_TLS_1_1;
sslver->max = SSL_LIBRARY_VERSION_TLS_1_1;
return CURLE_OK;
#endif
break;
case CURL_SSLVERSION_TLSv1_2:
#ifdef SSL_LIBRARY_VERSION_TLS_1_2
sslver->min = SSL_LIBRARY_VERSION_TLS_1_2;
sslver->max = SSL_LIBRARY_VERSION_TLS_1_2;
return CURLE_OK;
#endif
break;
}
failf(data, "TLS minor version cannot be set");
return CURLE_SSL_CONNECT_ERROR;
}
Daniel Stenberg
committed
CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
{
PRErrorCode err = 0;
PRFileDesc *model = NULL;
SSLVersionRange sslver;
PRBool ssl_cbc_random_iv;
struct SessionHandle *data = conn->data;
curl_socket_t sockfd = conn->sock[sockindex];
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
Daniel Stenberg
committed
const int *cipher_to_enable;
Kamil Dudka
committed
PRSocketOptionData sock_opt;
Kamil Dudka
committed
PRUint32 timeout;
if(connssl->state == ssl_connection_complete)
Daniel Stenberg
committed
return CURLE_OK;
connssl->data = data;
/* list of all NSS objects we need to destroy in Curl_nss_close() */
connssl->obj_list = Curl_llist_alloc(nss_destroy_object);
if(!connssl->obj_list)
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
/* FIXME. NSS doesn't support multiple databases open at the same time. */
Daniel Stenberg
committed
PR_Lock(nss_initlock);
curlerr = nss_init(conn->data);
if(CURLE_OK != curlerr) {
PR_Unlock(nss_initlock);
goto error;
}
if(!mod) {
char *configstring = aprintf("library=%s name=PEM", pem_library);
if(!configstring) {
PR_Unlock(nss_initlock);
goto error;
}
mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE);
free(configstring);
Daniel Stenberg
committed
if(!mod || !mod->loaded) {
if(mod) {
SECMOD_DestroyModule(mod);
mod = NULL;
}
infof(data, "WARNING: failed to load NSS PEM library %s. Using "
"OpenSSL PEM certificates will not work.\n", pem_library);
}
Daniel Stenberg
committed
PR_Unlock(nss_initlock);
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;
/* do not use SSL cache if disabled or we are not going to verify peer */
ssl_no_cache = (conn->ssl_config.sessionid && data->set.ssl.verifypeer) ?
PR_FALSE : PR_TRUE;
if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess)
goto error;
/* enable/disable the requested SSL version(s) */
if(nss_init_sslver(&sslver, data) != CURLE_OK)
if(SSL_VersionRangeSet(model, &sslver) != SECSuccess)
goto error;
ssl_cbc_random_iv = !data->set.ssl_enable_beast;
#ifdef SSL_CBC_RANDOM_IV
/* unless the user explicitly asks to allow the protocol vulnerability, we
use the work-around */
if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess)
infof(data, "warning: failed to set SSL_CBC_RANDOM_IV = %d\n",
ssl_cbc_random_iv);
#else
if(ssl_cbc_random_iv)
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;
Daniel Stenberg
committed
/* enable all ciphers from enable_ciphers_by_default */
cipher_to_enable = enable_ciphers_by_default;
while(SSL_NULL_WITH_NULL_NULL != *cipher_to_enable) {
if(SSL_CipherPrefSet(model, *cipher_to_enable, PR_TRUE) != SECSuccess) {
Daniel Stenberg
committed
curlerr = CURLE_SSL_CIPHER;
goto error;
}
cipher_to_enable++;
}
if(data->set.ssl.cipher_list) {
if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) {
curlerr = CURLE_SSL_CIPHER;
goto error;
}
}
if(!data->set.ssl.verifypeer && data->set.ssl.verifyhost)
infof(data, "warning: ignoring value of ssl.verifyhost\n");
/* bypass the default SSL_AuthCertificate() hook in case we do not want to
* verify peer */
if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, conn) != SECSuccess)
goto error;
data->set.ssl.certverifyresult=0; /* not checked yet */
if(SSL_BadCertHook(model, BadCertHandler, conn) != SECSuccess)
goto error;
if(SSL_HandshakeCallback(model, HandshakeCallback, NULL) != SECSuccess)
goto error;
if(data->set.ssl.verifypeer) {
const CURLcode rv = nss_load_ca_certificates(conn, sockindex);
if(CURLE_OK != rv) {
curlerr = rv;
goto error;
}
}
if(data->set.ssl.CRLfile) {
if(SECSuccess != nss_load_crl(data->set.ssl.CRLfile)) {
Daniel Stenberg
committed
curlerr = CURLE_SSL_CRL_BADFILE;
goto error;
}
infof(data,
" CRLfile: %s\n",
data->set.ssl.CRLfile ? data->set.ssl.CRLfile : "none");
}
Daniel Stenberg
committed
if(data->set.str[STRING_CERT]) {
char *nickname = dup_nickname(data, STRING_CERT);
if(nickname) {
/* we are not going to use libnsspem.so to read the client cert */
connssl->obj_clicert = NULL;
}
else {
CURLcode rv = cert_stuff(conn, sockindex, data->set.str[STRING_CERT],
data->set.str[STRING_KEY]);
if(CURLE_OK != rv) {
/* failf() is already done in cert_stuff() */
curlerr = rv;
goto error;
}
}
/* store the nickname for SelectClientCert() called during handshake */
connssl->client_nickname = nickname;
}
else
connssl->client_nickname = NULL;
if(SSL_GetClientAuthDataHook(model, SelectClientCert,
(void *)connssl) != SECSuccess) {
curlerr = CURLE_SSL_CERTPROBLEM;
goto error;
}
Daniel Stenberg
committed
/* 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;
PR_Close(model); /* We don't need this any more */
model = NULL;
/* This is the password associated with the cert that we're using */
if(data->set.str[STRING_KEY_PASSWD]) {
SSL_SetPKCS11PinArg(connssl->handle, data->set.str[STRING_KEY_PASSWD]);
}
/* Force handshake on next I/O */
SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE);
SSL_SetURL(connssl->handle, conn->host.name);
time_left = Curl_timeleft(data, NULL, TRUE);
if(time_left < 0L) {
failf(data, "timed out before SSL handshake");
curlerr = CURLE_OPERATION_TIMEDOUT;
goto error;
}
timeout = PR_MillisecondsToInterval((PRUint32) time_left);
Kamil Dudka
committed
if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) {
if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN)
curlerr = CURLE_PEER_FAILED_VERIFICATION;
else if(conn->data->set.ssl.certverifyresult!=0)
curlerr = CURLE_SSL_CACERT;
}
/* switch the SSL socket into non-blocking mode */
sock_opt.option = PR_SockOpt_Nonblocking;
sock_opt.value.non_blocking = PR_TRUE;
if(PR_SetSocketOption(connssl->handle, &sock_opt) != PR_SUCCESS)
goto error;
Daniel Stenberg
committed
connssl->state = ssl_connection_complete;
conn->recv[sockindex] = nss_recv;
conn->send[sockindex] = nss_send;
Daniel Stenberg
committed
display_conn_info(conn, connssl->handle);
if(data->set.str[STRING_SSL_ISSUERCERT]) {
SECStatus ret = SECFailure;
char *nickname = dup_nickname(data, STRING_SSL_ISSUERCERT);
if(nickname) {
/* we support only nicknames in case of STRING_SSL_ISSUERCERT for now */
ret = check_issuer_cert(connssl->handle, nickname);
Daniel Stenberg
committed
if(SECFailure == ret) {
infof(data,"SSL certificate issuer check failed\n");
Daniel Stenberg
committed
curlerr = CURLE_SSL_ISSUER_ERROR;
goto error;
}
else {
infof(data, "SSL certificate issuer check ok\n");
Daniel Stenberg
committed
}
}
return CURLE_OK;
/* 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();
if(is_cc_error(err))
curlerr = CURLE_SSL_CERTPROBLEM;
/* print the error number and error string */
infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err));
/* print a human-readable message describing the error if available */
nss_print_error_message(data, err);
}
if(model)
PR_Close(model);
/* cleanup on connection failure */
Curl_llist_destroy(connssl->obj_list, NULL);
connssl->obj_list = NULL;
if((sslver.min == SSL_LIBRARY_VERSION_3_0)
&& (sslver.max == SSL_LIBRARY_VERSION_TLS_1_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;
}
static ssize_t nss_send(struct connectdata *conn, /* connection data */
int sockindex, /* socketindex */
const void *mem, /* send this data */
size_t len, /* amount to write */
CURLcode *curlcode)
{
ssize_t rc = PR_Send(conn->ssl[sockindex].handle, mem, (int)len, 0,
PR_INTERVAL_NO_WAIT);
if(rc < 0) {
PRInt32 err = PR_GetError();
if(err == PR_WOULD_BLOCK_ERROR)
*curlcode = CURLE_AGAIN;
/* print the error number and error string */
const char *err_name = nss_error_to_name(err);
infof(conn->data, "SSL write: error %d (%s)\n", err, err_name);
/* print a human-readable message describing the error if available */
nss_print_error_message(conn->data, err);
*curlcode = (is_cc_error(err))
? CURLE_SSL_CERTPROBLEM
: CURLE_SEND_ERROR;
return -1;
}
return rc; /* number of bytes */
}
static ssize_t nss_recv(struct connectdata * conn, /* connection data */
int num, /* socketindex */
char *buf, /* store read data here */
size_t buffersize, /* max amount to read */
CURLcode *curlcode)
{
ssize_t nread = PR_Recv(conn->ssl[num].handle, buf, (int)buffersize, 0,
PR_INTERVAL_NO_WAIT);
if(nread < 0) {
/* failed SSL read */
PRInt32 err = PR_GetError();
if(err == PR_WOULD_BLOCK_ERROR)
*curlcode = CURLE_AGAIN;
/* print the error number and error string */
const char *err_name = nss_error_to_name(err);
infof(conn->data, "SSL read: errno %d (%s)\n", err, err_name);
/* print a human-readable message describing the error if available */
nss_print_error_message(conn->data, err);
*curlcode = (is_cc_error(err))
? CURLE_SSL_CERTPROBLEM
: CURLE_RECV_ERROR;
}
return -1;
}
return nread;
}
size_t Curl_nss_version(char *buffer, size_t size)
{
return snprintf(buffer, size, "NSS/%s", NSS_VERSION);
}
int Curl_nss_seed(struct SessionHandle *data)
{
/* make sure that NSS is initialized */
return !!Curl_nss_force_init(data);
void Curl_nss_random(struct SessionHandle *data,
unsigned char *entropy,
size_t length)
{
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();
}
}
void Curl_nss_md5sum(unsigned char *tmp, /* input */
size_t tmplen,
unsigned char *md5sum, /* output */
size_t md5len)
{
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);
}
#endif /* USE_NSS */