aboutsummaryrefslogtreecommitdiff
path: root/lib/nss.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nss.c')
-rw-r--r--lib/nss.c310
1 files changed, 154 insertions, 156 deletions
diff --git a/lib/nss.c b/lib/nss.c
index 3dc0ba61c..f63d9718b 100644
--- a/lib/nss.c
+++ b/lib/nss.c
@@ -27,9 +27,6 @@
#include "setup.h"
-#include <string.h>
-#include <stdlib.h>
-#include <ctype.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
@@ -95,7 +92,7 @@ typedef struct {
ptr->type = (_type); \
ptr->pValue = (_val); \
ptr->ulValueLen = (_len); \
-} while(0)
+} WHILE_FALSE
#define CERT_NewTempCertificate __CERT_NewTempCertificate
@@ -281,17 +278,16 @@ static int is_file(const char *filename)
return 0;
}
-/* Return on heap allocated filename/nickname of a certificate. The returned
- * string should be later deallocated using free(). *is_nickname is set to
- * TRUE if the given string is treated as nickname; FALSE if the given string
- * is treated as file name.
+/* Check if the given string is filename or nickname of a certificate. If the
+ * given string is recognized as filename, return NULL. If the given string is
+ * recognized as nickname, return a duplicated string. The returned string
+ * should be later deallocated using free(). If the OOM failure occurs, we
+ * return NULL, too.
*/
-static char *fmt_nickname(struct SessionHandle *data, enum dupstring cert_kind,
- bool *is_nickname)
+static char* dup_nickname(struct SessionHandle *data, enum dupstring cert_kind)
{
const char *str = data->set.str[cert_kind];
const char *n;
- *is_nickname = TRUE;
if(!is_file(str))
/* no such file exists, use the string as nickname */
@@ -306,10 +302,7 @@ static char *fmt_nickname(struct SessionHandle *data, enum dupstring cert_kind,
}
/* we'll use the PEM reader to read the certificate from file */
- *is_nickname = FALSE;
-
- n++; /* skip last slash */
- return aprintf("PEM Token #%d:%s", 1, n);
+ return NULL;
}
#ifdef HAVE_PK11_CREATEGENERICOBJECT
@@ -326,6 +319,9 @@ static CURLcode nss_create_object(struct ssl_connect_data *ssl,
CK_BBOOL ckfalse = CK_FALSE;
CK_ATTRIBUTE attrs[/* max count of attributes */ 4];
int attr_cnt = 0;
+ CURLcode err = (cacert)
+ ? CURLE_SSL_CACERT_BADFILE
+ : CURLE_SSL_CERTPROBLEM;
const int slot_id = (cacert) ? 0 : 1;
char *slot_name = aprintf("PEM Token #%d", slot_id);
@@ -335,7 +331,7 @@ static CURLcode nss_create_object(struct ssl_connect_data *ssl,
slot = PK11_FindSlotByName(slot_name);
free(slot_name);
if(!slot)
- return CURLE_SSL_CERTPROBLEM;
+ return err;
PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class));
PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL));
@@ -350,13 +346,17 @@ static CURLcode nss_create_object(struct ssl_connect_data *ssl,
obj = PK11_CreateGenericObject(slot, attrs, attr_cnt, PR_FALSE);
PK11_FreeSlot(slot);
if(!obj)
- return CURLE_SSL_CERTPROBLEM;
+ return err;
if(!Curl_llist_insert_next(ssl->obj_list, ssl->obj_list->tail, obj)) {
PK11_DestroyGenericObject(obj);
return CURLE_OUT_OF_MEMORY;
}
+ if(!cacert && CKO_CERTIFICATE == obj_class)
+ /* store reference to a client certificate */
+ ssl->obj_clicert = obj;
+
return CURLE_OK;
}
@@ -371,76 +371,43 @@ static void nss_destroy_object(void *user, void *ptr)
}
#endif
-static int nss_load_cert(struct ssl_connect_data *ssl,
- const char *filename, PRBool cacert)
+static CURLcode nss_load_cert(struct ssl_connect_data *ssl,
+ const char *filename, PRBool cacert)
{
-#ifdef HAVE_PK11_CREATEGENERICOBJECT
- /* All CA and trust objects go into slot 0. Other slots are used
- * for storing certificates.
- */
- const int slot_id = (cacert) ? 0 : 1;
-#endif
- CERTCertificate *cert;
- char *nickname = NULL;
- char *n = NULL;
+ CURLcode err = (cacert)
+ ? CURLE_SSL_CACERT_BADFILE
+ : CURLE_SSL_CERTPROBLEM;
- /* If there is no slash in the filename it is assumed to be a regular
- * NSS nickname.
- */
- if(is_file(filename)) {
- n = strrchr(filename, '/');
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+ /* libnsspem.so leaks memory if the requested file does not exist. For more
+ * details, go to <https://bugzilla.redhat.com/734760>. */
+ if(is_file(filename))
+ err = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert);
+
+ if(CURLE_OK == err && !cacert) {
+ /* we have successfully loaded a client certificate */
+ CERTCertificate *cert;
+ char *nickname = NULL;
+ char *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);
- if(!nickname)
- return 0;
- goto done;
- }
-
-#ifdef HAVE_PK11_CREATEGENERICOBJECT
- nickname = aprintf("PEM Token #%d:%s", slot_id, n);
- if(!nickname)
- return 0;
-
- if(CURLE_OK != nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert)) {
- 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
+ /* The following undocumented magic helps to avoid a SIGSEGV on call
+ * of PK11_ReadRawAttribute() from SelectClientCert() when using an
+ * immature version of libnsspem.so. For more details, go to
+ * <https://bugzilla.redhat.com/733685>. */
+ nickname = aprintf("PEM Token #1:%s", n);
+ if(nickname) {
+ cert = PK11_FindCertFromNickname(nickname, NULL);
+ if(cert)
+ CERT_DestroyCertificate(cert);
- 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);
}
+#endif
- free(nickname);
-
- return 1;
+ return err;
}
/* add given CRL to cache if it is not already there */
@@ -529,23 +496,23 @@ fail:
return SECFailure;
}
-static int nss_load_key(struct connectdata *conn, int sockindex,
- char *key_file)
+static CURLcode nss_load_key(struct connectdata *conn, int sockindex,
+ char *key_file)
{
#ifdef HAVE_PK11_CREATEGENERICOBJECT
PK11SlotInfo *slot;
SECStatus status;
struct ssl_connect_data *ssl = conn->ssl;
- (void)sockindex; /* unused */
- if(CURLE_OK != nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE)) {
+ CURLcode rv = nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE);
+ if(CURLE_OK != rv) {
PR_SetError(SEC_ERROR_BAD_KEY, 0);
- return 0;
+ return rv;
}
slot = PK11_FindSlotByName("PEM Token #1");
if(!slot)
- return 0;
+ return CURLE_SSL_CERTPROBLEM;
/* This will force the token to be seen as re-inserted */
SECMOD_WaitForAnyTokenEvent(mod, 0, 0);
@@ -554,16 +521,18 @@ static int nss_load_key(struct connectdata *conn, int sockindex,
status = PK11_Authenticate(slot, PR_TRUE,
conn->data->set.str[STRING_KEY_PASSWD]);
PK11_FreeSlot(slot);
- return (SECSuccess == status) ? 1 : 0;
+ return (SECSuccess == status)
+ ? CURLE_OK
+ : CURLE_SSL_CERTPROBLEM;
#else
/* If we don't have PK11_CreateGenericObject then we can't load a file-based
* key.
*/
(void)conn; /* unused */
(void)key_file; /* unused */
- (void)sockindex; /* unused */
- return 0;
+ return CURLE_SSL_CERTPROBLEM;
#endif
+ (void)sockindex; /* unused */
}
static int display_error(struct connectdata *conn, PRInt32 err,
@@ -582,34 +551,37 @@ static int display_error(struct connectdata *conn, PRInt32 err,
return 0; /* The caller will print a generic error */
}
-static int cert_stuff(struct connectdata *conn,
- int sockindex, char *cert_file, char *key_file)
+static CURLcode cert_stuff(struct connectdata *conn, int sockindex,
+ char *cert_file, char *key_file)
{
struct SessionHandle *data = conn->data;
- int rv = 0;
+ CURLcode rv;
if(cert_file) {
rv = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE);
- if(!rv) {
+ if(CURLE_OK != rv) {
if(!display_error(conn, PR_GetError(), cert_file))
failf(data, "Unable to load client cert %d.", PR_GetError());
- return 0;
+
+ return rv;
}
}
+
if(key_file || (is_file(cert_file))) {
if(key_file)
rv = nss_load_key(conn, sockindex, key_file);
else
/* In case the cert file also has the key */
rv = nss_load_key(conn, sockindex, cert_file);
- if(!rv) {
+ if(CURLE_OK != rv) {
if(!display_error(conn, PR_GetError(), key_file))
failf(data, "Unable to load client key %d.", PR_GetError());
- return 0;
+ return rv;
}
}
- return 1;
+
+ return CURLE_OK;
}
static char * nss_get_password(PK11SlotInfo * slot, PRBool retry, void *arg)
@@ -776,7 +748,6 @@ static SECStatus check_issuer_cert(PRFileDesc *sock,
cert_issuer = CERT_FindCertIssuer(cert,PR_Now(),certUsageObjectSigner);
proto_win = SSL_RevealPinArg(sock);
- issuer = NULL;
issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win);
if((!cert_issuer) || (!issuer))
@@ -800,44 +771,51 @@ static SECStatus SelectClientCert(void *arg, PRFileDesc *sock,
struct CERTCertificateStr **pRetCert,
struct SECKEYPrivateKeyStr **pRetKey)
{
- static const char pem_nickname[] = "PEM Token #1";
- const char *pem_slotname = pem_nickname;
-
struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg;
struct SessionHandle *data = connssl->data;
const char *nickname = connssl->client_nickname;
- if(mod && nickname &&
- 0 == strncmp(nickname, pem_nickname, /* length of "PEM Token" */ 9)) {
-
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+ if(connssl->obj_clicert) {
/* use the cert/key provided by PEM reader */
- PK11SlotInfo *slot;
+ static const char pem_slotname[] = "PEM Token #1";
+ SECItem cert_der = { 0, NULL, 0 };
void *proto_win = SSL_RevealPinArg(sock);
- *pRetKey = NULL;
- *pRetCert = PK11_FindCertFromNickname(nickname, proto_win);
- if(NULL == *pRetCert) {
- failf(data, "NSS: client certificate not found: %s", nickname);
+ PK11SlotInfo *slot = PK11_FindSlotByName(pem_slotname);
+ if(NULL == slot) {
+ failf(data, "NSS: PK11 slot not found: %s", pem_slotname);
return SECFailure;
}
- slot = PK11_FindSlotByName(pem_slotname);
- if(NULL == slot) {
- failf(data, "NSS: PK11 slot not found: %s", pem_slotname);
+ if(PK11_ReadRawAttribute(PK11_TypeGeneric, connssl->obj_clicert, CKA_VALUE,
+ &cert_der) != SECSuccess) {
+ failf(data, "NSS: CKA_VALUE not found in PK11 generic object");
+ PK11_FreeSlot(slot);
+ return SECFailure;
+ }
+
+ *pRetCert = PK11_FindCertFromDERCertItem(slot, &cert_der, proto_win);
+ SECITEM_FreeItem(&cert_der, PR_FALSE);
+ if(NULL == *pRetCert) {
+ failf(data, "NSS: client certificate from file not found");
+ PK11_FreeSlot(slot);
return SECFailure;
}
*pRetKey = PK11_FindPrivateKeyFromCert(slot, *pRetCert, NULL);
PK11_FreeSlot(slot);
if(NULL == *pRetKey) {
- failf(data, "NSS: private key not found for certificate: %s", nickname);
+ failf(data, "NSS: private key from file not found");
+ CERT_DestroyCertificate(*pRetCert);
return SECFailure;
}
- infof(data, "NSS: client certificate: %s\n", nickname);
+ infof(data, "NSS: client certificate from file\n");
display_cert_info(data, *pRetCert);
return SECSuccess;
}
+#endif
/* use the default NSS hook */
if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames,
@@ -898,10 +876,42 @@ isTLSIntoleranceError(PRInt32 err)
}
}
-static CURLcode init_nss(struct SessionHandle *data)
+static CURLcode nss_init_core(struct SessionHandle *data, const char *cert_dir)
+{
+ if(NSS_IsInitialized())
+ return CURLE_OK;
+
+ if(cert_dir) {
+ SECStatus rv;
+ const bool use_sql = NSS_VersionCheck("3.12.0");
+ char *certpath = aprintf("%s%s", use_sql ? "sql:" : "", cert_dir);
+ if(!certpath)
+ return CURLE_OUT_OF_MEMORY;
+
+ infof(data, "Initializing NSS with certpath: %s\n", certpath);
+ rv = NSS_Initialize(certpath, "", "", "", NSS_INIT_READONLY);
+ free(certpath);
+
+ if(rv == SECSuccess)
+ return CURLE_OK;
+
+ infof(data, "Unable to initialize NSS database\n");
+ }
+
+ infof(data, "Initializing NSS with certpath: none\n");
+ if(NSS_NoDB_Init(NULL) == SECSuccess)
+ return CURLE_OK;
+
+ infof(data, "Unable to initialize NSS\n");
+ return CURLE_SSL_CACERT_BADFILE;
+}
+
+static CURLcode nss_init(struct SessionHandle *data)
{
char *cert_dir;
struct_stat st;
+ CURLcode rv;
+
if(initialized)
return CURLE_OK;
@@ -922,31 +932,14 @@ static CURLcode init_nss(struct SessionHandle *data)
}
}
- if(!NSS_IsInitialized()) {
- SECStatus rv;
- initialized = 1;
- infof(data, "Initializing NSS with certpath: %s\n",
- cert_dir ? cert_dir : "none");
- if(!cert_dir) {
- rv = NSS_NoDB_Init(NULL);
- }
- else {
- char *certpath =
- PR_smprintf("%s%s", NSS_VersionCheck("3.12.0") ? "sql:" : "",
- cert_dir);
- rv = NSS_Initialize(certpath, "", "", "", NSS_INIT_READONLY);
- PR_smprintf_free(certpath);
- }
- if(rv != SECSuccess) {
- infof(data, "Unable to initialize NSS database\n");
- initialized = 0;
- return CURLE_SSL_CACERT_BADFILE;
- }
- }
+ rv = nss_init_core(data, cert_dir);
+ if(rv)
+ return rv;
if(num_enabled_ciphers() == 0)
NSS_SetDomesticPolicy();
+ initialized = 1;
return CURLE_OK;
}
@@ -981,7 +974,7 @@ CURLcode Curl_nss_force_init(struct SessionHandle *data)
}
PR_Lock(nss_initlock);
- rv = init_nss(data);
+ rv = nss_init(data);
PR_Unlock(nss_initlock);
return rv;
}
@@ -1064,6 +1057,7 @@ void Curl_nss_close(struct connectdata *conn, int sockindex)
/* destroy all NSS objects in order to avoid failure of NSS shutdown */
Curl_llist_destroy(connssl->obj_list, NULL);
connssl->obj_list = NULL;
+ connssl->obj_clicert = NULL;
#endif
PR_Close(connssl->handle);
connssl->handle = NULL;
@@ -1111,8 +1105,11 @@ static CURLcode nss_load_ca_certificates(struct connectdata *conn,
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(cafile) {
+ CURLcode rv = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE);
+ if(CURLE_OK != rv)
+ return rv;
+ }
if(capath) {
struct_stat st;
@@ -1132,7 +1129,7 @@ static CURLcode nss_load_ca_certificates(struct connectdata *conn,
return CURLE_OUT_OF_MEMORY;
}
- if(!nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE))
+ 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);
@@ -1184,7 +1181,7 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
/* FIXME. NSS doesn't support multiple databases open at the same time. */
PR_Lock(nss_initlock);
- curlerr = init_nss(conn->data);
+ curlerr = nss_init(conn->data);
if(CURLE_OK != curlerr) {
PR_Unlock(nss_initlock);
goto error;
@@ -1327,16 +1324,21 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
}
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;
+ char *nickname = dup_nickname(data, STRING_CERT);
+ if(nickname) {
+ /* we are not going to use libnsspem.so to read the client cert */
+#ifdef HAVE_PK11_CREATEGENERICOBJECT
+ connssl->obj_clicert = NULL;
+#endif
+ }
+ 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 */
@@ -1395,16 +1397,12 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
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;
-
- if(is_nickname)
+ 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);
-
- free(nickname);
+ free(nickname);
+ }
if(SECFailure == ret) {
infof(data,"SSL certificate issuer check failed\n");