Newer
Older
mod = NULL;
}
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;
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
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) {
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);
}
#ifdef HAVE_PK11_CREATEGENERICOBJECT
/* destroy all NSS objects in order to avoid failure of NSS shutdown */
Curl_llist_destroy(connssl->obj_list, NULL);
connssl->obj_list = NULL;
#endif
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;
}
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
/* 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;
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
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 && !nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE))
return CURLE_SSL_CACERT_BADFILE;
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(!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;
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;
Daniel Stenberg
committed
if (connssl->state == ssl_connection_complete)
return CURLE_OK;
connssl->data = data;
#ifdef HAVE_PK11_CREATEGENERICOBJECT
/* 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;
#endif
Daniel Stenberg
committed
/* FIXME. NSS doesn't support multiple databases open at the same time. */
Daniel Stenberg
committed
PR_Lock(nss_initlock);
curlerr = init_nss(conn->data);
if(CURLE_OK != curlerr) {
PR_Unlock(nss_initlock);
goto error;
}
#ifdef HAVE_PK11_CREATEGENERICOBJECT
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);
}
#endif
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;
/* 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) {
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;
}
}
Daniel Stenberg
committed
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]) {
bool is_nickname;
char *nickname = fmt_nickname(data, STRING_CERT, &is_nickname);
if(!nickname)
return CURLE_OUT_OF_MEMORY;
if(!is_nickname && !cert_stuff(conn, sockindex, data->set.str[STRING_CERT],
data->set.str[STRING_KEY])) {
/* failf() is already done in cert_stuff() */
free(nickname);
return CURLE_SSL_CERTPROBLEM;
}
/* 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);
Daniel Stenberg
committed
if (data->set.str[STRING_SSL_ISSUERCERT]) {
SECStatus ret = SECFailure;
bool is_nickname;
char *nickname = fmt_nickname(data, STRING_SSL_ISSUERCERT, &is_nickname);
if(!nickname)
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
if(is_nickname)
/* we support only nicknames in case of STRING_SSL_ISSUERCERT for now */
ret = check_issuer_cert(connssl->handle, nickname);
Daniel Stenberg
committed
free(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);
#ifdef HAVE_PK11_CREATEGENERICOBJECT
/* cleanup on connection failure */
Curl_llist_destroy(connssl->obj_list, NULL);
connssl->obj_list = NULL;
#endif
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 */