mirror of
https://github.com/curl/curl.git
synced 2024-11-21 01:16:58 +08:00
- Axel Tillequin and Arnaud Ebalard added support for CURLOPT_ISSUERCERT, for
OpenSSL, NSS and GnuTLS-built libcurls.
This commit is contained in:
parent
b39d1e9b9d
commit
621c2b9015
3
CHANGES
3
CHANGES
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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 -
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
53
lib/gtls.c
53
lib/gtls.c
@ -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 */
|
||||
|
69
lib/nss.c
69
lib/nss.c
@ -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:
|
||||
|
42
lib/ssluse.c
42
lib/ssluse.c
@ -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);
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user