- Axel Tillequin and Arnaud Ebalard added support for CURLOPT_ISSUERCERT, for

OpenSSL, NSS and GnuTLS-built libcurls.
This commit is contained in:
Daniel Stenberg
2008-06-06 20:52:32 +00:00
parent b39d1e9b9d
commit 621c2b9015
12 changed files with 199 additions and 12 deletions

View File

@@ -8,6 +8,9 @@
Daniel Stenberg (6 Jun 2008) Daniel Stenberg (6 Jun 2008)
- Axel Tillequin and Arnaud Ebalard added support for CURLOPT_ISSUERCERT, for
OpenSSL, NSS and GnuTLS-built libcurls.
- Axel Tillequin and Arnaud Ebalard added support for CURLOPT_CRLFILE, for - Axel Tillequin and Arnaud Ebalard added support for CURLOPT_CRLFILE, for
OpenSSL, NSS and GnuTLS-built libcurls. OpenSSL, NSS and GnuTLS-built libcurls.

View File

@@ -2,7 +2,7 @@ Curl and libcurl 7.18.3
Public curl releases: 106 Public curl releases: 106
Command line options: 126 Command line options: 126
curl_easy_setopt() options: 151 curl_easy_setopt() options: 152
Public functions in libcurl: 58 Public functions in libcurl: 58
Public web site mirrors: 37 Public web site mirrors: 37
Known libcurl bindings: 36 Known libcurl bindings: 36
@@ -11,7 +11,8 @@ Curl and libcurl 7.18.3
This release includes the following changes: This release includes the following changes:
o Added CURLINFO_PRIMARY_IP o Added CURLINFO_PRIMARY_IP
o Added CURLOPT_CRLFILE o Added CURLOPT_CRLFILE and CURLE_SSL_CRL_BADFILE
o Added CURLOPT_ISSUERCERT and CURLE_SSL_ISSUER_ERROR
This release includes the following bugfixes: This release includes the following bugfixes:

View File

@@ -3,8 +3,6 @@ To be addressed before 7.18.3 (planned release: August 2008)
139 - Christopher Palow's CURLM_EASY_HANDLE_EXISTS patch 139 - Christopher Palow's CURLM_EASY_HANDLE_EXISTS patch
140 - Arnaud Ebalard and Axel Tillequin's CRL support and issuer check patches
144 - Help apps use 64bit/LFS libcurl! 144 - Help apps use 64bit/LFS libcurl!
145 - 145 -

View File

@@ -1443,6 +1443,22 @@ bundle is assumed to be stored, as established at build time.
When built against NSS this is the directory that the NSS certificate When built against NSS this is the directory that the NSS certificate
database resides in. database resides in.
.IP CURLOPT_ISSUERCERT
Pass a char * to a zero terminated string naming a file holding a CA
certificate in PEM format. If the option is set, an additional check against
the peer certificate is performed to verify the issuer is indeed the one
associated with the certificate provided by the option. This additional check
is useful in multi-level PKI where one need to enforce the peer certificate is
from a specific branch of the tree.
This option makes sense only when used in combination with the
\fICURLOPT_SSL_VERIFYPEER\fP option. Otherwise, the result of the check is not
considered as failure.
A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option,
which is returned if the setup of the SSL/TLS session has failed due to a
mismatch with the issuer of peer certificate (\fICURLOPT_SSL_VERIFYPEER\fP has
to be set too for the check to fail). (Added in 7.18.3)
.IP CURLOPT_CAPATH .IP CURLOPT_CAPATH
Pass a char * to a zero terminated string naming a directory holding multiple Pass a char * to a zero terminated string naming a directory holding multiple
CA certificates to verify the peer with. The certificate directory must be CA certificates to verify the peer with. The certificate directory must be

View File

