-/* $Cambridge: exim/test/src/client.c,v 1.1 2006/02/06 16:24:05 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 */
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 RSA_BITS 512
+# define DH_BITS 768
/* Local static variables for GNUTLS */
-static gnutls_rsa_params rsa_params = NULL;
static gnutls_dh_params dh_params = NULL;
static gnutls_certificate_credentials_t x509_cred = NULL;
GNUTLS_KX_RSA,
GNUTLS_KX_DHE_DSS,
GNUTLS_KX_DHE_RSA,
- GNUTLS_KX_RSA_EXPORT,
0 };
static int default_cipher_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 *
/****************************************************************************/
#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";
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);
static void
gnutls_error(uschar *prefix, int err)
{
-fprintf(stderr, "GnuTLS connection error:%s", prefix);
+fprintf(stderr, "GnuTLS connection error: %s:", prefix);
if (err != 0) fprintf(stderr, " %s", gnutls_strerror(err));
fprintf(stderr, "\n");
exit(1);
/*************************************************
-* Setup up RSA and DH parameters *
+* Setup up DH parameters *
*************************************************/
/* For the test suite, the parameters should always be available in the spool
directory. */
static void
-init_rsa_dh(void)
+init_dh(void)
{
int fd;
int ret;
/* Initialize the data structures for holding the parameters */
-ret = gnutls_rsa_params_init(&rsa_params);
-if (ret < 0) gnutls_error(US"init rsa_params", ret);
-
ret = gnutls_dh_params_init(&dh_params);
if (ret < 0) gnutls_error(US"init dh_params", ret);
/* Open the cache file for reading and if successful, read it and set up the
-parameters. If we can't set up the RSA parameters, assume that we are dealing
-with an old-style cache file that is in another format, and fall through to
-compute new values. However, if we correctly get RSA parameters, a failure to
-set up D-H parameters is treated as an error. */
+parameters. */
fd = open("aux-fixed/gnutls-params", O_RDONLY, 0);
if (fd < 0)
return gnutls_error(US"TLS cache read failed", 0);
(void)close(fd);
-ret = gnutls_rsa_params_import_pkcs1(rsa_params, &m, GNUTLS_X509_FMT_PEM);
-if (ret < 0) return gnutls_error(US"RSA params import", ret);
ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
if (ret < 0) return gnutls_error(US"DH params import", ret);
free(m.data);
rc = gnutls_global_init();
if (rc < 0) gnutls_error(US"gnutls_global_init", rc);
-/* Read RSA and D-H parameters from the cache file. */
+/* Read D-H parameters from the cache file. */
-init_rsa_dh();
+init_dh();
/* Create the credentials structure */
/* Associate the parameters with the x509 credentials structure. */
gnutls_certificate_set_dh_params(x509_cred, dh_params);
-gnutls_certificate_set_rsa_export_params(x509_cred, rsa_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);
}
* 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)
{
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;
while (argc >= argi + 1 && argv[argi][0] == '-')
{
+ if (strcmp(argv[argi], "-help") == 0 ||
+ strcmp(argv[argi], "--help") == 0 ||
+ strcmp(argv[argi], "-h") == 0)
+ {
+ puts(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 %ld seconds requested\n",
+ tmplong);
+ exit(1);
+ }
+ if (tmplong < 0L)
+ {
+ fprintf(stderr, "Timeout must not be negative (%ld)\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);
}
}
if (argc < argi+1)
{
- printf("No IP address given\n");
+ fprintf(stderr, "No IP address given\n");
exit(1);
}
if (argc < argi+1)
{
- printf("No port number given\n");
+ fprintf(stderr, "No port number given\n");
exit(1);
}
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,
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");
}
{
if (lineptr[0] == '2')
{
+int rc;
+ unsigned int verify;
+
printf("Attempting to start TLS\n");
fflush(stdout);
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");
}