- Axel Tillequin and Arnaud Ebalard added support for CURLOPT_ISSUERCERT, for
authorDaniel Stenberg <daniel@haxx.se>
Fri, 6 Jun 2008 20:52:32 +0000 (20:52 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 6 Jun 2008 20:52:32 +0000 (20:52 +0000)
  OpenSSL, NSS and GnuTLS-built libcurls.

12 files changed:
CHANGES
RELEASE-NOTES
TODO-RELEASE
docs/libcurl/curl_easy_setopt.3
docs/libcurl/libcurl-errors.3
include/curl/curl.h
lib/gtls.c
lib/nss.c
lib/ssluse.c
lib/strerror.c
lib/url.c
lib/urldata.h

diff --git a/CHANGES b/CHANGES
index 4145bbe..5efd5df 100644 (file)
--- 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.
 
index 9f7157e..fc90d76 100644 (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:
 
index 661894c..3fa2f0a 100644 (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 -
index f1f086e..6e7ce3e 100644 (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
index 994489b..c1ab312 100644 (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.
index a67fd22..556c222 100644 (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;
 
index e9e4102..6e762f1 100644 (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 */
index a5fc795..5e01c44 100644 (file)
--- 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:
index f14ad34..4fff13d 100644 (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);
index 03b0158..032e2c7 100644 (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";
 
index 0e71ced..a7f1a0c 100644 (file)
--- 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];
index def598b..2e0f692 100644 (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 */