Add support in the fakens utility for TLSA records
[users/jgh/exim.git] / test / src / client.c
index ccd6f419cf6981733536337c66bb79e6fb75c095..e7210f22398ac15252efbde89b3d85078f0a6eec 100644 (file)
@@ -1,9 +1,8 @@
-/* $Cambridge: exim/test/src/client.c,v 1.2 2006/10/16 13:42:19 ph10 Exp $ */
-
 /* A little hacked up program that makes a TCP/IP call and reads a script to
 drive it, for testing Exim server code running as a daemon. It's got a bit
 messy with the addition of support for either OpenSSL or GnuTLS. The code for
-those was hacked out of Exim itself. */
+those was hacked out of Exim itself, then code for OpenSSL OCSP stapling was
+ripped from the openssl ocsp and s_client utilities. */
 
 /* ANSI C standard includes */
 
@@ -61,22 +60,27 @@ static int sigalrm_seen = 0;
 latter needs a whole pile of tables. */
 
 #ifdef HAVE_OPENSSL
-#define HAVE_TLS
-#include <openssl/crypto.h>
-#include <openssl/x509.h>
-#include <openssl/pem.h>
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/rand.h>
+# define HAVE_TLS
+# include <openssl/crypto.h>
+# include <openssl/x509.h>
+# include <openssl/pem.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/rand.h>
+# include <openssl/ocsp.h>
 #endif
 
 
 #ifdef HAVE_GNUTLS
-#define HAVE_TLS
-#include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
+# define HAVE_TLS
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+# if GNUTLS_VERSION_NUMBER >= 0x030103
+#  define HAVE_OCSP
+#  include <gnutls/ocsp.h>
+# endif
 
-#define DH_BITS      768
+# define DH_BITS      768
 
 /* Local static variables for GNUTLS */
 
@@ -112,10 +116,14 @@ static const int mac_priority[16] = {
 static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
 static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 };
 
-#endif
+#endif /*HAVE_GNUTLS*/
 
 
 
+#ifdef HAVE_TLS
+char * ocsp_stapling = NULL;
+#endif
+
 
 /*************************************************
 *            SIGALRM handler - crash out         *
@@ -147,11 +155,97 @@ sigalrm_seen = 1;
 /****************************************************************************/
 
 #ifdef HAVE_OPENSSL
+
+X509_STORE *
+setup_verify(BIO *bp, char *CAfile, char *CApath)
+{
+        X509_STORE *store;
+        X509_LOOKUP *lookup;
+        if(!(store = X509_STORE_new())) goto end;
+        lookup=X509_STORE_add_lookup(store,X509_LOOKUP_file());
+        if (lookup == NULL) goto end;
+        if (CAfile) {
+                if(!X509_LOOKUP_load_file(lookup,CAfile,X509_FILETYPE_PEM)) {
+                        BIO_printf(bp, "Error loading file %s\n", CAfile);
+                        goto end;
+                }
+        } else X509_LOOKUP_load_file(lookup,NULL,X509_FILETYPE_DEFAULT);
+
+        lookup=X509_STORE_add_lookup(store,X509_LOOKUP_hash_dir());
+        if (lookup == NULL) goto end;
+        if (CApath) {
+                if(!X509_LOOKUP_add_dir(lookup,CApath,X509_FILETYPE_PEM)) {
+                        BIO_printf(bp, "Error loading directory %s\n", CApath);
+                        goto end;
+                }
+        } else X509_LOOKUP_add_dir(lookup,NULL,X509_FILETYPE_DEFAULT);
+
+        ERR_clear_error();
+        return store;
+        end:
+        X509_STORE_free(store);
+        return NULL;
+}
+
+
+static int
+tls_client_stapling_cb(SSL *s, void *arg)
+{
+const unsigned char *p;
+int len;
+OCSP_RESPONSE *rsp;
+OCSP_BASICRESP *bs;
+char *CAfile = NULL;
+X509_STORE *store = NULL;
+int ret = 1;
+
+len = SSL_get_tlsext_status_ocsp_resp(s, &p);
+/*BIO_printf(arg, "OCSP response: ");*/
+if (!p)
+       {
+       BIO_printf(arg, "no response received\n");
+       return 1;
+       }
+if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
+       {
+       BIO_printf(arg, "response parse error\n");
+       BIO_dump_indent(arg, (char *)p, len, 4);
+       return 0;
+       }
+if(!(bs = OCSP_response_get1_basic(rsp)))
+  {
+  BIO_printf(arg, "error parsing response\n");
+  return 0;
+  }
+
+CAfile = ocsp_stapling;
+if(!(store = setup_verify(arg, CAfile, NULL)))
+  {
+  BIO_printf(arg, "error in cert setup\n");
+  return 0;
+  }
+
+/* No file of alternate certs, no options */
+if(OCSP_basic_verify(bs, NULL, store, 0) <= 0)
+  {
+  BIO_printf(arg, "Response Verify Failure\n");
+  ERR_print_errors(arg);
+  ret = 0;
+  }
+else
+  BIO_printf(arg, "Response verify OK\n");
+
+X509_STORE_free(store);
+return ret;
+}
+
+
 /*************************************************
 *         Start an OpenSSL TLS session           *
 *************************************************/
 
