From 8c3f40ee320c419800b97f7ed385c43948970f61 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 18 Sep 2007 22:21:54 +0000 Subject: [PATCH] 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. --- CHANGES | 25 +++ RELEASE-NOTES | 3 +- configure.ac | 10 +- docs/curl.1 | 9 +- lib/nss.c | 572 ++++++++++++++++++++++++++++++++++++++++++++++---- lib/urldata.h | 1 + 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=" 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 " @@ -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 " 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 #include #include +#include #include #include #include @@ -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, ¬Before, ¬After); + 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;