- 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)
- 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
OpenSSL, NSS and GnuTLS-built libcurls.

View File

@ -2,7 +2,7 @@ Curl and libcurl 7.18.3
Public curl releases: 106
Command line options: 126
curl_easy_setopt() options: 151
curl_easy_setopt() options: 152
Public functions in libcurl: 58
Public web site mirrors: 37
Known libcurl bindings: 36
@ -11,7 +11,8 @@ Curl and libcurl 7.18.3
This release includes the following changes:
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:

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
140 - Arnaud Ebalard and Axel Tillequin's CRL support and issuer check patches
144 - Help apps use 64bit/LFS libcurl!
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
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
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

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)
.IP "CURLE_SSL_CRL_BADFILE (82)"
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*"
These error codes will never be returned. They used to be used in an old libcurl
version and are currently unused.

View File

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

View File

@ -143,6 +143,32 @@ static void showtime(struct SessionHandle *data,
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 */
static CURLcode handshake(struct connectdata *conn,
gnutls_session session,
@ -221,7 +247,8 @@ Curl_gtls_connect(struct connectdata *conn,
unsigned int cert_list_size;
const gnutls_datum *chainp;
unsigned int verify_status;
gnutls_x509_crt x509_cert;
gnutls_x509_crt x509_cert,x509_issuer;
gnutls_datum issuerp;
char certbuf[256]; /* big enough? */
size_t size;
unsigned int algo;
@ -375,7 +402,9 @@ Curl_gtls_connect(struct connectdata *conn,
chainp = gnutls_certificate_get_peers(session, &cert_list_size);
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");
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 */
if(verify_status & GNUTLS_CERT_INVALID) {
if(data->set.ssl.verifypeer) {
failf(data, "server certificate verification failed. CAfile: %s",
data->set.ssl.CAfile?data->set.ssl.CAfile:"none");
failf(data, "server certificate verification failed. CAfile: %s "
"CRLfile: %s", data->set.ssl.CAfile?data->set.ssl.CAfile:"none",
data->set.ssl.CRLfile?data->set.ssl.CRLfile:"none");
return CURLE_SSL_CACERT;
}
else
@ -419,6 +449,21 @@ Curl_gtls_connect(struct connectdata *conn,
gnutls_x509_crt_t format */
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);
rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME,
0, /* the first and only one */

View File

@ -721,6 +721,43 @@ static void display_conn_info(struct connectdata *conn, PRFileDesc *sock)
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.
@ -853,7 +890,7 @@ int Curl_nss_close_all(struct SessionHandle *data)
return 0;
}
CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
{
PRInt32 err;
PRFileDesc *model = NULL;
@ -1046,6 +1083,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
}
else {
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) {
free(nickname);
@ -1061,7 +1099,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
connssl->client_nickname = strdup(nickname);
if(SSL_GetClientAuthDataHook(model,
(SSLGetClientAuthData) SelectClientCert,
(void *)connssl->client_nickname) !=
(void *)connssl) !=
SECSuccess) {
curlerr = CURLE_SSL_CERTPROBLEM;
goto error;
@ -1074,6 +1112,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
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);
@ -1099,6 +1138,32 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex)
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;
error:

View File

@ -1629,6 +1629,9 @@ static CURLcode servercert(struct connectdata *conn,
long lerr;
ASN1_TIME *certdate;
struct SessionHandle *data = conn->data;
X509 *issuer;
FILE *fp;
connssl->server_cert = SSL_get_peer_certificate(connssl->handle);
if(!connssl->server_cert) {
if(strict)
@ -1678,6 +1681,41 @@ static CURLcode servercert(struct connectdata *conn,
/* We could do all sorts of certificate verification stuff here before
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=
SSL_get_verify_result(connssl->handle);
if(data->set.ssl.certverifyresult != X509_V_OK) {
@ -1690,12 +1728,12 @@ static CURLcode servercert(struct connectdata *conn,
retcode = CURLE_PEER_FAILED_VERIFICATION;
}
else
infof(data, "SSL certificate verify result: %s (%ld),"
infof(data, "\t SSL certificate verify result: %s (%ld),"
" continuing anyway.\n",
X509_verify_cert_error_string(lerr), lerr);
}
else
infof(data, "SSL certificate verify ok.\n");
infof(data, "\t SSL certificate verify ok.\n");
}
X509_free(connssl->server_cert);

View File

@ -225,6 +225,9 @@ curl_easy_strerror(CURLcode error)
case CURLE_SSL_CRL_BADFILE:
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:
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],
va_arg(param, char *));
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:
/*
* 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.CAfile = data->set.str[STRING_SSL_CAFILE];
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.egdsocket = data->set.str[STRING_SSL_EGDSOCKET];
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 *CAfile; /* cerficate to verify peer against */
char *CRLfile; /* CRL to check cerficate revocation */
char *issuercert; /* optional issuer cerficate filename */
char *random_file; /* path to file containing "random" data */
char *egdsocket; /* path to file containing the EGD daemon socket */
char *cipher_list; /* list of ciphers to use */
@ -1319,6 +1320,7 @@ enum dupstring {
STRING_USERPWD, /* <user:password>, if used */
STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */
STRING_SSL_CRLFILE, /* crl file to check certificate */
STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */
/* -- end of strings -- */
STRING_LAST /* not used, just an end-of-list marker */