-int tls_start(int sock, SSL **ssl, SSL_CTX *ctx)
+int
+tls_start(int sock, SSL **ssl, SSL_CTX *ctx)
 {
 int rc;
 static const char *sid_ctx = "exim";
@@ -163,6 +257,13 @@ SSL_set_session_id_context(*ssl, sid_ctx, strlen(sid_ctx));
 SSL_set_fd (*ssl, sock);
 SSL_set_connect_state(*ssl);
 
+if (ocsp_stapling)
+  {
+  SSL_CTX_set_tlsext_status_cb(ctx, tls_client_stapling_cb);
+  SSL_CTX_set_tlsext_status_arg(ctx, BIO_new_fp(stdout, BIO_NOCLOSE));
+  SSL_set_tlsext_status_type(*ssl, TLSEXT_STATUSTYPE_ocsp);
+  }
+
 signal(SIGALRM, sigalrm_handler_flag);
 sigalrm_seen = 0;
 alarm(5);
@@ -322,6 +423,11 @@ if (certificate != NULL)
 /* Associate the parameters with the x509 credentials structure. */
 
 gnutls_certificate_set_dh_params(x509_cred, dh_params);
+
+/* set the CA info for server-cert verify */
+if (ocsp_stapling)
+  gnutls_certificate_set_x509_trust_file(x509_cred, ocsp_stapling,
+               GNUTLS_X509_FMT_PEM);
 }
 
 
@@ -363,13 +469,14 @@ return session;
 *                 Main Program                   *
 *************************************************/
 
