Home | History | Annotate | Download | only in https
      1 /*
      2   This file is part of libmicrohttpd
      3   Copyright (C) 2013 Christian Grothoff
      4 
      5   libmicrohttpd is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as published
      7   by the Free Software Foundation; either version 3, or (at your
      8   option) any later version.
      9 
     10   libmicrohttpd is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13   General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public License
     16   along with libmicrohttpd; see the file COPYING.  If not, write to the
     17   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
     18   Boston, MA 02111-1307, USA.
     19 */
     20 
     21 /**
     22  * @file test_https_sni.c
     23  * @brief  Testcase for libmicrohttpd HTTPS with SNI operations
     24  * @author Christian Grothoff
     25  */
     26 #include "platform.h"
     27 #include "microhttpd.h"
     28 #include <limits.h>
     29 #include <sys/stat.h>
     30 #include <curl/curl.h>
     31 #include <gcrypt.h>
     32 #include "tls_test_common.h"
     33 #include <gnutls/gnutls.h>
     34 
     35 /* This test only works with GnuTLS >= 3.0 */
     36 #if GNUTLS_VERSION_MAJOR >= 3
     37 
     38 #include <gnutls/abstract.h>
     39 
     40 /**
     41  * A hostname, server key and certificate.
     42  */
     43 struct Hosts
     44 {
     45   struct Hosts *next;
     46   const char *hostname;
     47   gnutls_pcert_st pcrt;
     48   gnutls_privkey_t key;
     49 };
     50 
     51 
     52 /**
     53  * Linked list of supported TLDs and respective certificates.
     54  */
     55 static struct Hosts *hosts;
     56 
     57 /* Load the certificate and the private key.
     58  * (This code is largely taken from GnuTLS).
     59  */
     60 static void
     61 load_keys(const char *hostname,
     62           const char *CERT_FILE,
     63           const char *KEY_FILE)
     64 {
     65   int ret;
     66   gnutls_datum_t data;
     67   struct Hosts *host;
     68 
     69   host = malloc (sizeof (struct Hosts));
     70   if (NULL == host)
     71     abort ();
     72   host->hostname = hostname;
     73   host->next = hosts;
     74   hosts = host;
     75 
     76   ret = gnutls_load_file (CERT_FILE, &data);
     77   if (ret < 0)
     78     {
     79       fprintf (stderr,
     80                "*** Error loading certificate file %s.\n",
     81                CERT_FILE);
     82       exit (1);
     83     }
     84   ret =
     85     gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
     86                                   0);
     87   if (ret < 0)
     88     {
     89       fprintf (stderr,
     90                "*** Error loading certificate file: %s\n",
     91                gnutls_strerror (ret));
     92       exit (1);
     93     }
     94   gnutls_free (data.data);
     95 
     96   ret = gnutls_load_file (KEY_FILE, &data);
     97   if (ret < 0)
     98     {
     99       fprintf (stderr,
    100                "*** Error loading key file %s.\n",
    101                KEY_FILE);
    102       exit (1);
    103     }
    104 
    105   gnutls_privkey_init (&host->key);
    106   ret =
    107     gnutls_privkey_import_x509_raw (host->key,
    108                                     &data, GNUTLS_X509_FMT_PEM,
    109                                     NULL, 0);
    110   if (ret < 0)
    111     {
    112       fprintf (stderr,
    113                "*** Error loading key file: %s\n",
    114                gnutls_strerror (ret));
    115       exit (1);
    116     }
    117   gnutls_free (data.data);
    118 }
    119 
    120 
    121 
    122 /**
    123  * @param session the session we are giving a cert for
    124  * @param req_ca_dn NULL on server side
    125  * @param nreqs length of req_ca_dn, and thus 0 on server side
    126  * @param pk_algos NULL on server side
    127  * @param pk_algos_length 0 on server side
    128  * @param pcert list of certificates (to be set)
    129  * @param pcert_length length of pcert (to be set)
    130  * @param pkey the private key (to be set)
    131  */
    132 static int
    133 sni_callback (gnutls_session_t session,
    134               const gnutls_datum_t* req_ca_dn,
    135               int nreqs,
    136               const gnutls_pk_algorithm_t* pk_algos,
    137               int pk_algos_length,
    138               gnutls_pcert_st** pcert,
    139               unsigned int *pcert_length,
    140               gnutls_privkey_t * pkey)
    141 {
    142   char name[256];
    143   size_t name_len;
    144   struct Hosts *host;
    145   unsigned int type;
    146 
    147   name_len = sizeof (name);
    148   if (GNUTLS_E_SUCCESS !=
    149       gnutls_server_name_get (session,
    150                               name,
    151                               &name_len,
    152                               &type,
    153                               0 /* index */))
    154     return -1;
    155   for (host = hosts; NULL != host; host = host->next)
    156     if (0 == strncmp (name, host->hostname, name_len))
    157       break;
    158   if (NULL == host)
    159     {
    160       fprintf (stderr,
    161                "Need certificate for %.*s\n",
    162                (int) name_len,
    163                name);
    164       return -1;
    165     }
    166 #if 0
    167   fprintf (stderr,
    168            "Returning certificate for %.*s\n",
    169            (int) name_len,
    170            name);
    171 #endif
    172   *pkey = host->key;
    173   *pcert_length = 1;
    174   *pcert = &host->pcrt;
    175   return 0;
    176 }
    177 
    178 
    179 /* perform a HTTP GET request via SSL/TLS */
    180 static int
    181 do_get (const char *url)
    182 {
    183   CURL *c;
    184   struct CBC cbc;
    185   CURLcode errornum;
    186   size_t len;
    187   struct curl_slist *dns_info;
    188 
    189   len = strlen (test_data);
    190   if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
    191     {
    192       fprintf (stderr, MHD_E_MEM);
    193       return -1;
    194     }
    195   cbc.size = len;
    196   cbc.pos = 0;
    197 
    198   c = curl_easy_init ();
    199 #if DEBUG_HTTPS_TEST
    200   curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
    201 #endif
    202   curl_easy_setopt (c, CURLOPT_URL, url);
    203   curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
    204   curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
    205   curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
    206   curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
    207   curl_easy_setopt (c, CURLOPT_FILE, &cbc);
    208 
    209   /* perform peer authentication */
    210   /* TODO merge into send_curl_req */
    211   curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
    212   curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 2);
    213   dns_info = curl_slist_append (NULL, "host1:4233:127.0.0.1");
    214   dns_info = curl_slist_append (dns_info, "host2:4233:127.0.0.1");
    215   curl_easy_setopt (c, CURLOPT_RESOLVE, dns_info);
    216   curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
    217 
    218   /* NOTE: use of CONNECTTIMEOUT without also
    219      setting NOSIGNAL results in really weird
    220      crashes on my system! */
    221   curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
    222   if (CURLE_OK != (errornum = curl_easy_perform (c)))
    223     {
    224       fprintf (stderr, "curl_easy_perform failed: `%s'\n",
    225                curl_easy_strerror (errornum));
    226       curl_easy_cleanup (c);
    227       free (cbc.buf);
    228       curl_slist_free_all (dns_info);
    229       return errornum;
    230     }
    231 
    232   curl_easy_cleanup (c);
    233   curl_slist_free_all (dns_info);
    234   if (memcmp (cbc.buf, test_data, len) != 0)
    235     {
    236       fprintf (stderr, "Error: local file & received file differ.\n");
    237       free (cbc.buf);
    238       return -1;
    239     }
    240 
    241   free (cbc.buf);
    242   return 0;
    243 }
    244 
    245 
    246 int
    247 main (int argc, char *const *argv)
    248 {
    249   unsigned int error_count = 0;
    250   struct MHD_Daemon *d;
    251 
    252   gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
    253 #ifdef GCRYCTL_INITIALIZATION_FINISHED
    254   gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
    255 #endif
    256   if (0 != curl_global_init (CURL_GLOBAL_ALL))
    257     {
    258       fprintf (stderr, "Error: %s\n", strerror (errno));
    259       return -1;
    260     }
    261   load_keys ("host1", ABS_SRCDIR "/host1.crt", ABS_SRCDIR "/host1.key");
    262   load_keys ("host2", ABS_SRCDIR "/host2.crt", ABS_SRCDIR "/host2.key");
    263   d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL | MHD_USE_DEBUG,
    264                         4233,
    265                         NULL, NULL,
    266                         &http_ahc, NULL,
    267                         MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
    268                         MHD_OPTION_END);
    269   if (d == NULL)
    270     {
    271       fprintf (stderr, MHD_E_SERVER_INIT);
    272       return -1;
    273     }
    274   error_count += do_get ("https://host1:4233/");
    275   error_count += do_get ("https://host2:4233/");
    276 
    277   MHD_stop_daemon (d);
    278   curl_global_cleanup ();
    279   return error_count != 0;
    280 }
    281 
    282 
    283 #else
    284 
    285 int main ()
    286 {
    287   fprintf (stderr,
    288            "SNI not supported by GnuTLS < 3.0\n");
    289   return 0;
    290 }
    291 #endif
    292