Newer
Older
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;
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];
SECStatus rv;
char *certDir = NULL;
int curlerr;
Daniel Stenberg
committed
const int *cipher_to_enable;
Kamil Dudka
committed
PRSocketOptionData sock_opt;
Kamil Dudka
committed
PRUint32 timeout;
curlerr = CURLE_SSL_CONNECT_ERROR;
Daniel Stenberg
committed
if (connssl->state == ssl_connection_complete)
return CURLE_OK;
connssl->data = data;
#ifdef HAVE_PK11_CREATEGENERICOBJECT
Daniel Stenberg
committed
connssl->cacert[0] = NULL;
connssl->cacert[1] = NULL;
connssl->key = NULL;
#endif
Daniel Stenberg
committed
/* FIXME. NSS doesn't support multiple databases open at the same time. */
Daniel Stenberg
committed
PR_Lock(nss_initlock);
if(!initialized) {
Guenter Knauf
committed
struct_stat st;
/* First we check if $SSL_DIR points to a valid dir */
certDir = getenv("SSL_DIR");
if(certDir) {
if((stat(certDir, &st) != 0) ||
(!S_ISDIR(st.st_mode))) {
Guenter Knauf
committed
certDir = NULL;
}
}
Guenter Knauf
committed
/* Now we check if the default location is a valid dir */
Daniel Stenberg
committed
if(!certDir) {
Guenter Knauf
committed
if((stat(SSL_DIR, &st) == 0) &&
(S_ISDIR(st.st_mode))) {
Guenter Knauf
committed
certDir = (char *)SSL_DIR;
}
}
if (!NSS_IsInitialized()) {
initialized = 1;
infof(conn->data, "Initializing NSS with certpath: %s\n",
certDir ? certDir : "none");
if(!certDir) {
rv = NSS_NoDB_Init(NULL);
}
else {
Guenter Knauf
committed
char *certpath = PR_smprintf("%s%s",
NSS_VersionCheck("3.12.0") ? "sql:" : "",
certDir);
Guenter Knauf
committed
rv = NSS_Initialize(certpath, "", "", "", NSS_INIT_READONLY);
PR_smprintf_free(certpath);
}
if(rv != SECSuccess) {
infof(conn->data, "Unable to initialize NSS database\n");
curlerr = CURLE_SSL_CACERT_BADFILE;
initialized = 0;
PR_Unlock(nss_initlock);
goto error;
}
}
if(num_enabled_ciphers() == 0)
NSS_SetDomesticPolicy();
#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
PK11_SetPasswordFunc(nss_get_password);
}
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) != SECSuccess)
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;
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.verifyhost == 1)
infof(data, "warning: ignoring unsupported value (1) of ssl.verifyhost\n");
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)
/* skip the verifying of the peer */
;
Daniel Stenberg
committed
else if(data->set.ssl.CAfile) {
Daniel Stenberg
committed
int rc = nss_load_cert(&conn->ssl[sockindex], data->set.ssl.CAfile,
PR_TRUE);
Daniel Stenberg
committed
if(!rc) {
curlerr = CURLE_SSL_CACERT_BADFILE;
goto error;
}
}
Daniel Stenberg
committed
else if(data->set.ssl.CApath) {
struct_stat st;
PRDir *dir;
PRDirEntry *entry;
Daniel Stenberg
committed
if(stat(data->set.ssl.CApath, &st) == -1) {
curlerr = CURLE_SSL_CACERT_BADFILE;
goto error;
}
Daniel Stenberg
committed
if(S_ISDIR(st.st_mode)) {
int rc;
dir = PR_OpenDir(data->set.ssl.CApath);
do {
entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN);
Daniel Stenberg
committed
if(entry) {
char fullpath[PATH_MAX];
snprintf(fullpath, sizeof(fullpath), "%s/%s", data->set.ssl.CApath,
entry->name);
Daniel Stenberg
committed
rc = nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE);
/* FIXME: check this return value! */
}
/* This is purposefully tolerant of errors so non-PEM files
* can be in the same directory */
Daniel Stenberg
committed
} while(entry != NULL);
PR_CloseDir(dir);
}
}
infof(data,
" CAfile: %s\n"
" CApath: %s\n",
data->set.ssl.CAfile ? data->set.ssl.CAfile : "none",
data->set.ssl.CApath ? data->set.ssl.CApath : "none");
Daniel Stenberg
committed
if (data->set.ssl.CRLfile) {
int rc = nss_load_crl(data->set.ssl.CRLfile, PR_FALSE);
if (!rc) {
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]) {
Daniel Stenberg
committed
bool nickname_alloc = FALSE;
char *nickname = fmt_nickname(data->set.str[STRING_CERT], &nickname_alloc);
if(!nickname)
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
if(!cert_stuff(conn, sockindex, data->set.str[STRING_CERT],
data->set.str[STRING_KEY])) {
/* failf() is already done in cert_stuff() */
Daniel Stenberg
committed
if(nickname_alloc)
free(nickname);
return CURLE_SSL_CERTPROBLEM;
}
Daniel Stenberg
committed
/* this "takes over" the pointer to the allocated name or makes a
dup of it */
connssl->client_nickname = nickname_alloc?nickname:strdup(nickname);
if(!connssl->client_nickname)
return CURLE_OUT_OF_MEMORY;
}
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);
/* check timeout situation */
time_left = Curl_timeleft(conn, 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 = nss_recv;
conn->send = nss_send;
Daniel Stenberg
committed
display_conn_info(conn, connssl->handle);
Daniel Stenberg
committed
if (data->set.str[STRING_SSL_ISSUERCERT]) {
Daniel Stenberg
committed
SECStatus ret;
bool nickname_alloc = FALSE;
char *nickname = fmt_nickname(data->set.str[STRING_SSL_ISSUERCERT],
&nickname_alloc);
Daniel Stenberg
committed
if(!nickname)
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
ret = check_issuer_cert(connssl->handle, nickname);
if(nickname_alloc)
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);
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);
}
#endif /* USE_NSS */