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"
#include <string.h>
#include <stdlib.h>
#include <ctype.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"
#include "easyif.h" /* for Curl_convert_from_utf8 prototype */
/* 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(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 };
#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},
105
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
{"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;
}
static char *fmt_nickname(char *str, bool *nickname_alloc)
{
char *nickname = NULL;
*nickname_alloc = FALSE;
if(is_file(str)) {
char *n = strrchr(str, '/');
if(n) {
*nickname_alloc = TRUE;
n++; /* skip last slash */
nickname = aprintf("PEM Token #%d:%s", 1, n);
Daniel Stenberg
committed
static int nss_load_cert(struct ssl_connect_data *ssl,
const char *filename, PRBool cacert)
{
#ifdef HAVE_PK11_CREATEGENERICOBJECT
CK_SLOT_ID slotID;
PK11SlotInfo * slot = NULL;
CK_ATTRIBUTE *attrs;
CK_ATTRIBUTE theTemplate[20];
CK_BBOOL cktrue = CK_TRUE;
CK_BBOOL ckfalse = CK_FALSE;
CK_OBJECT_CLASS objClass = CKO_CERTIFICATE;
Daniel Stenberg
committed
char slotname[SLOTSIZE];
#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
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.
*/
Daniel Stenberg
committed
if(cacert)
slotID = 0;
else
slotID = 1;
snprintf(slotname, SLOTSIZE, "PEM Token #%ld", slotID);
Daniel Stenberg
committed
nickname = aprintf("PEM Token #%ld:%s", slotID, n);
if(!nickname)
return 0;
slot = PK11_FindSlotByName(slotname);
Daniel Stenberg
committed
if(!slot) {
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,
Daniel Stenberg
committed
if(cacert) {
PK11_SETATTRS(attrs, CKA_TRUST, &cktrue, sizeof(CK_BBOOL) );
}
else {
PK11_SETATTRS(attrs, CKA_TRUST, &ckfalse, sizeof(CK_BBOOL) );
}
/* This load the certificate in our PEM module into the appropriate
* slot.
*/
Daniel Stenberg
committed
ssl->cacert[slotID] = PK11_CreateGenericObject(slot, theTemplate, 4,
PR_FALSE /* isPerm */);
PK11_FreeSlot(slot);
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;
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
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 = NULL;
CK_ATTRIBUTE *attrs;
CK_ATTRIBUTE theTemplate[20];
CK_BBOOL cktrue = CK_TRUE;
CK_OBJECT_CLASS objClass = CKO_PRIVATE_KEY;
CK_SLOT_ID slotID;
Daniel Stenberg
committed
char slotname[SLOTSIZE];
Daniel Stenberg
committed
struct ssl_connect_data *sslconn = &conn->ssl[sockindex];
attrs = theTemplate;
/* FIXME: grok the various file types */
slotID = 1; /* hardcoded for now */
Daniel Stenberg
committed
snprintf(slotname, sizeof(slotname), "PEM Token #%ld", slotID);
slot = PK11_FindSlotByName(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 */
Daniel Stenberg
committed
sslconn->key = PK11_CreateGenericObject(slot, theTemplate, 3,
PR_FALSE /* isPerm */);
if(sslconn->key == 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 is initialized in nss_Init_Tokens() */
Daniel Stenberg
committed
if(PK11_Authenticate(slot, PR_TRUE,
conn->data->set.str[STRING_KEY_PASSWD]) != SECSuccess) {
PK11_FreeSlot(slot);
return 0;
}
PK11_FreeSlot(slot);
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:
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);
}
static SECStatus BadCertHandler(void *arg, PRFileDesc *sock)
{
SECStatus success = SECSuccess;
struct connectdata *conn = (struct connectdata *)arg;
PRErrorCode err = PR_GetError();
CERTCertificate *cert = NULL;
char *subject, *subject_cn, *issuer;
Daniel Stenberg
committed
if(conn->data->set.ssl.certverifyresult!=0)
return success;
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);
Daniel Stenberg
committed
if(conn->data->set.ssl.verifypeer)
success = SECFailure;
break;
case SEC_ERROR_UNTRUSTED_ISSUER:
Daniel Stenberg
committed
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.verifyhost) {
failf(conn->data, "SSL: certificate subject name '%s' does not match "
"target host name '%s'", subject_cn, conn->host.dispname);
success = SECFailure;
} else {
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:
Daniel Stenberg
committed
if(conn->data->set.ssl.verifypeer)
success = SECFailure;
infof(conn->data, "Remote Certificate has expired.\n");
break;
case SEC_ERROR_UNKNOWN_ISSUER:
if(conn->data->set.ssl.verifypeer)
success = SECFailure;
infof(conn->data, "Peer's certificate issuer is not recognized: '%s'\n",
issuer);
break;
default:
Daniel Stenberg
committed
if(conn->data->set.ssl.verifypeer)
success = SECFailure;
infof(conn->data, "Bad certificate received. Subject = '%s', "
"Issuer = '%s'\n", subject, issuer);
break;
}
Daniel Stenberg
committed
if(success == SECSuccess)
infof(conn->data, "SSL certificate verify ok.\n");
PR_Free(subject);
PR_Free(subject_cn);
PR_Free(issuer);
return success;
}
/**
* Inform the application that the handshake is complete.
*/
static SECStatus HandshakeCallback(PRFileDesc *sock, void *arg)
{
(void)sock;
(void)arg;
return SECSuccess;
}
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
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))
res = SECFailure;
Daniel Stenberg
committed
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)
{
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)) {
/* use the cert/key provided by PEM reader */
PK11SlotInfo *slot;
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);
return SECFailure;
}
slot = PK11_FindSlotByName(pem_slotname);
if (NULL == slot) {
failf(data, "NSS: PK11 slot not found: %s", pem_slotname);
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);
return SECFailure;
}
infof(data, "NSS: client certificate: %s\n", nickname);
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;
}
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
/* 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;
}
}
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
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
941
942
943
944
static CURLcode init_nss(struct SessionHandle *data)
{
char *cert_dir;
struct_stat st;
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;
}
}
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;
}
}
if(num_enabled_ciphers() == 0)
NSS_SetDomesticPolicy();
return CURLE_OK;
}
/**
* 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) {
failf(data, "unable to initialize NSS, curl_global_init() should have been "
"called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL");
return CURLE_OUT_OF_MEMORY;
}
PR_Lock(nss_initlock);
rv = init_nss(data);
PR_Unlock(nss_initlock);
return rv;
}
/* Global cleanup */
void Curl_nss_cleanup(void)
{
Daniel Stenberg
committed
/* This function isn't required to be threadsafe and this is only done
* as a safety feature.
*/
PR_Lock(nss_initlock);
if (initialized) {
/* Free references to client certificates held in the SSL session cache.
* Omitting this hampers destruction of the security module owning
* the certificates. */
SSL_ClearSessionCache();
if(mod && SECSuccess == SECMOD_UnloadUserModule(mod)) {
SECMOD_DestroyModule(mod);
mod = NULL;
}
Daniel Stenberg
committed
NSS_Shutdown();
}
Daniel Stenberg
committed
PR_Unlock(nss_initlock);