aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2007-09-18 22:21:54 +0000
committerDaniel Stenberg <daniel@haxx.se>2007-09-18 22:21:54 +0000
commit8c3f40ee320c419800b97f7ed385c43948970f61 (patch)
treef1e57c3b2d09ed1567af8103aba649b6bb32cf36
parentb1aafbd95730bd826e55fad9e368630d835df573 (diff)
Rob Crittenden provided an NSS update with the following highlights:
o It looks for the NSS database first in the environment variable SSL_DIR, then in /etc/pki/nssdb, then it initializes with no database if neither of those exist. o If the NSS PKCS#11 libnspsem.so driver is available then PEM files may be loaded, including the ca-bundle. If it is not available then only certificates already in the NSS database are used. o Tries to detect whether a file or nickname is being passed in so the right thing is done o Added a bit of code to make the output more like the OpenSSL module, including displaying the certificate information when connecting in verbose mode o Improved handling of certificate errors (expired, untrusted, etc) The libnsspem.so PKCS#11 module is currently only available in Fedora 8/rawhide. Work will be done soon to upstream it. The NSS module will work with or without it, all that changes is the source of the certificates and keys.
-rw-r--r--CHANGES25
-rw-r--r--RELEASE-NOTES3
-rw-r--r--configure.ac10
-rw-r--r--docs/curl.19
-rw-r--r--lib/nss.c572
-rw-r--r--lib/urldata.h1
6 files changed, 579 insertions, 41 deletions
diff --git a/CHANGES b/CHANGES
index f3a9ce7e5..52715abcd 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,31 @@
Changelog
+Daniel S (19 September 2007)
+- Rob Crittenden provided an NSS update with the following highlights:
+
+ o It looks for the NSS database first in the environment variable SSL_DIR,
+ then in /etc/pki/nssdb, then it initializes with no database if neither of
+ those exist.
+
+ o If the NSS PKCS#11 libnspsem.so driver is available then PEM files may be
+ loaded, including the ca-bundle. If it is not available then only
+ certificates already in the NSS database are used.
+
+ o Tries to detect whether a file or nickname is being passed in so the right
+ thing is done
+
+ o Added a bit of code to make the output more like the OpenSSL module,
+ including displaying the certificate information when connecting in
+ verbose mode
+
+ o Improved handling of certificate errors (expired, untrusted, etc)
+
+ The libnsspem.so PKCS#11 module is currently only available in Fedora
+ 8/rawhide. Work will be done soon to upstream it. The NSS module will work
+ with or without it, all that changes is the source of the certificates and
+ keys.
+
Daniel S (18 September 2007)
- Immanuel pointed out that public key SSH auth failed if no public/private
key was specified and there was no HOME environment variable, and then it
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index a10177b90..30f42a6a8 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -12,6 +12,7 @@ Curl and libcurl 7.17.1
This release includes the following changes:
o automatically append ";type=<a|i>" when using HTTP proxies for FTP urls
+ o improved NSS support
This release includes the following bugfixes:
@@ -35,6 +36,6 @@ New curl mirrors:
This release would not have looked like this without help, code, reports and
advice from friends like these:
- Dan Fandrich, Michal Marek, Günter Knauf
+ Dan Fandrich, Michal Marek, Günter Knauf, Rob Crittenden
Thanks! (and sorry if I forgot to mention someone)
diff --git a/configure.ac b/configure.ac
index c793c28b3..d93fc49cc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1470,6 +1470,14 @@ if test "$OPENSSL_ENABLED" != "1" -a "$GNUTLS_ENABLED" != "1"; then
version="unknown"
gtlsprefix=$OPT_GNUTLS
fi
+
+ dnl Check for functionPK11_CreateGenericObject
+ dnl this is needed for using the PEM PKCS#11 module
+ AC_CHECK_LIB(nss3, PK11_CreateGenericObject-d,
+ [
+ AC_DEFINE(HAVE_PK11_CREATEGENERICOBJECT, 1, [if you have the function PK11_CreateGenericObject])
+ AC_SUBST(HAVE_PK11_CREATEGENERICOBJECT, [1])
+ ])
if test -n "$addlib"; then
CLEANLIBS="$LIBS"
@@ -1521,7 +1529,7 @@ dnl **********************************************************************
dnl Check for the CA bundle
dnl **********************************************************************
-if test X"$USE_GNUTLS$OPENSSL_ENABLED" != "X"; then
+if test X"$USE_NSS$USE_GNUTLS$OPENSSL_ENABLED" != "X"; then
AC_MSG_CHECKING([CA cert bundle install path])
diff --git a/docs/curl.1 b/docs/curl.1
index bfa1a7d6f..2b1736998 100644
--- a/docs/curl.1
+++ b/docs/curl.1
@@ -330,7 +330,9 @@ them independently.
If curl is built against the NSS SSL library then this option tells
curl the nickname of the certificate to use within the NSS database defined
-by --cacert.
+by the environment variable SSL_DIR (or by default /etc/pki/nssdb). If the
+NSS PEM PKCS#11 module (libnsspem.so) is available then PEM files may be
+loaded.
If this option is used several times, the last one will be used.
.IP "--cert-type <type>"
@@ -352,7 +354,10 @@ The windows version of curl will automatically look for a CA certs file named
Current Working Directory, or in any folder along your PATH.
If curl is built against the NSS SSL library then this option tells
-curl the directory that the NSS certificate database resides in.
+curl the nickname of the CA certificate to use within the NSS database
+defined by the environment variable SSL_DIR (or by default /etc/pki/nssdb).
+If the NSS PEM PKCS#11 module (libnsspem.so) is available then PEM files
+may be loaded.
If this option is used several times, the last one will be used.
.IP "--capath <CA certificate directory>"
diff --git a/lib/nss.c b/lib/nss.c
index 36facb1b3..663234902 100644
--- a/lib/nss.c
+++ b/lib/nss.c
@@ -55,6 +55,7 @@
#include <ssl.h>
#include <sslerr.h>
#include <secerr.h>
+#include <secmod.h>
#include <sslproto.h>
#include <prtypes.h>
#include <pk11pub.h>
@@ -69,10 +70,14 @@
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
+#define SSL_DIR "/etc/pki/nssdb"
+
+/* enough to fit the string "PEM Token #[0|1]" */
+#define SLOTSIZE 13
+
PRFileDesc *PR_ImportTCPSocket(PRInt32 osfd);
-static int initialized = 0;
-static int noverify = 0;
+int initialized = 0;
#define HANDSHAKE_TIMEOUT 30
@@ -87,13 +92,17 @@ typedef struct {
PRInt32 version; /* protocol version valid for this cipher */
} cipher_s;
-/* the table itself is defined in nss_engine_init.c */
#ifdef NSS_ENABLE_ECC
#define ciphernum 48
#else
#define ciphernum 23
#endif
+#define PK11_SETATTRS(x,id,v,l) (x)->type = (id); \
+ (x)->pValue=(v); (x)->ulValueLen = (l)
+
+#define CERT_NewTempCertificate __CERT_NewTempCertificate
+
enum sslversion { SSL2 = 1, SSL3 = 2, TLS = 4 };
static const cipher_s cipherlist[ciphernum] = {
@@ -154,6 +163,11 @@ static const cipher_s cipherlist[ciphernum] = {
#endif
};
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+static const char* pem_library = "libnsspem.so";
+#endif
+SECMODModule* mod = NULL;
+
static SECStatus set_ciphers(struct SessionHandle *data, PRFileDesc * model,
char *cipher_list)
{
@@ -197,9 +211,7 @@ static SECStatus set_ciphers(struct SessionHandle *data, PRFileDesc * model,
}
if(found == PR_FALSE) {
- char buf[1024];
- snprintf(buf, 1024, "Unknown cipher in list: %s", cipher);
- failf(data, buf);
+ failf(data, "Unknown cipher in list: %s", cipher);
return SECFailure;
}
@@ -220,23 +232,279 @@ static SECStatus set_ciphers(struct SessionHandle *data, PRFileDesc * model,
return SECSuccess;
}
+/*
+ * Determine whether the nickname passed in is a filename that needs to
+ * be loaded as a PEM or a regular NSS nickname.
+ *
+ * returns 1 for a file
+ * returns 0 for not a file (NSS nickname)
+ */
+static int is_file(const char *filename)
+{
+ struct stat st;
+
+ if(filename == NULL)
+ return 0;
+
+ if(stat(filename, &st) == 0)
+ if(S_ISREG(st.st_mode))
+ return 1;
+
+ return 0;
+}
+
+static int
+nss_load_cert(const char *filename, PRBool cacert)
+{
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+ CK_SLOT_ID slotID;
+ PK11SlotInfo * slot = NULL;
+ PK11GenericObject *rv;
+ CK_ATTRIBUTE *attrs;
+ CK_ATTRIBUTE theTemplate[20];
+ CK_BBOOL cktrue = CK_TRUE;
+ CK_BBOOL ckfalse = CK_FALSE;
+ CK_OBJECT_CLASS objClass = CKO_CERTIFICATE;
+ char *slotname = NULL;
+#endif
+ CERTCertificate *cert;
+ char *nickname = NULL;
+ char *n = NULL;
+
+ /* If there is no slash in the filename it is assumed to be a regular
+ * NSS nickname.
+ */
+ if(is_file(filename)) {
+ n = strrchr(filename, '/');
+ if(n)
+ n++;
+ if(!mod)
+ return 1;
+ }
+ else {
+ /* A nickname from the NSS internal database */
+ if (cacert)
+ return 0; /* You can't specify an NSS CA nickname this way */
+ nickname = strdup(filename);
+ goto done;
+ }
+
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+ attrs = theTemplate;
+
+ /* All CA and trust objects go into slot 0. Other slots are used
+ * for storing certificates. With each new user certificate we increment
+ * the slot count. We only support 1 user certificate right now.
+ */
+ if (cacert)
+ slotID = 0;
+ else
+ slotID = 1;
+
+ slotname = (char *)malloc(SLOTSIZE);
+ nickname = (char *)malloc(PATH_MAX);
+ snprintf(slotname, SLOTSIZE, "PEM Token #%ld", slotID);
+ snprintf(nickname, PATH_MAX, "PEM Token #%ld:%s", slotID, n);
+
+ slot = PK11_FindSlotByName(slotname);
+
+ if (!slot) {
+ free(slotname);
+ free(nickname);
+ return 0;
+ }
+
+ PK11_SETATTRS(attrs, CKA_CLASS, &objClass, sizeof(objClass) ); attrs++;
+ PK11_SETATTRS(attrs, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL) ); attrs++;
+ PK11_SETATTRS(attrs, CKA_LABEL, (unsigned char *)filename,
+ strlen(filename)+1); attrs++;
+ if (cacert) {
+ PK11_SETATTRS(attrs, CKA_TRUST, &cktrue, sizeof(CK_BBOOL) ); attrs++;
+ }
+ else {
+ PK11_SETATTRS(attrs, CKA_TRUST, &ckfalse, sizeof(CK_BBOOL) ); attrs++;
+ }
+
+ /* This load the certificate in our PEM module into the appropriate
+ * slot.
+ */
+ rv = PK11_CreateGenericObject(slot, theTemplate, 4, PR_FALSE /* isPerm */);
+
+ PK11_FreeSlot(slot);
+
+ free(slotname);
+ if(rv == NULL) {
+ free(nickname);
+ return 0;
+ }
+#else
+ /* We don't have PK11_CreateGenericObject but a file-based cert was passed
+ * in. We need to fail.
+ */
+ return 0;
+#endif
+
+done:
+ /* Double-check that the certificate or nickname requested exists in
+ * either the token or the NSS certificate database.
+ */
+ if (!cacert) {
+ cert = PK11_FindCertFromNickname((char *)nickname, NULL);
+
+ /* An invalid nickname was passed in */
+ if (cert == NULL) {
+ free(nickname);
+ PR_SetError(SEC_ERROR_UNKNOWN_CERT, 0);
+ return 0;
+ }
+
+ CERT_DestroyCertificate(cert);
+ }
+
+ free(nickname);
+
+ return 1;
+}
+
+static int nss_load_key(struct connectdata *conn, char *key_file)
+{
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+ PK11SlotInfo * slot = NULL;
+ PK11GenericObject *rv;
+ CK_ATTRIBUTE *attrs;
+ CK_ATTRIBUTE theTemplate[20];
+ CK_BBOOL cktrue = CK_TRUE;
+ CK_OBJECT_CLASS objClass = CKO_PRIVATE_KEY;
+ CK_SLOT_ID slotID;
+ char *slotname = NULL;
+ pphrase_arg_t *parg = NULL;
+
+ attrs = theTemplate;
+
+ /* FIXME: grok the various file types */
+
+ slotID = 1; /* hardcoded for now */
+
+ slotname = (char *)malloc(SLOTSIZE);
+ snprintf(slotname, SLOTSIZE, "PEM Token #%ld", slotID);
+
+ slot = PK11_FindSlotByName(slotname);
+ free(slotname);
+
+ if(!slot)
+ return 0;
+
+ PK11_SETATTRS(attrs, CKA_CLASS, &objClass, sizeof(objClass) ); attrs++;
+ PK11_SETATTRS(attrs, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL) ); attrs++;
+ PK11_SETATTRS(attrs, CKA_LABEL, (unsigned char *)key_file,
+ strlen(key_file)+1); attrs++;
+
+ /* When adding an encrypted key the PKCS#11 will be set as removed */
+ rv = PK11_CreateGenericObject(slot, theTemplate, 3, PR_FALSE /* isPerm */);
+ if(rv == NULL) {
+ PR_SetError(SEC_ERROR_BAD_KEY, 0);
+ return 0;
+ }
+
+ /* This will force the token to be seen as re-inserted */
+ SECMOD_WaitForAnyTokenEvent(mod, 0, 0);
+ PK11_IsPresent(slot);
+
+ parg = (pphrase_arg_t *) malloc(sizeof(*parg));
+ parg->retryCount = 0;
+ parg->data = conn->data;
+ /* parg is initialized in nss_Init_Tokens() */
+ if(PK11_Authenticate(slot, PR_TRUE, parg) != SECSuccess) {
+ free(parg);
+ return 0;
+ }
+ free(parg);
+
+ return 1;
+#else
+ /* If we don't have PK11_CreateGenericObject then we can't load a file-based
+ * key.
+ */
+ (void)conn; /* unused */
+ (void)key_file; /* unused */
+ return 0;
+#endif
+}
+
+static int display_error(struct connectdata *conn, PRInt32 err,
+ const char *filename)
+{
+ switch(err) {
+ case SEC_ERROR_BAD_PASSWORD:
+ failf(conn->data, "Unable to load client key: Incorrect password\n");
+ return 1;
+ case SEC_ERROR_UNKNOWN_CERT:
+ failf(conn->data, "Unable to load certificate %s\n", filename);
+ return 1;
+ default:
+ break;
+ }
+ return 0; /* The caller will print a generic error */
+}
+
+static int cert_stuff(struct connectdata *conn, char *cert_file, char *key_file)
+{
+ struct SessionHandle *data = conn->data;
+ int rv = 0;
+
+ if(cert_file) {
+ rv = nss_load_cert(cert_file, PR_FALSE);
+ if(!rv) {
+ if(!display_error(conn, PR_GetError(), cert_file))
+ failf(data, "Unable to load client cert %d.", PR_GetError());
+ return 0;
+ }
+ }
+ if(key_file || (is_file(cert_file))) {
+ if(key_file)
+ rv = nss_load_key(conn, key_file);
+ else
+ /* In case the cert file also has the key */
+ rv = nss_load_key(conn, cert_file);
+ if(!rv) {
+ if(!display_error(conn, PR_GetError(), key_file))
+ failf(data, "Unable to load client key %d.", PR_GetError());
+
+ return 0;
+ }
+ }
+ return 1;
+}
+
static char * nss_get_password(PK11SlotInfo * slot, PRBool retry, void *arg)
{
- pphrase_arg_t *parg = (pphrase_arg_t *) arg;
+ pphrase_arg_t *parg;
+ parg = (pphrase_arg_t *) arg;
+
(void)slot; /* unused */
- (void)retry; /* unused */
+ if(retry > 2)
+ return NULL;
if(parg->data->set.str[STRING_KEY_PASSWD])
return (char *)PORT_Strdup((char *)parg->data->set.str[STRING_KEY_PASSWD]);
else
return NULL;
}
+/* No longer ask for the password, parg has been freed */
+static char * nss_no_password(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ (void)slot; /* unused */
+ (void)retry; /* unused */
+ (void)arg; /* unused */
+ return NULL;
+}
+
static SECStatus nss_Init_Tokens(struct connectdata * conn)
{
PK11SlotList *slotList;
PK11SlotListElement *listEntry;
SECStatus ret, status = SECSuccess;
- pphrase_arg_t *parg;
+ pphrase_arg_t *parg = NULL;
parg = (pphrase_arg_t *) malloc(sizeof(*parg));
parg->retryCount = 0;
@@ -265,6 +533,9 @@ static SECStatus nss_Init_Tokens(struct connectdata * conn)
ret = PK11_Authenticate(slot, PR_TRUE, parg);
if(SECSuccess != ret) {
+ if (PR_GetError() == SEC_ERROR_BAD_PASSWORD)
+ infof(conn->data, "The password for token '%s' is incorrect\n",
+ PK11_GetTokenName(slot));
status = SECFailure;
break;
}
@@ -273,14 +544,61 @@ static SECStatus nss_Init_Tokens(struct connectdata * conn)
}
free(parg);
+
return status;
}
static SECStatus BadCertHandler(void *arg, PRFileDesc * socket)
{
SECStatus success = SECSuccess;
- (void)arg;
- (void)socket;
+ struct connectdata *conn = (struct connectdata *)arg;
+ PRErrorCode err = PR_GetError();
+ CERTCertificate *cert = NULL;
+ char *subject, *issuer;
+
+ if (conn->data->set.ssl.certverifyresult!=0)
+ return success;
+
+ conn->data->set.ssl.certverifyresult=err;
+ cert = SSL_PeerCertificate(socket);
+ subject = CERT_NameToAscii(&cert->subject);
+ issuer = CERT_NameToAscii(&cert->issuer);
+ CERT_DestroyCertificate(cert);
+
+ switch(err) {
+ case SEC_ERROR_CA_CERT_INVALID:
+ infof(conn->data, "Issuer certificate is invalid: '%s'\n", issuer);
+ if (conn->data->set.ssl.verifypeer)
+ success = SECFailure;
+ break;
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ if (conn->data->set.ssl.verifypeer)
+ success = SECFailure;
+ infof(conn->data, "Certificate is signed by an untrusted issuer: '%s'\n",
+ issuer);
+ break;
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ if (conn->data->set.ssl.verifypeer)
+ success = SECFailure;
+ infof(conn->data, "common name: %s (does not match '%s')\n",
+ subject, conn->host.dispname);
+ break;
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ if (conn->data->set.ssl.verifypeer)
+ success = SECFailure;
+ infof(conn->data, "Remote Certificate has expired.\n");
+ break;
+ default:
+ if (conn->data->set.ssl.verifypeer)
+ success = SECFailure;
+ infof(conn->data, "Bad certificate received. Subject = '%s', "
+ "Issuer = '%s'\n", subject, issuer);
+ break;
+ }
+ if (success == SECSuccess)
+ infof(conn->data, "SSL certificate verify ok.\n");
+ PR_Free(subject);
+ PR_Free(issuer);
return success;
}
@@ -295,6 +613,52 @@ static SECStatus HandshakeCallback(PRFileDesc * socket, void *arg)
return SECSuccess;
}
+static void display_conn_info(struct connectdata *conn, PRFileDesc * socket)
+{
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+ CERTCertificate *cert;
+ char *subject, *issuer, *common_name;
+ PRExplodedTime printableTime;
+ char timeString[256];
+ PRTime notBefore, notAfter;
+
+ if (SSL_GetChannelInfo(socket, &channel, sizeof channel) ==
+ SECSuccess && channel.length == sizeof channel &&
+ channel.cipherSuite) {
+ if (SSL_GetCipherSuiteInfo(channel.cipherSuite,
+ &suite, sizeof suite) == SECSuccess) {
+ infof(conn->data, "SSL connection using %s\n", suite.cipherSuiteName);
+ }
+ }
+
+ infof(conn->data, "Server certificate:\n");
+
+ cert = SSL_PeerCertificate(socket);
+ subject = CERT_NameToAscii(&cert->subject);
+ issuer = CERT_NameToAscii(&cert->issuer);
+ common_name = CERT_GetCommonName(&cert->subject);
+ infof(conn->data, "\tsubject: %s\n", subject);
+
+ CERT_GetCertTimes(cert, &notBefore, &notAfter);
+ PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime);
+ PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime);
+ infof(conn->data, "\tstart date: %s\n", timeString);
+ PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime);
+ PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime);
+ infof(conn->data, "\texpire date: %s\n", timeString);
+ infof(conn->data, "\tcommon name: %s\n", common_name);
+ infof(conn->data, "\tissuer: %s\n", issuer);
+
+ PR_Free(subject);
+ PR_Free(issuer);
+ PR_Free(common_name);
+
+ CERT_DestroyCertificate(cert);
+
+ return;
+}
+
/**
*
* Callback to pick the SSL client certificate.
@@ -309,18 +673,33 @@ static SECStatus SelectClientCert(void *arg, PRFileDesc * socket,
char *nickname = (char *)arg;
void *proto_win = NULL;
SECStatus secStatus = SECFailure;
+ PK11SlotInfo *slot;
(void)caNames;
proto_win = SSL_RevealPinArg(socket);
+ if (!nickname)
+ return secStatus;
+
cert = PK11_FindCertFromNickname(nickname, proto_win);
if(cert) {
- privKey = PK11_FindKeyByAnyCert(cert, proto_win);
- if(privKey) {
- secStatus = SECSuccess;
+
+ if(!strncmp(nickname, "PEM Token", 9)) {
+ CK_SLOT_ID slotID = 1; /* hardcoded for now */
+ char * slotname = (char *)malloc(SLOTSIZE);
+ snprintf(slotname, SLOTSIZE, "PEM Token #%ld", slotID);
+ slot = PK11_FindSlotByName(slotname);
+ privKey = PK11_FindPrivateKeyFromCert(slot, cert, NULL);
+ PK11_FreeSlot(slot);
+ free(slotname);
+ if(privKey) {
+ secStatus = SECSuccess;
+ }
}
else {
- CERT_DestroyCertificate(cert);
+ privKey = PK11_FindKeyByAnyCert(cert, proto_win);
+ if(privKey)
+ secStatus = SECSuccess;
}
}
@@ -328,6 +707,10 @@ static SECStatus SelectClientCert(void *arg, PRFileDesc * socket,
*pRetCert = cert;
*pRetKey = privKey;
}
+ else {
+ if (cert)
+ CERT_DestroyCertificate(cert);
+ }
return secStatus;
}
@@ -390,6 +773,10 @@ void Curl_nss_close(struct connectdata *conn, int sockindex)
if(connssl->handle) {
PR_Close(connssl->handle);
+ if(connssl->client_nickname != NULL) {
+ free(connssl->client_nickname);
+ connssl->client_nickname = NULL;
+ }
connssl->handle = NULL;
}
}
@@ -413,31 +800,59 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
curl_socket_t sockfd = conn->sock[sockindex];
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
SECStatus rv;
- int curlerr = CURLE_SSL_CONNECT_ERROR;
+ char *configstring = NULL;
+ char *certDir = NULL;
+ int curlerr;
+
+ curlerr = CURLE_SSL_CONNECT_ERROR;
/* FIXME. NSS doesn't support multiple databases open at the same time. */
if(!initialized) {
- if(!data->set.ssl.CAfile) {
- if(data->set.ssl.verifypeer) {
- failf(data, "No NSS cacert database specified.");
- return CURLE_SSL_CACERT_BADFILE;
- }
- else {
- rv = NSS_NoDB_Init(NULL);
- noverify = 1;
- }
+ initialized = 1;
+
+ certDir = getenv("SSL_DIR"); /* Look in $SSL_DIR */
+
+ if (!certDir) {
+ struct stat st;
+
+ if (stat(SSL_DIR, &st) == 0)
+ if (S_ISDIR(st.st_mode)) {
+ certDir = (char *)SSL_DIR;
+ }
+ }
+
+ if(!certDir) {
+ rv = NSS_NoDB_Init(NULL);
}
else {
- rv = NSS_Initialize(data->set.ssl.CAfile, NULL, NULL, "secmod.db",
+ rv = NSS_Initialize(certDir, NULL, NULL, "secmod.db",
NSS_INIT_READONLY);
}
if(rv != SECSuccess) {
+ infof(conn->data, "Unable to initialize NSS database\n");
curlerr = CURLE_SSL_CACERT_BADFILE;
goto error;
}
- }
- NSS_SetDomesticPolicy();
+ NSS_SetDomesticPolicy();
+
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+ configstring = (char *)malloc(PATH_MAX);
+
+ PR_snprintf(configstring, PATH_MAX, "library=%s name=PEM", pem_library);
+
+ mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE);
+ free(configstring);
+ 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
+ }
model = PR_NewTCPSocket();
if(!model)
@@ -477,34 +892,112 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
goto error;
if(data->set.ssl.cipher_list) {
- if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess)
+ if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) {
+ curlerr = CURLE_SSL_CIPHER;
goto error;
+ }
}
- if(SSL_BadCertHook(model, (SSLBadCertHandler) BadCertHandler, NULL)
- != SECSuccess)
+ 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.CAfile) {
+ rv = nss_load_cert(data->set.ssl.CAfile, PR_TRUE);
+ if (!rv) {
+ curlerr = CURLE_SSL_CACERT_BADFILE;
+ goto error;
+ }
+ }
+ else if (data->set.ssl.CApath) {
+ struct stat st;
+ PRDir *dir;
+ PRDirEntry *entry;
+
+ if (stat(data->set.ssl.CApath, &st) == -1) {
+ curlerr = CURLE_SSL_CACERT_BADFILE;
+ goto error;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ int rv;
+
+ dir = PR_OpenDir(data->set.ssl.CApath);
+ do {
+ entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN);
+
+ if (entry) {
+ char fullpath[PATH_MAX];
+
+ snprintf(fullpath, sizeof(fullpath), "%s/%s", data->set.ssl.CApath,
+ entry->name);
+ rv = nss_load_cert(fullpath, PR_TRUE);
+ }
+ /* This is purposefully tolerant of errors so non-PEM files
+ * can be in the same directory */
+ } 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");
+
if(data->set.str[STRING_CERT]) {
+ char * n;
+ char * nickname;
+
+ nickname = (char *)malloc(PATH_MAX);
+ if(is_file(data->set.str[STRING_CERT])) {
+ n = strrchr(data->set.str[STRING_CERT], '/');
+ if (n) {
+ n++; /* skip last slash */
+ snprintf(nickname, PATH_MAX, "PEM Token #%ld:%s", 1, n);
+ }
+ }
+ else {
+ strncpy(nickname, data->set.str[STRING_CERT], PATH_MAX);
+ }
+ if(nss_Init_Tokens(conn) != SECSuccess) {
+ free(nickname);
+ goto error;
+ }
+ if (!cert_stuff(conn, data->set.str[STRING_CERT],
+ data->set.str[STRING_KEY])) {
+ /* failf() is already done in cert_stuff() */
+ free(nickname);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+ connssl->client_nickname = strdup(nickname);
if(SSL_GetClientAuthDataHook(model,
(SSLGetClientAuthData) SelectClientCert,
- (void *)data->set.str[STRING_CERT]) !=
- SECSuccess) {
+ (void *)connssl->client_nickname) !=
+ SECSuccess) {
curlerr = CURLE_SSL_CERTPROBLEM;
goto error;
}
- if(nss_Init_Tokens(conn) != SECSuccess)
- goto error;
+
+ free(nickname);
+
+ PK11_SetPasswordFunc(nss_no_password);
}
+ else
+ connssl->client_nickname = NULL;
/* 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 */
/* Force handshake on next I/O */
SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE);
@@ -514,14 +1007,19 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
/* Force the handshake now */
if (SSL_ForceHandshakeWithTimeout(connssl->handle,
PR_SecondsToInterval(HANDSHAKE_TIMEOUT))
- != SECSuccess)
+ != SECSuccess) {
+ if (conn->data->set.ssl.certverifyresult!=0)
+ curlerr = CURLE_SSL_CACERT;
goto error;
+ }
+
+ display_conn_info(conn, connssl->handle);
return CURLE_OK;
error:
err = PR_GetError();
- failf(data, "NSS error %d", err);
+ infof(data, "NSS error %d\n", err);
if(model)
PR_Close(model);
return curlerr;
diff --git a/lib/urldata.h b/lib/urldata.h
index 381849a38..2d09be07b 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -183,6 +183,7 @@ struct ssl_connect_data {
#endif /* USE_GNUTLS */
#ifdef USE_NSS
PRFileDesc *handle;
+ char *client_nickname;
#endif /* USE_NSS */
#ifdef USE_QSOSSL
SSLHandle *handle;