@@ -214,6 +214,8 @@ return code is only returned from \fIcurl_easy_recv(3)\fP and
\fIcurl_easy_send(3)\fP (Added in 7.18.2) \fIcurl_easy_send(3)\fP (Added in 7.18.2)
.IP "CURLE_SSL_CRL_BADFILE (82)" .IP "CURLE_SSL_CRL_BADFILE (82)"
Failed to load CRL file (Added in 7.18.3) Failed to load CRL file (Added in 7.18.3)
.IP "CURLE_SSL_ISSUER_ERROR (83)"
Issuer check failed (Added in 7.18.3)
.IP "CURLE_OBSOLETE*" .IP "CURLE_OBSOLETE*"
These error codes will never be returned. They used to be used in an old libcurl These error codes will never be returned. They used to be used in an old libcurl
version and are currently unused. version and are currently unused.

View File

@@ -456,6 +456,8 @@ typedef enum {
in 7.18.2) */ in 7.18.2) */
CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or
wrong format (Added in 7.18.3) */ wrong format (Added in 7.18.3) */
CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in
7.18.3) */
CURL_LAST /* never use! */ CURL_LAST /* never use! */
} CURLcode; } CURLcode;
@@ -1206,6 +1208,9 @@ typedef enum {
/* CRL file */ /* CRL file */
CINIT(CRLFILE, OBJECTPOINT, 169), CINIT(CRLFILE, OBJECTPOINT, 169),
/* Issuer certificate */
CINIT(ISSUERCERT, OBJECTPOINT, 170),
CURLOPT_LASTENTRY /* the last unused */ CURLOPT_LASTENTRY /* the last unused */
} CURLoption; } CURLoption;

View File

