aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES3
-rw-r--r--RELEASE-NOTES5
-rw-r--r--TODO-RELEASE2
-rw-r--r--docs/libcurl/curl_easy_setopt.316
-rw-r--r--docs/libcurl/libcurl-errors.32
-rw-r--r--include/curl/curl.h5
-rw-r--r--lib/gtls.c53
-rw-r--r--lib/nss.c69
-rw-r--r--lib/ssluse.c42
-rw-r--r--lib/strerror.c3
-rw-r--r--lib/url.c9
-rw-r--r--lib/urldata.h2
12 files changed, 199 insertions, 12 deletions
diff --git a/CHANGES b/CHANGES
index 4145bbe0b..5efd5df90 100644
--- a/CHANGES
+++ b/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.
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index 9f7157e26..fc90d7611 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -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:
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 661894c3d..3fa2f0aff 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -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 -
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index f1f086e84..6e7ce3ec7 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -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
diff --git a/docs/libcurl/libcurl-errors.3 b/docs/libcurl/libcurl-errors.3
index 994489b71..c1ab31237 100644
--- a/docs/libcurl/libcurl-errors.3
+++ b/docs/libcurl/libcurl-errors.3
@@ -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.
diff --git a/include/curl/curl.h b/include/curl/curl.h
index a67fd2210..556c2226a 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -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;
diff --git a/lib/gtls.c b/lib/gtls.c
index e9e410243..6e762f161 100644
--- a/lib/gtls.c
+++ b/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 */
diff --git a/lib/nss.c b/lib/nss.c
index a5fc795f8..5e01c4448 100644
--- a/lib/nss.c
+++ b/lib/nss.c
@@ -723,6 +723,43 @@ static void display_conn_info(struct connectdata *conn, PRFileDesc *sock)
/**
*
+ * 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.
*/
static SECStatus SelectClientCert(void *arg, PRFileDesc *sock,
@@ -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:
diff --git a/lib/ssluse.c b/lib/ssluse.c
index f14ad344e..4fff13d78 100644
--- a/lib/ssluse.c
+++ b/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);
diff --git a/lib/strerror.c b/lib/strerror.c
index 03b01582c..032e2c747 100644
--- a/lib/strerror.c
+++ b/lib/strerror.c
@@ -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";
diff --git a/lib/url.c b/lib/url.c
index 0e71cede0..a7f1a0c1a 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -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];
diff --git a/lib/urldata.h b/lib/urldata.h
index def598b41..2e0f69204 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -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 */