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, ©Buffer);
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