@@ -143,6 +143,32 @@ static void showtime(struct SessionHandle *data,
infof(data, "%s", data->state.buffer); infof(data, "%s", data->state.buffer);
} }
static gnutls_datum load_file (const char *file)
{
FILE *f;
gnutls_datum loaded_file = { NULL, 0 };
long filelen;
void *ptr;
if (!(f = fopen(file, "r"))
|| fseek(f, 0, SEEK_END) != 0
|| (filelen = ftell(f)) < 0
|| fseek(f, 0, SEEK_SET) != 0
|| !(ptr = malloc((size_t)filelen))
|| fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) {
return loaded_file;
}
loaded_file.data = ptr;
loaded_file.size = (unsigned int)filelen;
return loaded_file;
}
static void unload_file(gnutls_datum data) {
free(data.data);
}
/* this function does a BLOCKING SSL/TLS (re-)handshake */ /* this function does a BLOCKING SSL/TLS (re-)handshake */
static CURLcode handshake(struct connectdata *conn, static CURLcode handshake(struct connectdata *conn,
gnutls_session session, gnutls_session session,
@@ -221,7 +247,8 @@ Curl_gtls_connect(struct connectdata *conn,
unsigned int cert_list_size; unsigned int cert_list_size;
const gnutls_datum *chainp; const gnutls_datum *chainp;
unsigned int verify_status; unsigned int verify_status;
gnutls_x509_crt x509_cert; gnutls_x509_crt x509_cert,x509_issuer;
gnutls_datum issuerp;
char certbuf[256]; /* big enough? */ char certbuf[256]; /* big enough? */
size_t size; size_t size;
unsigned int algo; unsigned int algo;
@@ -375,7 +402,9 @@ Curl_gtls_connect(struct connectdata *conn,
chainp = gnutls_certificate_get_peers(session, &cert_list_size); chainp = gnutls_certificate_get_peers(session, &cert_list_size);
if(!chainp) { if(!chainp) {
if(data->set.ssl.verifypeer) { if(data->set.ssl.verifypeer ||
data->set.ssl.verifyhost ||
data->set.ssl.issuercert) {
failf(data, "failed to get server cert"); failf(data, "failed to get server cert");
return CURLE_PEER_FAILED_VERIFICATION; return CURLE_PEER_FAILED_VERIFICATION;
} }
@@ -399,8 +428,9 @@ Curl_gtls_connect(struct connectdata *conn,
/* verify_status is a bitmask of gnutls_certificate_status bits */ /* verify_status is a bitmask of gnutls_certificate_status bits */
if(verify_status & GNUTLS_CERT_INVALID) { if(verify_status & GNUTLS_CERT_INVALID) {
if(data->set.ssl.verifypeer) { if(data->set.ssl.verifypeer) {
failf(data, "server certificate verification failed. CAfile: %s", failf(data, "server certificate verification failed. CAfile: %s "
data->set.ssl.CAfile?data->set.ssl.CAfile:"none"); "CRLfile: %s", data->set.ssl.CAfile?data->set.ssl.CAfile:"none",
data->set.ssl.CRLfile?data->set.ssl.CRLfile:"none");
return CURLE_SSL_CACERT; return CURLE_SSL_CACERT;
} }
else else
@@ -419,6 +449,21 @@ Curl_gtls_connect(struct connectdata *conn,
gnutls_x509_crt_t format */ gnutls_x509_crt_t format */
gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER);
if (data->set.ssl.issuercert) {
gnutls_x509_crt_init(&x509_issuer);
issuerp = load_file(data->set.ssl.issuercert);
gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM);
rc = gnutls_x509_crt_check_issuer(x509_cert,x509_issuer);
unload_file(issuerp);
if (rc <= 0) {
failf(data, "server certificate issuer check failed (IssuerCert: %s)",
data->set.ssl.issuercert?data->set.ssl.issuercert:"none");
return CURLE_SSL_ISSUER_ERROR;
}
infof(data,"\t server certificate issuer check OK (Issuer Cert: %s)\n",
data->set.ssl.issuercert?data->set.ssl.issuercert:"none");
}
size=sizeof(certbuf); size=sizeof(certbuf);
rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME, rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME,
0, /* the first and only one */ 0, /* the first and only one */

View File

@@ -721,6 +721,43 @@ static void display_conn_info(struct connectdata *conn, PRFileDesc *sock)
return; return;
} }
/**
*
* 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)
*/
static SECStatus check_issuer_cert(struct connectdata *conn, PRFileDesc *sock, char* issuer_nickname)
{
CERTCertificate *cert,*cert_issuer,*issuer;
SECStatus res=SECSuccess;
void *proto_win = NULL;
/*
PRArenaPool *tmpArena = NULL;
CERTAuthKeyID *authorityKeyID = NULL;
SECITEM *caname = NULL;
*/
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;
else if (CERT_CompareCerts(cert_issuer,issuer)==PR_FALSE)
res = SECFailure;
CERT_DestroyCertificate(cert);
CERT_DestroyCertificate(issuer);
CERT_DestroyCertificate(cert_issuer);
return res;
}
/** /**
* *
* Callback to pick the SSL client certificate. * Callback to pick the SSL client certificate.
@@ -853,7 +890,7 @@ int Curl_nss_close_all(struct SessionHandle *data)
return 0; return 0;
} }
CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
{ {
PRInt32 err; PRInt32 err;
PRFileDesc *model = NULL; PRFileDesc *model = NULL;
@@ -1046,6 +1083,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
} }
else { else {
strncpy(nickname, data->set.str[STRING_CERT], PATH_MAX); strncpy(nickname, data->set.str[STRING_CERT], PATH_MAX);
nickname[PATH_MAX-1]=0; /* make sure this is zero terminated */
} }
if(nss_Init_Tokens(conn) != SECSuccess) { if(nss_Init_Tokens(conn) != SECSuccess) {
free(nickname); free(nickname);
@@ -1061,7 +1099,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
connssl->client_nickname = strdup(nickname); connssl->client_nickname = strdup(nickname);
if(SSL_GetClientAuthDataHook(model, if(SSL_GetClientAuthDataHook(model,
(SSLGetClientAuthData) SelectClientCert, (SSLGetClientAuthData) SelectClientCert,
(void *)connssl->client_nickname) != (void *)connssl) !=
SECSuccess) { SECSuccess) {
curlerr = CURLE_SSL_CERTPROBLEM; curlerr = CURLE_SSL_CERTPROBLEM;
goto error; goto error;
@@ -1074,6 +1112,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
else else
connssl->client_nickname = NULL; connssl->client_nickname = NULL;
/* Import our model socket onto the existing file descriptor */ /* Import our model socket onto the existing file descriptor */
connssl->handle = PR_ImportTCPSocket(sockfd); connssl->handle = PR_ImportTCPSocket(sockfd);
connssl->handle = SSL_ImportFD(model, connssl->handle); connssl->handle = SSL_ImportFD(model, connssl->handle);
@@ -1099,6 +1138,32 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
display_conn_info(conn, connssl->handle); display_conn_info(conn, connssl->handle);
if (data->set.str[STRING_SSL_ISSUERCERT]) {
char *n;
char *nickname;
nickname = (char *)malloc(PATH_MAX);
if(is_file(data->set.str[STRING_SSL_ISSUERCERT])) {
n = strrchr(data->set.str[STRING_SSL_ISSUERCERT], '/');
if (n) {
n++; /* skip last slash */
snprintf(nickname, PATH_MAX, "PEM Token #%ld:%s", 1, n);
}
}
else {
strncpy(nickname, data->set.str[STRING_SSL_ISSUERCERT], PATH_MAX);
nickname[PATH_MAX-1]=0; /* make sure this is zero terminated */
}
if (check_issuer_cert(conn,connssl->handle,nickname)==SECFailure) {
infof(data,"SSL certificate issuer check failed\n");
free(nickname);
curlerr = CURLE_SSL_ISSUER_ERROR;
goto error;
}
else {
infof("SSL certificate issuer check ok\n");
}
}
return CURLE_OK; return CURLE_OK;
error: error:

View File

@@ -1629,6 +1629,9 @@ static CURLcode servercert(struct connectdata *conn,
long lerr; long lerr;
ASN1_TIME *certdate; ASN1_TIME *certdate;
struct SessionHandle *data = conn->data; struct SessionHandle *data = conn->data;
X509 *issuer;
FILE *fp;
connssl->server_cert = SSL_get_peer_certificate(connssl->handle); connssl->server_cert = SSL_get_peer_certificate(connssl->handle);
if(!connssl->server_cert) { if(!connssl->server_cert) {
if(strict) if(strict)
@@ -1678,6 +1681,41 @@ static CURLcode servercert(struct connectdata *conn,
/* We could do all sorts of certificate verification stuff here before /* We could do all sorts of certificate verification stuff here before
deallocating the certificate. */ deallocating the certificate. */
/* e.g. match issuer name with provided issuer certificate */
if (data->set.str[STRING_SSL_ISSUERCERT]) {
if (! (fp=fopen(data->set.str[STRING_SSL_ISSUERCERT],"r"))) {
if (strict)
failf(data, "SSL: Unable to open issuer cert (%s)\n",
data->set.str[STRING_SSL_ISSUERCERT]);
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
return CURLE_SSL_ISSUER_ERROR;
}
issuer = PEM_read_X509(fp,NULL,NULL,NULL);
if (!issuer) {
if (strict)
failf(data, "SSL: Unable to read issuer cert (%s)\n",
data->set.str[STRING_SSL_ISSUERCERT]);
X509_free(connssl->server_cert);
X509_free(issuer);
fclose(fp);
return CURLE_SSL_ISSUER_ERROR;
}
fclose(fp);
if (X509_check_issued(issuer,connssl->server_cert) != X509_V_OK) {
if (strict)
failf(data, "SSL: Certificate issuer check failed (%s)\n",
data->set.str[STRING_SSL_ISSUERCERT]);
X509_free(connssl->server_cert);
X509_free(issuer);
connssl->server_cert = NULL;
return CURLE_SSL_ISSUER_ERROR;
}
infof(data, "\t SSL certificate issuer check ok (%s)\n",
data->set.str[STRING_SSL_ISSUERCERT]);
X509_free(issuer);
}
lerr = data->set.ssl.certverifyresult= lerr = data->set.ssl.certverifyresult=
SSL_get_verify_result(connssl->handle); SSL_get_verify_result(connssl->handle);
if(data->set.ssl.certverifyresult != X509_V_OK) { if(data->set.ssl.certverifyresult != X509_V_OK) {
@@ -1690,12 +1728,12 @@ static CURLcode servercert(struct connectdata *conn,
retcode = CURLE_PEER_FAILED_VERIFICATION; retcode = CURLE_PEER_FAILED_VERIFICATION;
} }
else else
infof(data, "SSL certificate verify result: %s (%ld)," infof(data, "\t SSL certificate verify result: %s (%ld),"
" continuing anyway.\n", " continuing anyway.\n",
X509_verify_cert_error_string(lerr), lerr); X509_verify_cert_error_string(lerr), lerr);
} }
else else
infof(data, "SSL certificate verify ok.\n"); infof(data, "\t SSL certificate verify ok.\n");
} }
X509_free(connssl->server_cert); X509_free(connssl->server_cert);

View File

@@ -225,6 +225,9 @@ curl_easy_strerror(CURLcode error)
case CURLE_SSL_CRL_BADFILE: case CURLE_SSL_CRL_BADFILE:
return "Failed to load CRL file (path? access rights?, format?)"; return "Failed to load CRL file (path? access rights?, format?)";
case CURLE_SSL_ISSUER_ERROR:
return "Issuer check against peer certificate failed";
case CURLE_SEND_FAIL_REWIND: case CURLE_SEND_FAIL_REWIND:
return "Send failed since rewinding of the data stream failed"; return "Send failed since rewinding of the data stream failed";

View File

@@ -1819,6 +1819,14 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
result = setstropt(&data->set.str[STRING_SSL_CRLFILE], result = setstropt(&data->set.str[STRING_SSL_CRLFILE],
va_arg(param, char *)); va_arg(param, char *));
break; break;
case CURLOPT_ISSUERCERT:
/*
* Set Issuer certificate file
* to check certificates issuer
*/
result = setstropt(&data->set.str[STRING_SSL_ISSUERCERT],
va_arg(param, char *));
break;
case CURLOPT_TELNETOPTIONS: case CURLOPT_TELNETOPTIONS:
/* /*
* Set a linked list of telnet options * Set a linked list of telnet options
@@ -3960,6 +3968,7 @@ static CURLcode CreateConnection(struct SessionHandle *data,
data->set.ssl.CApath = data->set.str[STRING_SSL_CAPATH]; data->set.ssl.CApath = data->set.str[STRING_SSL_CAPATH];
data->set.ssl.CAfile = data->set.str[STRING_SSL_CAFILE]; data->set.ssl.CAfile = data->set.str[STRING_SSL_CAFILE];
data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE]; data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE];
data->set.ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT];
data->set.ssl.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; data->set.ssl.random_file = data->set.str[STRING_SSL_RANDOM_FILE];
data->set.ssl.egdsocket = data->set.str[STRING_SSL_EGDSOCKET]; data->set.ssl.egdsocket = data->set.str[STRING_SSL_EGDSOCKET];
data->set.ssl.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; data->set.ssl.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST];

View File

@@ -213,6 +213,7 @@ struct ssl_config_data {
char *CApath; /* certificate dir (doesn't work on windows) */ char *CApath; /* certificate dir (doesn't work on windows) */
char *CAfile; /* cerficate to verify peer against */ char *CAfile; /* cerficate to verify peer against */
char *CRLfile; /* CRL to check cerficate revocation */ char *CRLfile; /* CRL to check cerficate revocation */
char *issuercert; /* optional issuer cerficate filename */
char *random_file; /* path to file containing "random" data */ char *random_file; /* path to file containing "random" data */
char *egdsocket; /* path to file containing the EGD daemon socket */ char *egdsocket; /* path to file containing the EGD daemon socket */
char *cipher_list; /* list of ciphers to use */ char *cipher_list; /* list of ciphers to use */
@@ -1319,6 +1320,7 @@ enum dupstring {
STRING_USERPWD, /* <user:password>, if used */ STRING_USERPWD, /* <user:password>, if used */
STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */
STRING_SSL_CRLFILE, /* crl file to check certificate */ STRING_SSL_CRLFILE, /* crl file to check certificate */
STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */
/* -- end of strings -- */ /* -- end of strings -- */
STRING_LAST /* not used, just an end-of-list marker */ STRING_LAST /* not used, just an end-of-list marker */