/* This file is part of libmicrohttpd Copyright (C) 2013 Christian Grothoff libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. libmicrohttpd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libmicrohttpd; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file test_https_sni.c * @brief Testcase for libmicrohttpd HTTPS with SNI operations * @author Christian Grothoff */ #include "platform.h" #include "microhttpd.h" #include <limits.h> #include <sys/stat.h> #include <curl/curl.h> #include <gcrypt.h> #include "tls_test_common.h" #include <gnutls/gnutls.h> /* This test only works with GnuTLS >= 3.0 */ #if GNUTLS_VERSION_MAJOR >= 3 #include <gnutls/abstract.h> /** * A hostname, server key and certificate. */ struct Hosts { struct Hosts *next; const char *hostname; gnutls_pcert_st pcrt; gnutls_privkey_t key; }; /** * Linked list of supported TLDs and respective certificates. */ static struct Hosts *hosts; /* Load the certificate and the private key. * (This code is largely taken from GnuTLS). */ static void load_keys(const char *hostname, const char *CERT_FILE, const char *KEY_FILE) { int ret; gnutls_datum_t data; struct Hosts *host; host = malloc (sizeof (struct Hosts)); if (NULL == host) abort (); host->hostname = hostname; host->next = hosts; hosts = host; ret = gnutls_load_file (CERT_FILE, &data); if (ret < 0) { fprintf (stderr, "*** Error loading certificate file %s.\n", CERT_FILE); exit (1); } ret = gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM, 0); if (ret < 0) { fprintf (stderr, "*** Error loading certificate file: %s\n", gnutls_strerror (ret)); exit (1); } gnutls_free (data.data); ret = gnutls_load_file (KEY_FILE, &data); if (ret < 0) { fprintf (stderr, "*** Error loading key file %s.\n", KEY_FILE); exit (1); } gnutls_privkey_init (&host->key); ret = gnutls_privkey_import_x509_raw (host->key, &data, GNUTLS_X509_FMT_PEM, NULL, 0); if (ret < 0) { fprintf (stderr, "*** Error loading key file: %s\n", gnutls_strerror (ret)); exit (1); } gnutls_free (data.data); } /** * @param session the session we are giving a cert for * @param req_ca_dn NULL on server side * @param nreqs length of req_ca_dn, and thus 0 on server side * @param pk_algos NULL on server side * @param pk_algos_length 0 on server side * @param pcert list of certificates (to be set) * @param pcert_length length of pcert (to be set) * @param pkey the private key (to be set) */ static int sni_callback (gnutls_session_t session, const gnutls_datum_t* req_ca_dn, int nreqs, const gnutls_pk_algorithm_t* pk_algos, int pk_algos_length, gnutls_pcert_st** pcert, unsigned int *pcert_length, gnutls_privkey_t * pkey) { char name[256]; size_t name_len; struct Hosts *host; unsigned int type; name_len = sizeof (name); if (GNUTLS_E_SUCCESS != gnutls_server_name_get (session, name, &name_len, &type, 0 /* index */)) return -1; for (host = hosts; NULL != host; host = host->next) if (0 == strncmp (name, host->hostname, name_len)) break; if (NULL == host) { fprintf (stderr, "Need certificate for %.*s\n", (int) name_len, name); return -1; } #if 0 fprintf (stderr, "Returning certificate for %.*s\n", (int) name_len, name); #endif *pkey = host->key; *pcert_length = 1; *pcert = &host->pcrt; return 0; } /* perform a HTTP GET request via SSL/TLS */ static int do_get (const char *url) { CURL *c; struct CBC cbc; CURLcode errornum; size_t len; struct curl_slist *dns_info; len = strlen (test_data); if (NULL == (cbc.buf = malloc (sizeof (char) * len))) { fprintf (stderr, MHD_E_MEM); return -1; } cbc.size = len; cbc.pos = 0; c = curl_easy_init (); #if DEBUG_HTTPS_TEST curl_easy_setopt (c, CURLOPT_VERBOSE, 1); #endif curl_easy_setopt (c, CURLOPT_URL, url); curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L); curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L); curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); curl_easy_setopt (c, CURLOPT_FILE, &cbc); /* perform peer authentication */ /* TODO merge into send_curl_req */ curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 2); dns_info = curl_slist_append (NULL, "host1:4233:127.0.0.1"); dns_info = curl_slist_append (dns_info, "host2:4233:127.0.0.1"); curl_easy_setopt (c, CURLOPT_RESOLVE, dns_info); curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); /* NOTE: use of CONNECTTIMEOUT without also setting NOSIGNAL results in really weird crashes on my system! */ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); if (CURLE_OK != (errornum = curl_easy_perform (c))) { fprintf (stderr, "curl_easy_perform failed: `%s'\n", curl_easy_strerror (errornum)); curl_easy_cleanup (c); free (cbc.buf); curl_slist_free_all (dns_info); return errornum; } curl_easy_cleanup (c); curl_slist_free_all (dns_info); if (memcmp (cbc.buf, test_data, len) != 0) { fprintf (stderr, "Error: local file & received file differ.\n"); free (cbc.buf); return -1; } free (cbc.buf); return 0; } int main (int argc, char *const *argv) { unsigned int error_count = 0; struct MHD_Daemon *d; gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); #ifdef GCRYCTL_INITIALIZATION_FINISHED gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); #endif if (0 != curl_global_init (CURL_GLOBAL_ALL)) { fprintf (stderr, "Error: %s\n", strerror (errno)); return -1; } load_keys ("host1", ABS_SRCDIR "/host1.crt", ABS_SRCDIR "/host1.key"); load_keys ("host2", ABS_SRCDIR "/host2.crt", ABS_SRCDIR "/host2.key"); d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL | MHD_USE_DEBUG, 4233, NULL, NULL, &http_ahc, NULL, MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback, MHD_OPTION_END); if (d == NULL) { fprintf (stderr, MHD_E_SERVER_INIT); return -1; } error_count += do_get ("https://host1:4233/"); error_count += do_get ("https://host2:4233/"); MHD_stop_daemon (d); curl_global_cleanup (); return error_count != 0; } #else int main () { fprintf (stderr, "SNI not supported by GnuTLS < 3.0\n"); return 0; } #endif