-/* Usage: client
-          <IP address>
-          <port>
-          [<outgoing interface>]
-          [<cert file>]
-          [<key file>]
-*/
+const char * const HELP_MESSAGE = "\n\
+Usage: client\n\
+          <IP address>\n\
+          <port>\n\
+          [<outgoing interface>]\n\
+          [<cert file>]\n\
+          [<key file>]\n\
+\n";
 
 int main(int argc, char **argv)
 {
@@ -379,12 +486,14 @@ char *interface = NULL;
 char *address = NULL;
 char *certfile = NULL;
 char *keyfile = NULL;
+char *end = NULL;
 int argi = 1;
 int host_af, port, s_len, rc, sock, save_errno;
 int timeout = 1;
 int tls_active = 0;
 int sent_starttls = 0;
 int tls_on_connect = 0;
+long tmplong;
 
 #if HAVE_IPV6
 struct sockaddr_in6 s_in6;
@@ -405,19 +514,56 @@ unsigned char *inptr = inbuffer;
 
 while (argc >= argi + 1 && argv[argi][0] == '-')
   {
+  if (strcmp(argv[argi], "-help") == 0 ||
+      strcmp(argv[argi], "--help") == 0 ||
+      strcmp(argv[argi], "-h") == 0)
+    {
+    printf(HELP_MESSAGE);
+    exit(0);
+    }
   if (strcmp(argv[argi], "-tls-on-connect") == 0)
     {
     tls_on_connect = 1;
     argi++;
     }
+#ifdef HAVE_TLS
+  else if (strcmp(argv[argi], "-ocsp") == 0)
+    {
+    if (argc < ++argi + 1)
+      {
+      fprintf(stderr, "Missing required certificate file for ocsp option\n");
+      exit(1);
+      }
+    ocsp_stapling = argv[argi++];
+    }
+
+#endif
   else if (argv[argi][1] == 't' && isdigit(argv[argi][2]))
     {
-    timeout = atoi(argv[argi]+1);
+    tmplong = strtol(argv[argi]+2, &end, 10);
+    if (end == argv[argi]+2 || *end)
+      {
+      fprintf(stderr, "Failed to parse seconds from option <%s>\n",
+        argv[argi]);
+      exit(1);
+      }
+    if (tmplong > 10000L)
+      {
+      fprintf(stderr, "Unreasonably long wait of %d seconds requested\n",
+        tmplong);
+      exit(1);
+      }
+    if (tmplong < 0L)
+      {
+      fprintf(stderr, "Timeout must not be negative (%d)\n", tmplong);
+      exit(1);
+      }
+    timeout = (int) tmplong;
     argi++;
     }
   else
     {
-    printf("Unrecognized option %s\n", argv[argi]);
+    fprintf(stderr, "Unrecognized option %s\n", argv[argi]);
     exit(1);
     }
   }
@@ -426,7 +572,7 @@ while (argc >= argi + 1 && argv[argi][0] == '-')
 
 if (argc < argi+1)
   {
-  printf("No IP address given\n");
+  fprintf(stderr, "No IP address given\n");
   exit(1);
   }
 
@@ -437,7 +583,7 @@ host_af = (strchr(address, ':') != NULL)? AF_INET6 : AF_INET;
 
 if (argc < argi+1)
   {
-  printf("No port number given\n");
+  fprintf(stderr, "No port number given\n");
   exit(1);
   }
 
@@ -624,6 +770,10 @@ if (certfile != NULL) printf("Certificate file = %s\n", certfile);
 if (keyfile != NULL) printf("Key file = %s\n", keyfile);
 tls_init(certfile, keyfile);
 tls_session = tls_session_init();
+#ifdef HAVE_OCSP
+if (ocsp_stapling)
+  gnutls_ocsp_status_request_enable_client(tls_session, NULL, 0, NULL);
+#endif
 gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)sock);
 
 /* When the server asks for a certificate and the client does not have one,
@@ -658,6 +808,11 @@ if (tls_on_connect)
 
   if (!tls_active)
     printf("Failed to start TLS\n");
+  #if defined(HAVE_GNUTLS) && defined(HAVE_OCSP)
+  else if (  ocsp_stapling
+         && gnutls_ocsp_status_request_is_checked(tls_session, 0) == 0)
+    printf("Failed to verify certificate status\n");
+  #endif
   else
     printf("Succeeded in starting TLS\n");
   }
@@ -732,6 +887,9 @@ while (fgets(outbuffer, sizeof(outbuffer), stdin) != NULL)
       {
       if (lineptr[0] == '2')
         {
+int rc;
+       unsigned int verify;
+
         printf("Attempting to start TLS\n");
         fflush(stdout);
 
@@ -751,6 +909,44 @@ while (fgets(outbuffer, sizeof(outbuffer), stdin) != NULL)
           printf("Failed to start TLS\n");
           fflush(stdout);
           }
+       #ifdef HAVE_GNUTLS
+       else if (ocsp_stapling)
+         {
+         if ((rc= gnutls_certificate_verify_peers2(tls_session, &verify)) < 0)
+           {
+           printf("Failed to verify certificate: %s\n", gnutls_strerror(rc));
+           fflush(stdout);
+           }
+         else if (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
+           {
+           printf("Bad certificate\n");
+           fflush(stdout);
+           }
+         #ifdef HAVE_OCSP
+         else if (gnutls_ocsp_status_request_is_checked(tls_session, 0) == 0)
+           {
+           printf("Failed to verify certificate status\n");
+             {
+             gnutls_datum_t stapling;
+             gnutls_ocsp_resp_t resp;
+             gnutls_datum_t printed;
+             if (  (rc= gnutls_ocsp_status_request_get(tls_session, &stapling)) == 0
+                && (rc= gnutls_ocsp_resp_init(&resp)) == 0
+                && (rc= gnutls_ocsp_resp_import(resp, &stapling)) == 0
+                && (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0
+                )
+               {
+               fprintf(stderr, "%.4096s", printed.data);
+               gnutls_free(printed.data);
+               }
+             else
+               (void) fprintf(stderr,"ocsp decode: %s", gnutls_strerror(rc));
+             }
+           fflush(stdout);
+           }
+         #endif
+         }
+       #endif
         else
           printf("Succeeded in starting TLS\n");
         }