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
load_keys(const char * hostname,const char * CERT_FILE,const char * KEY_FILE)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
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)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
do_get(const char * url)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
main(int argc,char * const * argv)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 
main()285 int main ()
286 {
287   fprintf (stderr,
288            "SNI not supported by GnuTLS < 3.0\n");
289   return 0;
290 }
291 #endif
292