Newer
Older
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/*
* Source file for all NSS-specific code for the TLS/SSL layer. No code
* but sslgen.c should ever call or use these functions.
*/
#include "setup.h"
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include "urldata.h"
#include "sendf.h"
#include "formdata.h" /* for the boundary function */
#include "url.h" /* for the ssl config check function */
#include "connect.h"
#include "strequal.h"
#include "select.h"
#include "sslgen.h"
#define _MPRINTF_REPLACE /* use the internal *printf() functions */
#include <curl/mprintf.h>
#ifdef USE_NSS
#include "nssg.h"
#include <nspr.h>
#include <nss.h>
#include <ssl.h>
#include <sslerr.h>
#include <secerr.h>
#include <secmod.h>
#include <sslproto.h>
#include <prtypes.h>
#include <pk11pub.h>
Daniel Stenberg
committed
#include <prio.h>
#include <secitem.h>
#include <secport.h>
Daniel Stenberg
committed
#include <certdb.h>
Guenter Knauf
committed
#include <base64.h>
Daniel Stenberg
committed
#include "rawstr.h"
/* The last #include file should be: */
#include "memdebug.h"
#define SSL_DIR "/etc/pki/nssdb"
/* enough to fit the string "PEM Token #[0|1]" */
#define SLOTSIZE 13
PRFileDesc *PR_ImportTCPSocket(PRInt32 osfd);
Daniel Stenberg
committed
PRLock * nss_initlock = NULL;
PRLock * nss_crllock = NULL;
Daniel Stenberg
committed
volatile int initialized = 0;
typedef struct {
const char *name;
int num;
PRInt32 version; /* protocol version valid for this cipher */
} cipher_s;
#define PK11_SETATTRS(_attr, _idx, _type, _val, _len) do { \
CK_ATTRIBUTE *ptr = (_attr) + ((_idx)++); \
ptr->type = (_type); \
ptr->pValue = (_val); \
ptr->ulValueLen = (_len); \
#define CERT_NewTempCertificate __CERT_NewTempCertificate
enum sslversion { SSL2 = 1, SSL3 = 2, TLS = 4 };
#define NUM_OF_CIPHERS sizeof(cipherlist)/sizeof(cipherlist[0])
static const cipher_s cipherlist[] = {
/* SSL2 cipher suites */
{"rc4", SSL_EN_RC4_128_WITH_MD5, SSL2},
{"rc4-md5", SSL_EN_RC4_128_WITH_MD5, SSL2},
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
{"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5, SSL2},
{"rc2", SSL_EN_RC2_128_CBC_WITH_MD5, SSL2},
{"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, SSL2},
{"des", SSL_EN_DES_64_CBC_WITH_MD5, SSL2},
{"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5, SSL2},
/* SSL3/TLS cipher suites */
{"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5, SSL3 | TLS},
{"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA, SSL3 | TLS},
{"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL3 | TLS},
{"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA, SSL3 | TLS},
{"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL3 | TLS},
{"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, SSL3 | TLS},
{"rsa_null_md5", SSL_RSA_WITH_NULL_MD5, SSL3 | TLS},
{"rsa_null_sha", SSL_RSA_WITH_NULL_SHA, SSL3 | TLS},
{"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, SSL3 | TLS},
{"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA, SSL3 | TLS},
{"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, SSL3 | TLS},
{"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA, SSL3 | TLS},
{"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA, SSL3 | TLS},
/* TLS 1.0: Exportable 56-bit Cipher Suites. */
{"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, SSL3 | TLS},
{"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, SSL3 | TLS},
/* AES ciphers. */
{"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA, SSL3 | TLS},
{"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA, SSL3 | TLS},
#ifdef NSS_ENABLE_ECC
/* ECC ciphers. */
{"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA, TLS},
{"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA, TLS},
{"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS},
{"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS},
{"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS},
{"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA, TLS},
{"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS},
{"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS},
{"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS},
{"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS},
{"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA, TLS},
{"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA, TLS},
{"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, TLS},
{"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS},
{"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS},
{"echde_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA, TLS},
{"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS},
{"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS},
{"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS},
{"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS},
{"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA, TLS},
{"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA, TLS},
{"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, TLS},
{"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA, TLS},
{"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA, TLS},
#endif
};
/* following ciphers are new in NSS 3.4 and not enabled by default, therefore
Daniel Stenberg
committed
they are enabled explicitly */
static const int enable_ciphers_by_default[] = {
TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_256_CBC_SHA,
SSL_NULL_WITH_NULL_NULL
};
#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)
{
unsigned int i;
PRBool cipher_state[NUM_OF_CIPHERS];
PRBool found;
char *cipher;
SECStatus rv;
/* First disable all ciphers. This uses a different max value in case
* NSS adds more ciphers later we don't want them available by
* accident
*/
for(i=0; i<SSL_NumImplementedCiphers; i++) {
SSL_CipherPrefSet(model, SSL_ImplementedCiphers[i], SSL_NOT_ALLOWED);
}
/* Set every entry in our list to false */
for(i=0; i<NUM_OF_CIPHERS; i++) {
cipher_state[i] = PR_FALSE;
}
cipher = cipher_list;
while(cipher_list && (cipher_list[0])) {
++cipher;
if((cipher_list = strchr(cipher, ','))) {
*cipher_list++ = '\0';
}
found = PR_FALSE;
for(i=0; i<NUM_OF_CIPHERS; i++) {
Daniel Stenberg
committed
if(Curl_raw_equal(cipher, cipherlist[i].name)) {
cipher_state[i] = PR_TRUE;
found = PR_TRUE;
break;
}
}
if(found == PR_FALSE) {
failf(data, "Unknown cipher in list: %s", cipher);
return SECFailure;
}
if(cipher_list) {
cipher = cipher_list;
}
}
/* Finally actually enable the selected ciphers */
for(i=0; i<NUM_OF_CIPHERS; i++) {
rv = SSL_CipherPrefSet(model, cipherlist[i].num, cipher_state[i]);
if(rv != SECSuccess) {
failf(data, "Unknown cipher in cipher list");
return SECFailure;
}
}
return SECSuccess;
}
/*
* Get the number of ciphers that are enabled. We use this to determine
* if we need to call NSS_SetDomesticPolicy() to enable the default ciphers.
*/
{
PRInt32 policy = 0;
int count = 0;
for(i=0; i<NUM_OF_CIPHERS; i++) {
SSL_CipherPolicyGet(cipherlist[i].num, &policy);
if(policy)
count++;
}
return count;
}
/*
* 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;
}
/* 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.
*/
static char *fmt_nickname(struct SessionHandle *data, enum dupstring cert_kind,
bool *is_nickname)
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 */
return strdup(str);
/* search the last slash; we require at least one slash in a file name */
n = strrchr(str, '/');
if(!n) {
infof(data, "warning: certificate file name \"%s\" handled as nickname; "
"please use \"./%s\" to force file name\n", str, str);
return strdup(str);
/* 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);
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
#ifdef HAVE_PK11_CREATEGENERICOBJECT
/* Call PK11_CreateGenericObject() with the given obj_class and filename. If
* the call succeeds, append the object handle to the list of objects so that
* the object can be destroyed in Curl_nss_close(). */
static CURLcode nss_create_object(struct ssl_connect_data *ssl,
CK_OBJECT_CLASS obj_class,
const char *filename, bool cacert)
{
PK11SlotInfo *slot;
PK11GenericObject *obj;
CK_BBOOL cktrue = CK_TRUE;
CK_BBOOL ckfalse = CK_FALSE;
CK_ATTRIBUTE attrs[/* max count of attributes */ 4];
int attr_cnt = 0;
const int slot_id = (cacert) ? 0 : 1;
char *slot_name = aprintf("PEM Token #%d", slot_id);
if(!slot_name)
return CURLE_OUT_OF_MEMORY;
slot = PK11_FindSlotByName(slot_name);
free(slot_name);
if(!slot)
return CURLE_SSL_CERTPROBLEM;
PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class));
PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL));
PK11_SETATTRS(attrs, attr_cnt, CKA_LABEL, (unsigned char *)filename,
strlen(filename) + 1);
if(CKO_CERTIFICATE == obj_class) {
CK_BBOOL *pval = (cacert) ? (&cktrue) : (&ckfalse);
PK11_SETATTRS(attrs, attr_cnt, CKA_TRUST, pval, sizeof(*pval));
}
obj = PK11_CreateGenericObject(slot, attrs, attr_cnt, PR_FALSE);
PK11_FreeSlot(slot);
if(!obj)
return CURLE_SSL_CERTPROBLEM;
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;
}
/* Destroy the NSS object whose handle is given by ptr. This function is
* a callback of Curl_llist_alloc() used by Curl_llist_destroy() to destroy
* NSS objects in Curl_nss_close() */
static void nss_destroy_object(void *user, void *ptr)
{
PK11GenericObject *obj = (PK11GenericObject *)ptr;
(void) user;
PK11_DestroyGenericObject(obj);
}
#endif
Daniel Stenberg
committed
static int 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;
/* 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 */
Daniel Stenberg
committed
if(cacert)
return 0; /* You can't specify an NSS CA nickname this way */
nickname = strdup(filename);
Daniel Stenberg
committed
if(!nickname)
return 0;
goto done;
}
#ifdef HAVE_PK11_CREATEGENERICOBJECT
nickname = aprintf("PEM Token #%d:%s", slot_id, n);
Daniel Stenberg
committed
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
/* Double-check that the certificate or nickname requested exists in
* either the token or the NSS certificate database.
*/
Daniel Stenberg
committed
if(!cacert) {
cert = PK11_FindCertFromNickname((char *)nickname, NULL);
/* An invalid nickname was passed in */
Daniel Stenberg
committed
if(cert == NULL) {
free(nickname);
PR_SetError(SEC_ERROR_UNKNOWN_CERT, 0);
return 0;
}
CERT_DestroyCertificate(cert);
}
free(nickname);
return 1;
}
/* add given CRL to cache if it is not already there */
static SECStatus nss_cache_crl(SECItem *crlDER)
{
CERTCertDBHandle *db = CERT_GetDefaultCertDB();
CERTSignedCrl *crl = SEC_FindCrlByDERCert(db, crlDER, 0);
if(crl) {
/* CRL already cached */
SEC_DestroyCrl(crl);
SECITEM_FreeItem(crlDER, PR_FALSE);
return SECSuccess;
}
/* acquire lock before call of CERT_CacheCRL() */
PR_Lock(nss_crllock);
if(SECSuccess != CERT_CacheCRL(db, crlDER)) {
/* unable to cache CRL */
PR_Unlock(nss_crllock);
SECITEM_FreeItem(crlDER, PR_FALSE);
return SECFailure;
}
/* we need to clear session cache, so that the CRL could take effect */
SSL_ClearSessionCache();
PR_Unlock(nss_crllock);
return SECSuccess;
}
static SECStatus nss_load_crl(const char* crlfilename)
Daniel Stenberg
committed
{
PRFileDesc *infile;
PRFileInfo info;
SECItem filedata = { 0, NULL, 0 };
SECItem crlDER = { 0, NULL, 0 };
char *body;
Daniel Stenberg
committed
infile = PR_Open(crlfilename, PR_RDONLY, 0);
if(!infile)
return SECFailure;
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
if(PR_SUCCESS != PR_GetOpenFileInfo(infile, &info))
goto fail;
if(!SECITEM_AllocItem(NULL, &filedata, info.size + /* zero ended */ 1))
goto fail;
if(info.size != PR_Read(infile, filedata.data, info.size))
goto fail;
/* place a trailing zero right after the visible data */
body = (char*)filedata.data;
body[--filedata.len] = '\0';
body = strstr(body, "-----BEGIN");
if(body) {
/* assume ASCII */
char *trailer;
char *begin = PORT_Strchr(body, '\n');
if(!begin)
begin = PORT_Strchr(body, '\r');
if(!begin)
goto fail;
trailer = strstr(++begin, "-----END");
if(!trailer)
goto fail;
/* retrieve DER from ASCII */
*trailer = '\0';
if(ATOB_ConvertAsciiToItem(&crlDER, begin))
goto fail;
SECITEM_FreeItem(&filedata, PR_FALSE);
Daniel Stenberg
committed
}
else
/* assume DER */
crlDER = filedata;
PR_Close(infile);
return nss_cache_crl(&crlDER);
Daniel Stenberg
committed
fail:
PR_Close(infile);
SECITEM_FreeItem(&filedata, PR_FALSE);
return SECFailure;
Daniel Stenberg
committed
}
static int 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)) {
PR_SetError(SEC_ERROR_BAD_KEY, 0);
return 0;
}
slot = PK11_FindSlotByName("PEM Token #1");
if(!slot)
return 0;
/* This will force the token to be seen as re-inserted */
SECMOD_WaitForAnyTokenEvent(mod, 0, 0);
PK11_IsPresent(slot);
status = PK11_Authenticate(slot, PR_TRUE,
conn->data->set.str[STRING_KEY_PASSWD]);
PK11_FreeSlot(slot);
return (SECSuccess == status) ? 1 : 0;
#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;
#endif
}
static int display_error(struct connectdata *conn, PRInt32 err,
const char *filename)
{
switch(err) {
case SEC_ERROR_BAD_PASSWORD:
Daniel Stenberg
committed
failf(conn->data, "Unable to load client key: Incorrect password");
return 1;
case SEC_ERROR_UNKNOWN_CERT:
Daniel Stenberg
committed
failf(conn->data, "Unable to load certificate %s", filename);
return 1;
default:
break;
}
return 0; /* The caller will print a generic error */
}
Daniel Stenberg
committed
static int cert_stuff(struct connectdata *conn,
int sockindex, char *cert_file, char *key_file)
{
struct SessionHandle *data = conn->data;
int rv = 0;
if(cert_file) {
Daniel Stenberg
committed
rv = nss_load_cert(&conn->ssl[sockindex], 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)
Daniel Stenberg
committed
rv = nss_load_key(conn, sockindex, key_file);
else
/* In case the cert file also has the key */
Daniel Stenberg
committed
rv = nss_load_key(conn, sockindex, 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)
{
(void)slot; /* unused */
Daniel Stenberg
committed
if(retry || NULL == arg)
return NULL;
else
Daniel Stenberg
committed
return (char *)PORT_Strdup((char *)arg);
}
/* bypass the default SSL_AuthCertificate() hook in case we do not want to
* verify peer */
static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig,
PRBool isServer)
{
struct connectdata *conn = (struct connectdata *)arg;
if(!conn->data->set.ssl.verifypeer) {
infof(conn->data, "skipping SSL peer certificate verification\n");
return SECSuccess;
}
return SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer);
static SECStatus BadCertHandler(void *arg, PRFileDesc *sock)
{
SECStatus result = SECFailure;
struct connectdata *conn = (struct connectdata *)arg;
PRErrorCode err = PR_GetError();
CERTCertificate *cert = NULL;
char *subject, *subject_cn, *issuer;
conn->data->set.ssl.certverifyresult=err;
cert = SSL_PeerCertificate(sock);
subject = CERT_NameToAscii(&cert->subject);
subject_cn = CERT_GetCommonName(&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);
break;
case SEC_ERROR_UNTRUSTED_ISSUER:
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.verifyhost) {
failf(conn->data, "SSL: certificate subject name '%s' does not match "
"target host name '%s'", subject_cn, conn->host.dispname);
infof(conn->data, "warning: SSL: certificate subject name '%s' does not "
"match target host name '%s'\n", subject_cn, conn->host.dispname);
}
break;
case SEC_ERROR_EXPIRED_CERTIFICATE:
infof(conn->data, "Remote Certificate has expired.\n");
break;
case SEC_ERROR_UNKNOWN_ISSUER:
infof(conn->data, "Peer's certificate issuer is not recognized: '%s'\n",
issuer);
break;
default:
infof(conn->data, "Bad certificate received. Subject = '%s', "
"Issuer = '%s'\n", subject, issuer);
break;
}
if(result == SECSuccess)
infof(conn->data, "SSL certificate verify ok.\n");
PR_Free(subject);
PR_Free(subject_cn);
PR_Free(issuer);
}
/**
* Inform the application that the handshake is complete.
*/
static SECStatus HandshakeCallback(PRFileDesc *sock, void *arg)
{
(void)sock;
(void)arg;
return SECSuccess;
}
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
static void display_cert_info(struct SessionHandle *data,
CERTCertificate *cert)
{
char *subject, *issuer, *common_name;
PRExplodedTime printableTime;
char timeString[256];
PRTime notBefore, notAfter;
subject = CERT_NameToAscii(&cert->subject);
issuer = CERT_NameToAscii(&cert->issuer);
common_name = CERT_GetCommonName(&cert->subject);
infof(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(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(data, "\texpire date: %s\n", timeString);
infof(data, "\tcommon name: %s\n", common_name);
infof(data, "\tissuer: %s\n", issuer);
PR_Free(subject);
PR_Free(issuer);
PR_Free(common_name);
}
static void display_conn_info(struct connectdata *conn, PRFileDesc *sock)
{
SSLChannelInfo channel;
SSLCipherSuiteInfo suite;
CERTCertificate *cert;
Daniel Stenberg
committed
if(SSL_GetChannelInfo(sock, &channel, sizeof channel) ==
SECSuccess && channel.length == sizeof channel &&
channel.cipherSuite) {
Daniel Stenberg
committed
if(SSL_GetCipherSuiteInfo(channel.cipherSuite,
infof(conn->data, "SSL connection using %s\n", suite.cipherSuiteName);
}
}
infof(conn->data, "Server certificate:\n");
cert = SSL_PeerCertificate(sock);
display_cert_info(conn->data, cert);
CERT_DestroyCertificate(cert);
return;
}
Daniel Stenberg
committed
/**
*
* Check that the Peer certificate's issuer certificate matches the one found
* by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the
* issuer check, so we provide comments that mimic the OpenSSL
* X509_check_issued function (in x509v3/v3_purp.c)
*/
Daniel Stenberg
committed
static SECStatus check_issuer_cert(PRFileDesc *sock,
Daniel Stenberg
committed
char *issuer_nickname)
Daniel Stenberg
committed
{
CERTCertificate *cert,*cert_issuer,*issuer;
SECStatus res=SECSuccess;
void *proto_win = NULL;
/*
PRArenaPool *tmpArena = NULL;
CERTAuthKeyID *authorityKeyID = NULL;
SECITEM *caname = NULL;
Daniel Stenberg
committed
*/
cert = SSL_PeerCertificate(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))
Daniel Stenberg
committed
res = SECFailure;
else if(SECITEM_CompareItem(&cert_issuer->derCert,
&issuer->derCert)!=SECEqual)
Daniel Stenberg
committed
res = SECFailure;
CERT_DestroyCertificate(cert);
CERT_DestroyCertificate(issuer);
CERT_DestroyCertificate(cert_issuer);
return res;
}
/**
*
* Callback to pick the SSL client certificate.
*/
static SECStatus SelectClientCert(void *arg, PRFileDesc *sock,
struct CERTDistNamesStr *caNames,
struct CERTCertificateStr **pRetCert,
struct SECKEYPrivateKeyStr **pRetKey)
{
struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg;
struct SessionHandle *data = connssl->data;
const char *nickname = connssl->client_nickname;
#ifdef HAVE_PK11_CREATEGENERICOBJECT
if(connssl->obj_clicert) {
/* use the cert/key provided by PEM reader */
static const char pem_slotname[] = "PEM Token #1";
SECItem cert_der = { 0, NULL, 0 };
void *proto_win = SSL_RevealPinArg(sock);
PK11SlotInfo *slot = PK11_FindSlotByName(pem_slotname);
if(NULL == slot) {
failf(data, "NSS: PK11 slot not found: %s", pem_slotname);
return SECFailure;
}
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 from file not found");
CERT_DestroyCertificate(*pRetCert);
return SECFailure;
}
infof(data, "NSS: client certificate from file\n");
display_cert_info(data, *pRetCert);
return SECSuccess;
}
/* use the default NSS hook */
if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames,
pRetCert, pRetKey)
|| NULL == *pRetCert) {
if(NULL == nickname)
failf(data, "NSS: client certificate not found (nickname not "
"specified)");
else
failf(data, "NSS: client certificate not found: %s", nickname);
return SECFailure;
}
/* get certificate nickname if any */
nickname = (*pRetCert)->nickname;
if(NULL == nickname)
nickname = "[unknown]";
if(NULL == *pRetKey) {
failf(data, "NSS: private key not found for certificate: %s", nickname);
return SECFailure;
}
infof(data, "NSS: using client certificate: %s\n", nickname);
display_cert_info(data, *pRetCert);
return SECSuccess;
}
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
/* This function is supposed to decide, which error codes should be used
* to conclude server is TLS intolerant.
*
* taken from xulrunner - nsNSSIOLayer.cpp
*/
static PRBool
isTLSIntoleranceError(PRInt32 err)
{
switch (err) {
case SSL_ERROR_BAD_MAC_ALERT:
case SSL_ERROR_BAD_MAC_READ:
case SSL_ERROR_HANDSHAKE_FAILURE_ALERT:
case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT:
case SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE:
case SSL_ERROR_ILLEGAL_PARAMETER_ALERT:
case SSL_ERROR_NO_CYPHER_OVERLAP:
case SSL_ERROR_BAD_SERVER:
case SSL_ERROR_BAD_BLOCK_PADDING:
case SSL_ERROR_UNSUPPORTED_VERSION:
case SSL_ERROR_PROTOCOL_VERSION_ALERT:
case SSL_ERROR_RX_MALFORMED_FINISHED:
case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE:
case SSL_ERROR_DECODE_ERROR_ALERT:
case SSL_ERROR_RX_UNKNOWN_ALERT:
return PR_TRUE;
default:
return PR_FALSE;
}
}
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
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)
CURLcode rv;
if(initialized)
return CURLE_OK;
/* First we check if $SSL_DIR points to a valid dir */
cert_dir = getenv("SSL_DIR");
if(cert_dir) {
if((stat(cert_dir, &st) != 0) ||
(!S_ISDIR(st.st_mode))) {
cert_dir = NULL;
}
}
/* Now we check if the default location is a valid dir */
if(!cert_dir) {
if((stat(SSL_DIR, &st) == 0) &&
(S_ISDIR(st.st_mode))) {
cert_dir = (char *)SSL_DIR;
}
}
rv = nss_init_core(data, cert_dir);
if(rv)
return rv;
if(num_enabled_ciphers() == 0)
NSS_SetDomesticPolicy();
initialized = 1;
/**
* Global SSL init
*
* @retval 0 error initializing SSL
* @retval 1 SSL initialized successfully
*/
int Curl_nss_init(void)
{
Daniel Stenberg
committed
/* curl_global_init() is not thread-safe so this test is ok */
if(nss_initlock == NULL) {
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 256);
Daniel Stenberg
committed
nss_initlock = PR_NewLock();
nss_crllock = PR_NewLock();
Daniel Stenberg
committed
}
/* We will actually initialize NSS later */
return 1;
}
CURLcode Curl_nss_force_init(struct SessionHandle *data)
{
CURLcode rv;
if(!nss_initlock) {