Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
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) {
free(connssl->client_nickname);
connssl->client_nickname = NULL;
/* force NSS to ask again for a client cert when connecting
* next time to the same server */
SSL_InvalidateSession(connssl->handle);
}
/* 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;
}
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
/* handle client certificate related errors if any; return false otherwise */
static bool handle_cc_error(PRInt32 err, struct SessionHandle *data)
{
switch(err) {
case SSL_ERROR_BAD_CERT_ALERT:
failf(data, "SSL error: SSL_ERROR_BAD_CERT_ALERT");
return true;
case SSL_ERROR_REVOKED_CERT_ALERT:
failf(data, "SSL error: SSL_ERROR_REVOKED_CERT_ALERT");
return true;
case SSL_ERROR_EXPIRED_CERT_ALERT:
failf(data, "SSL error: SSL_ERROR_EXPIRED_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;
}
Daniel Stenberg
committed
CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
{
PRInt32 err;
PRFileDesc *model = NULL;
PRBool ssl2 = PR_FALSE;
PRBool ssl3 = PR_FALSE;
PRBool tlsv1 = PR_FALSE;
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);
Kamil Dudka
committed
/* make the socket nonblocking */
sock_opt.option = PR_SockOpt_Nonblocking;
sock_opt.value.non_blocking = PR_TRUE;
if(PR_SetSocketOption(model, &sock_opt) != PR_SUCCESS)
Kamil Dudka
committed
goto error;
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 we are not going to verify peer */
ssl_no_cache = (data->set.ssl.verifypeer) ? PR_FALSE : PR_TRUE;
if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess)
goto error;
switch (data->set.ssl.version) {
default:
case CURL_SSLVERSION_DEFAULT:
ssl3 = PR_TRUE;
if(data->state.ssl_connect_retry)
infof(data, "TLS disabled due to previous handshake failure\n");
else
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(SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, ssl2) != 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");
else if(data->set.ssl.verifyhost == 1)
infof(data, "warning: ignoring unsupported value (1) 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, (SSLBadCertHandler) BadCertHandler, conn)
!= SECSuccess) {
goto error;
}
if(SSL_HandshakeCallback(model, (SSLHandshakeCallback) 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");
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;
}
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;
err = PR_GetError();
if(handle_cc_error(err, data))
curlerr = CURLE_SSL_CERTPROBLEM;
else
infof(data, "NSS error %d\n", err);
if(model)
PR_Close(model);
/* cleanup on connection failure */
Curl_llist_destroy(connssl->obj_list, NULL);
connssl->obj_list = NULL;
if(ssl3 && tlsv1 && 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)
{
int rc;
Kamil Dudka
committed
rc = PR_Send(conn->ssl[sockindex].handle, mem, (int)len, 0, -1);
if(rc < 0) {
PRInt32 err = PR_GetError();
if(err == PR_WOULD_BLOCK_ERROR)
*curlcode = CURLE_AGAIN;
else if(handle_cc_error(err, conn->data))
*curlcode = CURLE_SSL_CERTPROBLEM;
else {
failf(conn->data, "SSL write: error %d", err);
*curlcode = 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;
Kamil Dudka
committed
nread = PR_Recv(conn->ssl[num].handle, buf, (int)buffersize, 0, -1);
if(nread < 0) {
/* failed SSL read */
PRInt32 err = PR_GetError();
if(err == PR_WOULD_BLOCK_ERROR)
*curlcode = CURLE_AGAIN;
else if(handle_cc_error(err, conn->data))
*curlcode = CURLE_SSL_CERTPROBLEM;
else {
failf(conn->data, "SSL read: errno %d", err);
*curlcode = 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)
{
/* TODO: implement? */
(void) data;
return 0;
}
#endif /* USE_NSS */