aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2013-12-21 16:28:54 +0000
committerChristian Grothoff <christian@grothoff.org>2013-12-21 16:28:54 +0000
commit9889fd9eeec2c8d40f6a627720fb1afeac044ab0 (patch)
treefc91c30734fbc0113e139b0ea05ec499d300a198
parent00a9277f498260289d89f0de6783829b6391394b (diff)
downloadlibmicrohttpd-9889fd9eeec2c8d40f6a627720fb1afeac044ab0.tar.gz
libmicrohttpd-9889fd9eeec2c8d40f6a627720fb1afeac044ab0.zip
add support for SNI
-rw-r--r--ChangeLog2
-rw-r--r--doc/chapters/tlsauthentication.inc247
-rw-r--r--doc/libmicrohttpd-tutorial.texi18
-rw-r--r--doc/libmicrohttpd.texi16
-rw-r--r--src/examples/demo.c106
-rw-r--r--src/include/microhttpd.h19
-rw-r--r--src/microhttpd/daemon.c24
-rw-r--r--src/microhttpd/internal.h11
-rw-r--r--src/testcurl/https/Makefile.am10
-rw-r--r--src/testcurl/https/host1.crt15
-rw-r--r--src/testcurl/https/host1.key15
-rw-r--r--src/testcurl/https/host2.crt15
-rw-r--r--src/testcurl/https/host2.key15
-rw-r--r--src/testcurl/https/test_https_sni.c289
14 files changed, 700 insertions, 102 deletions
diff --git a/ChangeLog b/ChangeLog
index 89d85beb..64318861 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,7 @@
1Sat Dec 21 17:26:08 CET 2013 1Sat Dec 21 17:26:08 CET 2013
2 Fixed an issue with a missing argument in the postexample. 2 Fixed an issue with a missing argument in the postexample.
3 Fixed issue with bogus offset increment involving sendfile 3 Fixed issue with bogus offset increment involving sendfile
4 on GNU/Linux. 4 on GNU/Linux. Adding support for SNI. -CG
5 5
6Mon Dec 9 21:41:57 CET 2013 6Mon Dec 9 21:41:57 CET 2013
7 Fix for per-worker daemon pipes enabled with 7 Fix for per-worker daemon pipes enabled with
diff --git a/doc/chapters/tlsauthentication.inc b/doc/chapters/tlsauthentication.inc
index f945544e..c1b66735 100644
--- a/doc/chapters/tlsauthentication.inc
+++ b/doc/chapters/tlsauthentication.inc
@@ -1,6 +1,6 @@
1We left the basic authentication chapter with the unsatisfactory conclusion that 1We left the basic authentication chapter with the unsatisfactory conclusion that
2any traffic, including the credentials, could be intercepted by anyone between 2any traffic, including the credentials, could be intercepted by anyone between
3the browser client and the server. Protecting the data while it is sent over 3the browser client and the server. Protecting the data while it is sent over
4unsecured lines will be the goal of this chapter. 4unsecured lines will be the goal of this chapter.
5 5
6Since version 0.4, the @emph{MHD} library includes support for encrypting the 6Since version 0.4, the @emph{MHD} library includes support for encrypting the
@@ -23,7 +23,7 @@ this tutorial, we will be content with a 1024 bit key:
23 23
24In addition to the key, a certificate describing the server in human readable tokens 24In addition to the key, a certificate describing the server in human readable tokens
25is also needed. This certificate will be attested with our aforementioned key. In this way, 25is also needed. This certificate will be attested with our aforementioned key. In this way,
26we obtain a self-signed certificate, valid for one year. 26we obtain a self-signed certificate, valid for one year.
27 27
28@verbatim 28@verbatim
29> openssl req -days 365 -out server.pem -new -x509 -key server.key 29> openssl req -days 365 -out server.pem -new -x509 -key server.key
@@ -38,7 +38,7 @@ any visitor can make sure the server's identity is real.
38 38
39Whether the server's certificate is signed by us or a third party, once it has been accepted 39Whether the server's certificate is signed by us or a third party, once it has been accepted
40by the client, both sides will be communicating over encrypted channels. From this point on, 40by the client, both sides will be communicating over encrypted channels. From this point on,
41it is the client's turn to authenticate itself. But this has already been implemented in the basic 41it is the client's turn to authenticate itself. But this has already been implemented in the basic
42authentication scheme. 42authentication scheme.
43 43
44 44
@@ -65,12 +65,12 @@ main ()
65@end verbatim 65@end verbatim
66@noindent 66@noindent
67 67
68and then we point the @emph{MHD} daemon to it upon initalization. 68and then we point the @emph{MHD} daemon to it upon initalization.
69@verbatim 69@verbatim
70 70
71 daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL, 71 daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
72 PORT, NULL, NULL, 72 PORT, NULL, NULL,
73 &answer_to_connection, NULL, 73 &answer_to_connection, NULL,
74 MHD_OPTION_HTTPS_MEM_KEY, key_pem, 74 MHD_OPTION_HTTPS_MEM_KEY, key_pem,
75 MHD_OPTION_HTTPS_MEM_CERT, cert_pem, 75 MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
76 MHD_OPTION_END); 76 MHD_OPTION_END);
@@ -78,10 +78,10 @@ and then we point the @emph{MHD} daemon to it upon initalization.
78 if (NULL == daemon) 78 if (NULL == daemon)
79 { 79 {
80 printf ("%s\n", cert_pem); 80 printf ("%s\n", cert_pem);
81 81
82 free (key_pem); 82 free (key_pem);
83 free (cert_pem); 83 free (cert_pem);
84 84
85 return 1; 85 return 1;
86 } 86 }
87@end verbatim 87@end verbatim
@@ -96,7 +96,7 @@ The rest consists of little new besides some additional memory cleanups.
96 MHD_stop_daemon (daemon); 96 MHD_stop_daemon (daemon);
97 free (key_pem); 97 free (key_pem);
98 free (cert_pem); 98 free (cert_pem);
99 99
100 return 0; 100 return 0;
101} 101}
102@end verbatim 102@end verbatim
@@ -110,18 +110,18 @@ The rather unexciting file loader can be found in the complete example @code{tls
110@itemize @bullet 110@itemize @bullet
111@item 111@item
112While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume 112While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume
113standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type 113standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type
114@code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to 114@code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to
115handle the answer properly. 115handle the answer properly.
116 116
117@item 117@item
118The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the 118The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the
119certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured) 119certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured)
120that they should not accept certificates of unknown origin. 120that they should not accept certificates of unknown origin.
121 121
122@item 122@item
123The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to 123The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to
124hardcode certificates in embedded devices. 124hardcode certificates in embedded devices.
125 125
126@item 126@item
127The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists 127The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
@@ -135,12 +135,12 @@ both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}.
135You can also use MHD to authenticate the client via SSL/TLS certificates 135You can also use MHD to authenticate the client via SSL/TLS certificates
136(as an alternative to using the password-based Basic or Digest authentication). 136(as an alternative to using the password-based Basic or Digest authentication).
137To do this, you will need to link your application against @emph{gnutls}. 137To do this, you will need to link your application against @emph{gnutls}.
138Next, when you start the MHD daemon, you must specify the root CA that you're 138Next, when you start the MHD daemon, you must specify the root CA that you're
139willing to trust: 139willing to trust:
140@verbatim 140@verbatim
141 daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL, 141 daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
142 PORT, NULL, NULL, 142 PORT, NULL, NULL,
143 &answer_to_connection, NULL, 143 &answer_to_connection, NULL,
144 MHD_OPTION_HTTPS_MEM_KEY, key_pem, 144 MHD_OPTION_HTTPS_MEM_KEY, key_pem,
145 MHD_OPTION_HTTPS_MEM_CERT, cert_pem, 145 MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
146 MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem, 146 MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem,
@@ -159,7 +159,7 @@ obtain the raw GnuTLS session handle from @emph{MHD} using
159gnutls_session_t tls_session; 159gnutls_session_t tls_session;
160union MHD_ConnectionInfo *ci; 160union MHD_ConnectionInfo *ci;
161 161
162ci = MHD_get_connection_info (connection, 162ci = MHD_get_connection_info (connection,
163 MHD_CONNECTION_INFO_GNUTLS_SESSION); 163 MHD_CONNECTION_INFO_GNUTLS_SESSION);
164tls_session = ci->tls_session; 164tls_session = ci->tls_session;
165@end verbatim 165@end verbatim
@@ -172,31 +172,31 @@ You can then extract the client certificate:
172 * 172 *
173 * @param tls_session the TLS session 173 * @param tls_session the TLS session
174 * @return NULL if no valid client certificate could be found, a pointer 174 * @return NULL if no valid client certificate could be found, a pointer
175 * to the certificate if found 175 * to the certificate if found
176 */ 176 */
177static gnutls_x509_crt_t 177static gnutls_x509_crt_t
178get_client_certificate (gnutls_session_t tls_session) 178get_client_certificate (gnutls_session_t tls_session)
179{ 179{
180 unsigned int listsize; 180 unsigned int listsize;
181 const gnutls_datum_t * pcert; 181 const gnutls_datum_t * pcert;
182 gnutls_certificate_status_t client_cert_status; 182 gnutls_certificate_status_t client_cert_status;
183 gnutls_x509_crt_t client_cert; 183 gnutls_x509_crt_t client_cert;
184 184
185 if (tls_session == NULL) 185 if (tls_session == NULL)
186 return NULL; 186 return NULL;
187 if (gnutls_certificate_verify_peers2(tls_session, 187 if (gnutls_certificate_verify_peers2(tls_session,
188 &client_cert_status)) 188 &client_cert_status))
189 return NULL; 189 return NULL;
190 pcert = gnutls_certificate_get_peers(tls_session, 190 pcert = gnutls_certificate_get_peers(tls_session,
191 &listsize); 191 &listsize);
192 if ( (pcert == NULL) || 192 if ( (pcert == NULL) ||
193 (listsize == 0)) 193 (listsize == 0))
194 { 194 {
195 fprintf (stderr, 195 fprintf (stderr,
196 "Failed to retrieve client certificate chain\n"); 196 "Failed to retrieve client certificate chain\n");
197 return NULL; 197 return NULL;
198 } 198 }
199 if (gnutls_x509_crt_init(&client_cert)) 199 if (gnutls_x509_crt_init(&client_cert))
200 { 200 {
201 fprintf (stderr, 201 fprintf (stderr,
202 "Failed to initialize client certificate\n"); 202 "Failed to initialize client certificate\n");
@@ -204,15 +204,15 @@ get_client_certificate (gnutls_session_t tls_session)
204 } 204 }
205 /* Note that by passing values between 0 and listsize here, you 205 /* Note that by passing values between 0 and listsize here, you
206 can get access to the CA's certs */ 206 can get access to the CA's certs */
207 if (gnutls_x509_crt_import(client_cert, 207 if (gnutls_x509_crt_import(client_cert,
208 &pcert[0], 208 &pcert[0],
209 GNUTLS_X509_FMT_DER)) 209 GNUTLS_X509_FMT_DER))
210 { 210 {
211 fprintf (stderr, 211 fprintf (stderr,
212 "Failed to import client certificate\n"); 212 "Failed to import client certificate\n");
213 gnutls_x509_crt_deinit(client_cert); 213 gnutls_x509_crt_deinit(client_cert);
214 return NULL; 214 return NULL;
215 } 215 }
216 return client_cert; 216 return client_cert;
217} 217}
218@end verbatim 218@end verbatim
@@ -229,15 +229,15 @@ and alternative names:
229 * to the dn if found 229 * to the dn if found
230 */ 230 */
231char * 231char *
232cert_auth_get_dn(gnutls_x509_crt_c client_cert) 232cert_auth_get_dn(gnutls_x509_crt_c client_cert)
233{ 233{
234 char* buf; 234 char* buf;
235 size_t lbuf; 235 size_t lbuf;
236 236
237 lbuf = 0; 237 lbuf = 0;
238 gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf); 238 gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
239 buf = malloc(lbuf); 239 buf = malloc(lbuf);
240 if (buf == NULL) 240 if (buf == NULL)
241 { 241 {
242 fprintf (stderr, 242 fprintf (stderr,
243 "Failed to allocate memory for certificate dn\n"); 243 "Failed to allocate memory for certificate dn\n");
@@ -260,8 +260,8 @@ cert_auth_get_dn(gnutls_x509_crt_c client_cert)
260 */ 260 */
261char * 261char *
262MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert, 262MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
263 int nametype, 263 int nametype,
264 unsigned int index) 264 unsigned int index)
265{ 265{
266 char* buf; 266 char* buf;
267 size_t lbuf; 267 size_t lbuf;
@@ -271,7 +271,7 @@ MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
271 int result; 271 int result;
272 272
273 subseq = 0; 273 subseq = 0;
274 for (seq=0;;seq++) 274 for (seq=0;;seq++)
275 { 275 {
276 lbuf = 0; 276 lbuf = 0;
277 result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf, 277 result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
@@ -280,21 +280,21 @@ MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
280 return NULL; 280 return NULL;
281 if (nametype != (int) type) 281 if (nametype != (int) type)
282 continue; 282 continue;
283 if (subseq == index) 283 if (subseq == index)
284 break; 284 break;
285 subseq++; 285 subseq++;
286 } 286 }
287 buf = malloc(lbuf); 287 buf = malloc(lbuf);
288 if (buf == NULL) 288 if (buf == NULL)
289 { 289 {
290 fprintf (stderr, 290 fprintf (stderr,
291 "Failed to allocate memory for certificate alt name\n"); 291 "Failed to allocate memory for certificate alt name\n");
292 return NULL; 292 return NULL;
293 } 293 }
294 result = gnutls_x509_crt_get_subject_alt_name2(client_cert, 294 result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
295 seq, 295 seq,
296 buf, 296 buf,
297 &lbuf, 297 &lbuf,
298 NULL, NULL); 298 NULL, NULL);
299 if (result != nametype) 299 if (result != nametype)
300 { 300 {
@@ -315,3 +315,174 @@ certificate:
315gnutls_x509_crt_deinit (client_cert); 315gnutls_x509_crt_deinit (client_cert);
316@end verbatim 316@end verbatim
317 317
318
319
320@heading Using TLS Server Name Indication (SNI)
321
322SNI enables hosting multiple domains under one IP address with TLS. So
323SNI is the TLS-equivalent of virtual hosting. To use SNI with MHD, you
324need at least GnuTLS 3.0. The main change compared to the simple hosting
325of one domain is that you need to provide a callback instead of the key
326and certificate. For example, when you start the MHD daemon, you could
327do this:
328@verbatim
329 daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
330 PORT, NULL, NULL,
331 &answer_to_connection, NULL,
332 MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
333 MHD_OPTION_END);
334@end verbatim
335Here, @code{sni_callback} is the name of a function that you will have to
336implement to retrieve the X.509 certificate for an incoming connection.
337The callback has type @code{gnutls_certificate_retrieve_function2} and
338is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2}
339as follows:
340
341@deftypefn {Function Pointer} int {*gnutls_certificate_retrieve_function2} (gnutls_session_t, 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)
342
343@table @var
344@item req_ca_cert
345is only used in X.509 certificates. Contains a list with the CA names that the server considers trusted. Normally we should send a certificate that is signed by one of these CAs. These names are DER encoded. To get a more meaningful value use the function @code{gnutls_x509_rdn_get()}.
346
347@item pk_algos
348contains a list with server’s acceptable signature algorithms. The certificate returned should support the server’s given algorithms.
349
350@item pcert
351should contain a single certificate and public or a list of them.
352
353@item pcert_length
354is the size of the previous list.
355
356@item pkey
357is the private key.
358@end table
359@end deftypefn
360
361A possible implementation of this callback would look like this:
362
363@verbatim
364struct Hosts
365{
366 struct Hosts *next;
367 const char *hostname;
368 gnutls_pcert_st pcrt;
369 gnutls_privkey_t key;
370};
371
372static struct Hosts *hosts;
373
374int
375sni_callback (gnutls_session_t session,
376 const gnutls_datum_t* req_ca_dn,
377 int nreqs,
378 const gnutls_pk_algorithm_t* pk_algos,
379 int pk_algos_length,
380 gnutls_pcert_st** pcert,
381 unsigned int *pcert_length,
382 gnutls_privkey_t * pkey)
383{
384 char name[256];
385 size_t name_len;
386 struct Hosts *host;
387 unsigned int type;
388
389 name_len = sizeof (name);
390 if (GNUTLS_E_SUCCESS !=
391 gnutls_server_name_get (session,
392 name,
393 &name_len,
394 &type,
395 0 /* index */))
396 return -1;
397 for (host = hosts; NULL != host; host = host->next)
398 if (0 == strncmp (name, host->hostname, name_len))
399 break;
400 if (NULL == host)
401 {
402 fprintf (stderr,
403 "Need certificate for %.*s\n",
404 (int) name_len,
405 name);
406 return -1;
407 }
408 fprintf (stderr,
409 "Returning certificate for %.*s\n",
410 (int) name_len,
411 name);
412 *pkey = host->key;
413 *pcert_length = 1;
414 *pcert = &host->pcrt;
415 return 0;
416}
417@end verbatim
418
419Note that MHD cannot offer passing a closure or any other additional information
420to this callback, as the GnuTLS API unfortunately does not permit this at this
421point.
422
423The @code{hosts} list can be initialized by loading the private keys and X.509
424certificats from disk as follows:
425
426@verbatim
427static void
428load_keys(const char *hostname,
429 const char *CERT_FILE,
430 const char *KEY_FILE)
431{
432 int ret;
433 gnutls_datum_t data;
434 struct Hosts *host;
435
436 host = malloc (sizeof (struct Hosts));
437 host->hostname = hostname;
438 host->next = hosts;
439 hosts = host;
440
441 ret = gnutls_load_file (CERT_FILE, &data);
442 if (ret < 0)
443 {
444 fprintf (stderr,
445 "*** Error loading certificate file %s.\n",
446 CERT_FILE);
447 exit(1);
448 }
449 ret =
450 gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
451 0);
452 if (ret < 0)
453 {
454 fprintf(stderr,
455 "*** Error loading certificate file: %s\n",
456 gnutls_strerror (ret));
457 exit(1);
458 }
459 gnutls_free (data.data);
460
461 ret = gnutls_load_file (KEY_FILE, &data);
462 if (ret < 0)
463 {
464 fprintf (stderr,
465 "*** Error loading key file %s.\n",
466 KEY_FILE);
467 exit(1);
468 }
469
470 gnutls_privkey_init (&host->key);
471 ret =
472 gnutls_privkey_import_x509_raw (host->key,
473 &data, GNUTLS_X509_FMT_PEM,
474 NULL, 0);
475 if (ret < 0)
476 {
477 fprintf (stderr,
478 "*** Error loading key file: %s\n",
479 gnutls_strerror (ret));
480 exit(1);
481 }
482 gnutls_free (data.data);
483}
484@end verbatim
485
486The code above was largely lifted from GnuTLS. You can find other
487methods for initializing certificates and keys in the GnuTLS manual
488and source code.
diff --git a/doc/libmicrohttpd-tutorial.texi b/doc/libmicrohttpd-tutorial.texi
index 2509247f..104f00c3 100644
--- a/doc/libmicrohttpd-tutorial.texi
+++ b/doc/libmicrohttpd-tutorial.texi
@@ -1,10 +1,10 @@
1\input texinfo @c -*-texinfo-*- 1\input texinfo @c -*-texinfo-*-
2@finalout 2@finalout
3@setfilename libmicrohttpd-tutorial.info 3@setfilename libmicrohttpd-tutorial.info
4@set UPDATED 17 July 2012 4@set UPDATED 17 November 2013
5@set UPDATED-MONTH July 2012 5@set UPDATED-MONTH November 2013
6@set EDITION 0.9.22 6@set EDITION 0.9.23
7@set VERSION 0.9.22 7@set VERSION 0.9.23
8@settitle A tutorial for GNU libmicrohttpd 8@settitle A tutorial for GNU libmicrohttpd
9@c Unify all the indices into concept index. 9@c Unify all the indices into concept index.
10@syncodeindex fn cp 10@syncodeindex fn cp
@@ -20,11 +20,11 @@
20 20
21@copying 21@copying
22This tutorial documents GNU libmicrohttpd version @value{VERSION}, last 22This tutorial documents GNU libmicrohttpd version @value{VERSION}, last
23updated @value{UPDATED}. 23updated @value{UPDATED}.
24 24
25Copyright (c) 2008 Sebastian Gerhardt. 25Copyright (c) 2008 Sebastian Gerhardt.
26 26
27Copyright (c) 2010, 2011, 2012 Christian Grothoff. 27Copyright (c) 2010, 2011, 2012, 2013 Christian Grothoff.
28@quotation 28@quotation
29Permission is granted to copy, distribute and/or modify this document 29Permission is granted to copy, distribute and/or modify this document
30under the terms of the GNU Free Documentation License, Version 1.3 30under the terms of the GNU Free Documentation License, Version 1.3
@@ -56,7 +56,7 @@ Free Documentation License".
56@node Top 56@node Top
57@top A Tutorial for GNU libmicrohttpd 57@top A Tutorial for GNU libmicrohttpd
58@insertcopying 58@insertcopying
59@end ifnottex 59@end ifnottex
60 60
61@menu 61@menu
62* Introduction:: 62* Introduction::
@@ -97,8 +97,8 @@ Free Documentation License".
97@chapter Processing POST data 97@chapter Processing POST data
98@include chapters/processingpost.inc 98@include chapters/processingpost.inc
99 99
100@node Improved processing of POST data 100@node Improved processing of POST data
101@chapter Improved processing of POST data 101@chapter Improved processing of POST data
102@include chapters/largerpost.inc 102@include chapters/largerpost.inc
103 103
104@node Session management 104@node Session management
diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi
index 6780ef68..c27d36a4 100644
--- a/doc/libmicrohttpd.texi
+++ b/doc/libmicrohttpd.texi
@@ -701,6 +701,22 @@ are acceptable for the application. The string is passed
701unchanged to gnutls_priority_init. If this option is not 701unchanged to gnutls_priority_init. If this option is not
702specified, ``NORMAL'' is used. 702specified, ``NORMAL'' is used.
703 703
704@item MHD_OPTION_HTTPS_CERT_CALLBACK
705@cindex SSL
706@cindex TLS
707@cindex SNI
708Use a callback to determine which X.509 certificate should be used for
709a given HTTPS connection. This option should be followed by a
710argument of type "gnutls_certificate_retrieve_function2 *". This
711option provides an alternative to MHD_OPTION_HTTPS_MEM_KEY and
712MHD_OPTION_HTTPS_MEM_CERT. You must use this version if multiple
713domains are to be hosted at the same IP address using TLS's Server
714Name Indication (SNI) extension. In this case, the callback is
715expected to select the correct certificate based on the SNI
716information provided. The callback is expected to access the SNI data
717using gnutls_server_name_get(). Using this option requires GnuTLS 3.0
718or higher.
719
704@item MHD_OPTION_DIGEST_AUTH_RANDOM 720@item MHD_OPTION_DIGEST_AUTH_RANDOM
705@cindex digest auth 721@cindex digest auth
706@cindex random 722@cindex random
diff --git a/src/examples/demo.c b/src/examples/demo.c
index 4e7c5d2a..5ce1a244 100644
--- a/src/examples/demo.c
+++ b/src/examples/demo.c
@@ -21,7 +21,7 @@
21 * @file demo.c 21 * @file demo.c
22 * @brief complex demonstration site: create directory index, offer 22 * @brief complex demonstration site: create directory index, offer
23 * upload via form and HTTP POST, download with mime type detection 23 * upload via form and HTTP POST, download with mime type detection
24 * and error reporting (403, etc.) --- and all of this with 24 * and error reporting (403, etc.) --- and all of this with
25 * high-performance settings (large buffers, thread pool). 25 * high-performance settings (large buffers, thread pool).
26 * If you want to benchmark MHD, this code should be used to 26 * If you want to benchmark MHD, this code should be used to
27 * run tests against. Note that the number of threads may need 27 * run tests against. Note that the number of threads may need
@@ -222,7 +222,7 @@ struct ResponseDataContext
222 * Response data string. 222 * Response data string.
223 */ 223 */
224 char *buf; 224 char *buf;
225 225
226 /** 226 /**
227 * Number of bytes allocated for 'buf'. 227 * Number of bytes allocated for 'buf'.
228 */ 228 */
@@ -253,12 +253,12 @@ list_directory (struct ResponseDataContext *rdc,
253 struct dirent *de; 253 struct dirent *de;
254 254
255 if (NULL == (dir = opendir (dirname))) 255 if (NULL == (dir = opendir (dirname)))
256 return MHD_NO; 256 return MHD_NO;
257 while (NULL != (de = readdir (dir))) 257 while (NULL != (de = readdir (dir)))
258 { 258 {
259 if ('.' == de->d_name[0]) 259 if ('.' == de->d_name[0])
260 continue; 260 continue;
261 if (sizeof (fullname) <= 261 if (sizeof (fullname) <=
262 snprintf (fullname, sizeof (fullname), 262 snprintf (fullname, sizeof (fullname),
263 "%s/%s", 263 "%s/%s",
264 dirname, de->d_name)) 264 dirname, de->d_name))
@@ -278,7 +278,7 @@ list_directory (struct ResponseDataContext *rdc,
278 break; /* out of memory */ 278 break; /* out of memory */
279 rdc->buf = r; 279 rdc->buf = r;
280 } 280 }
281 rdc->off += snprintf (&rdc->buf[rdc->off], 281 rdc->off += snprintf (&rdc->buf[rdc->off],
282 rdc->buf_len - rdc->off, 282 rdc->buf_len - rdc->off,
283 "<li><a href=\"/%s\">%s</a></li>\n", 283 "<li><a href=\"/%s\">%s</a></li>\n",
284 fullname, 284 fullname,
@@ -305,11 +305,11 @@ update_directory ()
305 char dir_name[128]; 305 char dir_name[128];
306 struct stat sbuf; 306 struct stat sbuf;
307 307
308 rdc.buf_len = initial_allocation; 308 rdc.buf_len = initial_allocation;
309 if (NULL == (rdc.buf = malloc (rdc.buf_len))) 309 if (NULL == (rdc.buf = malloc (rdc.buf_len)))
310 { 310 {
311 update_cached_response (NULL); 311 update_cached_response (NULL);
312 return; 312 return;
313 } 313 }
314 rdc.off = snprintf (rdc.buf, rdc.buf_len, 314 rdc.off = snprintf (rdc.buf, rdc.buf_len,
315 "%s", 315 "%s",
@@ -342,7 +342,7 @@ update_directory ()
342 rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, 342 rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
343 "<h3>%s</h3>\n", 343 "<h3>%s</h3>\n",
344 category); 344 category);
345 345
346 if (MHD_NO == list_directory (&rdc, dir_name)) 346 if (MHD_NO == list_directory (&rdc, dir_name))
347 { 347 {
348 free (rdc.buf); 348 free (rdc.buf);
@@ -352,7 +352,7 @@ update_directory ()
352 } 352 }
353 } 353 }
354 /* we ensured always +1k room, filenames are ~256 bytes, 354 /* we ensured always +1k room, filenames are ~256 bytes,
355 so there is always still enough space for the footer 355 so there is always still enough space for the footer
356 without need for a final reallocation check. */ 356 without need for a final reallocation check. */
357 rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, 357 rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
358 "%s", 358 "%s",
@@ -427,7 +427,7 @@ do_append (char **ret,
427{ 427{
428 char *buf; 428 char *buf;
429 size_t old_len; 429 size_t old_len;
430 430
431 if (NULL == *ret) 431 if (NULL == *ret)
432 old_len = 0; 432 old_len = 0;
433 else 433 else
@@ -471,8 +471,8 @@ process_upload_data (void *cls,
471 const char *filename, 471 const char *filename,
472 const char *content_type, 472 const char *content_type,
473 const char *transfer_encoding, 473 const char *transfer_encoding,
474 const char *data, 474 const char *data,
475 uint64_t off, 475 uint64_t off,
476 size_t size) 476 size_t size)
477{ 477{
478 struct UploadContext *uc = cls; 478 struct UploadContext *uc = cls;
@@ -484,10 +484,10 @@ process_upload_data (void *cls,
484 return do_append (&uc->language, data, size); 484 return do_append (&uc->language, data, size);
485 if (0 != strcmp (key, "upload")) 485 if (0 != strcmp (key, "upload"))
486 { 486 {
487 fprintf (stderr, 487 fprintf (stderr,
488 "Ignoring unexpected form value `%s'\n", 488 "Ignoring unexpected form value `%s'\n",
489 key); 489 key);
490 return MHD_YES; /* ignore */ 490 return MHD_YES; /* ignore */
491 } 491 }
492 if (NULL == filename) 492 if (NULL == filename)
493 { 493 {
@@ -497,7 +497,7 @@ process_upload_data (void *cls,
497 if ( (NULL == uc->category) || 497 if ( (NULL == uc->category) ||
498 (NULL == uc->language) ) 498 (NULL == uc->language) )
499 { 499 {
500 fprintf (stderr, 500 fprintf (stderr,
501 "Missing form data for upload `%s'\n", 501 "Missing form data for upload `%s'\n",
502 filename); 502 filename);
503 uc->response = request_refused_response; 503 uc->response = request_refused_response;
@@ -523,8 +523,8 @@ process_upload_data (void *cls,
523 snprintf (fn, sizeof (fn), 523 snprintf (fn, sizeof (fn),
524 "%s/%s", 524 "%s/%s",
525 uc->language, 525 uc->language,
526 uc->category); 526 uc->category);
527#ifdef WINDOWS 527#ifdef WINDOWS
528 (void) mkdir (fn); 528 (void) mkdir (fn);
529#else 529#else
530 (void) mkdir (fn, S_IRWXU); 530 (void) mkdir (fn, S_IRWXU);
@@ -534,12 +534,12 @@ process_upload_data (void *cls,
534 "%s/%s/%s", 534 "%s/%s/%s",
535 uc->language, 535 uc->language,
536 uc->category, 536 uc->category,
537 filename); 537 filename);
538 for (i=strlen (fn)-1;i>=0;i--) 538 for (i=strlen (fn)-1;i>=0;i--)
539 if (! isprint ((int) fn[i])) 539 if (! isprint ((int) fn[i]))
540 fn[i] = '_'; 540 fn[i] = '_';
541 uc->fd = open (fn, 541 uc->fd = open (fn,
542 O_CREAT | O_EXCL 542 O_CREAT | O_EXCL
543#if O_LARGEFILE 543#if O_LARGEFILE
544 | O_LARGEFILE 544 | O_LARGEFILE
545#endif 545#endif
@@ -547,20 +547,20 @@ process_upload_data (void *cls,
547 S_IRUSR | S_IWUSR); 547 S_IRUSR | S_IWUSR);
548 if (-1 == uc->fd) 548 if (-1 == uc->fd)
549 { 549 {
550 fprintf (stderr, 550 fprintf (stderr,
551 "Error opening file `%s' for upload: %s\n", 551 "Error opening file `%s' for upload: %s\n",
552 fn, 552 fn,
553 strerror (errno)); 553 strerror (errno));
554 uc->response = request_refused_response; 554 uc->response = request_refused_response;
555 return MHD_NO; 555 return MHD_NO;
556 } 556 }
557 uc->filename = strdup (fn); 557 uc->filename = strdup (fn);
558 } 558 }
559 if ( (0 != size) && 559 if ( (0 != size) &&
560 (size != write (uc->fd, data, size)) ) 560 (size != write (uc->fd, data, size)) )
561 { 561 {
562 /* write failed; likely: disk full */ 562 /* write failed; likely: disk full */
563 fprintf (stderr, 563 fprintf (stderr,
564 "Error writing to file `%s': %s\n", 564 "Error writing to file `%s': %s\n",
565 uc->filename, 565 uc->filename,
566 strerror (errno)); 566 strerror (errno));
@@ -573,7 +573,7 @@ process_upload_data (void *cls,
573 free (uc->filename); 573 free (uc->filename);
574 uc->filename = NULL; 574 uc->filename = NULL;
575 } 575 }
576 return MHD_NO; 576 return MHD_NO;
577 } 577 }
578 return MHD_YES; 578 return MHD_YES;
579} 579}
@@ -610,13 +610,13 @@ response_completed_callback (void *cls,
610 (void) close (uc->fd); 610 (void) close (uc->fd);
611 if (NULL != uc->filename) 611 if (NULL != uc->filename)
612 { 612 {
613 fprintf (stderr, 613 fprintf (stderr,
614 "Upload of file `%s' failed (incomplete or aborted), removing file.\n", 614 "Upload of file `%s' failed (incomplete or aborted), removing file.\n",
615 uc->filename); 615 uc->filename);
616 (void) unlink (uc->filename); 616 (void) unlink (uc->filename);
617 } 617 }
618 } 618 }
619 if (NULL != uc->filename) 619 if (NULL != uc->filename)
620 free (uc->filename); 620 free (uc->filename);
621 free (uc); 621 free (uc);
622} 622}
@@ -624,7 +624,7 @@ response_completed_callback (void *cls,
624 624
625/** 625/**
626 * Return the current directory listing. 626 * Return the current directory listing.
627 * 627 *
628 * @param connection connection to return the directory for 628 * @param connection connection to return the directory for
629 * @return MHD_YES on success, MHD_NO on error 629 * @return MHD_YES on success, MHD_NO on error
630 */ 630 */
@@ -635,12 +635,12 @@ return_directory_response (struct MHD_Connection *connection)
635 635
636 (void) pthread_mutex_lock (&mutex); 636 (void) pthread_mutex_lock (&mutex);
637 if (NULL == cached_directory_response) 637 if (NULL == cached_directory_response)
638 ret = MHD_queue_response (connection, 638 ret = MHD_queue_response (connection,
639 MHD_HTTP_INTERNAL_SERVER_ERROR, 639 MHD_HTTP_INTERNAL_SERVER_ERROR,
640 internal_error_response); 640 internal_error_response);
641 else 641 else
642 ret = MHD_queue_response (connection, 642 ret = MHD_queue_response (connection,
643 MHD_HTTP_OK, 643 MHD_HTTP_OK,
644 cached_directory_response); 644 cached_directory_response);
645 (void) pthread_mutex_unlock (&mutex); 645 (void) pthread_mutex_unlock (&mutex);
646 return ret; 646 return ret;
@@ -657,7 +657,7 @@ return_directory_response (struct MHD_Connection *connection)
657 * @param version HTTP version 657 * @param version HTTP version
658 * @param upload_data data from upload (PUT/POST) 658 * @param upload_data data from upload (PUT/POST)
659 * @param upload_data_size number of bytes in "upload_data" 659 * @param upload_data_size number of bytes in "upload_data"
660 * @param ptr our context 660 * @param ptr our context
661 * @return MHD_YES on success, MHD_NO to drop connection 661 * @return MHD_YES on success, MHD_NO to drop connection
662 */ 662 */
663static int 663static int
@@ -668,11 +668,11 @@ generate_page (void *cls,
668 const char *version, 668 const char *version,
669 const char *upload_data, 669 const char *upload_data,
670 size_t *upload_data_size, void **ptr) 670 size_t *upload_data_size, void **ptr)
671{ 671{
672 struct MHD_Response *response; 672 struct MHD_Response *response;
673 int ret; 673 int ret;
674 int fd; 674 int fd;
675 struct stat buf; 675 struct stat buf;
676 676
677 if (0 != strcmp (url, "/")) 677 if (0 != strcmp (url, "/"))
678 { 678 {
@@ -685,13 +685,13 @@ generate_page (void *cls,
685 return MHD_NO; /* unexpected method (we're not polite...) */ 685 return MHD_NO; /* unexpected method (we're not polite...) */
686 if ( (0 == stat (&url[1], &buf)) && 686 if ( (0 == stat (&url[1], &buf)) &&
687 (NULL == strstr (&url[1], "..")) && 687 (NULL == strstr (&url[1], "..")) &&
688 ('/' != url[1])) 688 ('/' != url[1]))
689 fd = open (&url[1], O_RDONLY); 689 fd = open (&url[1], O_RDONLY);
690 else 690 else
691 fd = -1; 691 fd = -1;
692 if (-1 == fd) 692 if (-1 == fd)
693 return MHD_queue_response (connection, 693 return MHD_queue_response (connection,
694 MHD_HTTP_NOT_FOUND, 694 MHD_HTTP_NOT_FOUND,
695 file_not_found_response); 695 file_not_found_response);
696 /* read beginning of the file to determine mime type */ 696 /* read beginning of the file to determine mime type */
697 got = read (fd, file_data, sizeof (file_data)); 697 got = read (fd, file_data, sizeof (file_data));
@@ -701,12 +701,12 @@ generate_page (void *cls,
701 mime = NULL; 701 mime = NULL;
702 (void) lseek (fd, 0, SEEK_SET); 702 (void) lseek (fd, 0, SEEK_SET);
703 703
704 if (NULL == (response = MHD_create_response_from_fd (buf.st_size, 704 if (NULL == (response = MHD_create_response_from_fd (buf.st_size,
705 fd))) 705 fd)))
706 { 706 {
707 /* internal error (i.e. out of memory) */ 707 /* internal error (i.e. out of memory) */
708 (void) close (fd); 708 (void) close (fd);
709 return MHD_NO; 709 return MHD_NO;
710 } 710 }
711 711
712 /* add mime type if we had one */ 712 /* add mime type if we had one */
@@ -714,8 +714,8 @@ generate_page (void *cls,
714 (void) MHD_add_response_header (response, 714 (void) MHD_add_response_header (response,
715 MHD_HTTP_HEADER_CONTENT_TYPE, 715 MHD_HTTP_HEADER_CONTENT_TYPE,
716 mime); 716 mime);
717 ret = MHD_queue_response (connection, 717 ret = MHD_queue_response (connection,
718 MHD_HTTP_OK, 718 MHD_HTTP_OK,
719 response); 719 response);
720 MHD_destroy_response (response); 720 MHD_destroy_response (response);
721 return ret; 721 return ret;
@@ -744,11 +744,11 @@ generate_page (void *cls,
744 } 744 }
745 *ptr = uc; 745 *ptr = uc;
746 return MHD_YES; 746 return MHD_YES;
747 } 747 }
748 if (0 != *upload_data_size) 748 if (0 != *upload_data_size)
749 { 749 {
750 if (NULL == uc->response) 750 if (NULL == uc->response)
751 (void) MHD_post_process (uc->pp, 751 (void) MHD_post_process (uc->pp,
752 upload_data, 752 upload_data,
753 *upload_data_size); 753 *upload_data_size);
754 *upload_data_size = 0; 754 *upload_data_size = 0;
@@ -764,8 +764,8 @@ generate_page (void *cls,
764 } 764 }
765 if (NULL != uc->response) 765 if (NULL != uc->response)
766 { 766 {
767 return MHD_queue_response (connection, 767 return MHD_queue_response (connection,
768 MHD_HTTP_FORBIDDEN, 768 MHD_HTTP_FORBIDDEN,
769 uc->response); 769 uc->response);
770 } 770 }
771 else 771 else
@@ -778,8 +778,8 @@ generate_page (void *cls,
778 return return_directory_response (connection); 778 return return_directory_response (connection);
779 779
780 /* unexpected request, refuse */ 780 /* unexpected request, refuse */
781 return MHD_queue_response (connection, 781 return MHD_queue_response (connection,
782 MHD_HTTP_FORBIDDEN, 782 MHD_HTTP_FORBIDDEN,
783 request_refused_response); 783 request_refused_response);
784} 784}
785 785
@@ -837,7 +837,7 @@ main (int argc, char *const *argv)
837 837
838 if ( (argc != 2) || 838 if ( (argc != 2) ||
839 (1 != sscanf (argv[1], "%u", &port)) || 839 (1 != sscanf (argv[1], "%u", &port)) ||
840 (UINT16_MAX < port) ) 840 (UINT16_MAX < port) )
841 { 841 {
842 fprintf (stderr, 842 fprintf (stderr,
843 "%s PORT\n", argv[0]); 843 "%s PORT\n", argv[0]);
@@ -864,14 +864,14 @@ main (int argc, char *const *argv)
864 mark_as_html (internal_error_response); 864 mark_as_html (internal_error_response);
865 update_directory (); 865 update_directory ();
866 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG 866 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG
867#if EPOLL_SUPPORT 867#if EPOLL_SUPPORT
868 | MHD_USE_EPOLL_LINUX_ONLY 868 | MHD_USE_EPOLL_LINUX_ONLY
869#endif 869#endif
870 , 870 ,
871 port, 871 port,
872 NULL, NULL, 872 NULL, NULL,
873 &generate_page, NULL, 873 &generate_page, NULL,
874 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024), 874 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024),
875#if PRODUCTION 875#if PRODUCTION
876 MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64), 876 MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64),
877#endif 877#endif
diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h
index 5c030c8d..69f9b784 100644
--- a/src/include/microhttpd.h
+++ b/src/include/microhttpd.h
@@ -774,7 +774,24 @@ enum MHD_OPTION
774 * Increment to use for growing the read buffer (followed by a 774 * Increment to use for growing the read buffer (followed by a
775 * `size_t`). Must fit within #MHD_OPTION_CONNECTION_MEMORY_LIMIT. 775 * `size_t`). Must fit within #MHD_OPTION_CONNECTION_MEMORY_LIMIT.
776 */ 776 */
777 MHD_OPTION_CONNECTION_MEMORY_INCREMENT = 21 777 MHD_OPTION_CONNECTION_MEMORY_INCREMENT = 21,
778
779 /**
780 * Use a callback to determine which X.509 certificate should be
781 * used for a given HTTPS connection. This option should be
782 * followed by a argument of type `gnutls_certificate_retrieve_function2 *`.
783 * This option provides an
784 * alternative to #MHD_OPTION_HTTPS_MEM_KEY,
785 * #MHD_OPTION_HTTPS_MEM_CERT. You must use this version if
786 * multiple domains are to be hosted at the same IP address using
787 * TLS's Server Name Indication (SNI) extension. In this case,
788 * the callback is expected to select the correct certificate
789 * based on the SNI information provided. The callback is expected
790 * to access the SNI data using `gnutls_server_name_get()`.
791 * Using this option requires GnuTLS 3.0 or higher.
792 */
793 MHD_OPTION_HTTPS_CERT_CALLBACK = 22
794
778}; 795};
779 796
780 797
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c
index 301186f7..f43f071f 100644
--- a/src/microhttpd/daemon.c
+++ b/src/microhttpd/daemon.c
@@ -471,6 +471,13 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon)
471 gnutls_datum_t key; 471 gnutls_datum_t key;
472 gnutls_datum_t cert; 472 gnutls_datum_t cert;
473 473
474#if GNUTLS_VERSION_MAJOR >= 3
475 if (NULL != daemon->cert_callback)
476 {
477 gnutls_certificate_set_retrieve_function2 (daemon->x509_cred,
478 daemon->cert_callback);
479 }
480#endif
474 if (NULL != daemon->https_mem_trust) 481 if (NULL != daemon->https_mem_trust)
475 { 482 {
476 cert.data = (unsigned char *) daemon->https_mem_trust; 483 cert.data = (unsigned char *) daemon->https_mem_trust;
@@ -499,6 +506,10 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon)
499 &cert, &key, 506 &cert, &key,
500 GNUTLS_X509_FMT_PEM); 507 GNUTLS_X509_FMT_PEM);
501 } 508 }
509#if GNUTLS_VERSION_MAJOR >= 3
510 if (NULL != daemon->cert_callback)
511 return 0;
512#endif
502#if HAVE_MESSAGES 513#if HAVE_MESSAGES
503 MHD_DLOG (daemon, "You need to specify a certificate and key location\n"); 514 MHD_DLOG (daemon, "You need to specify a certificate and key location\n");
504#endif 515#endif
@@ -2900,6 +2911,18 @@ parse_options_va (struct MHD_Daemon *daemon,
2900 } 2911 }
2901 } 2912 }
2902 break; 2913 break;
2914 case MHD_OPTION_HTTPS_CERT_CALLBACK:
2915#if GNUTLS_VERSION_MAJOR < 3
2916#if HAVE_MESSAGES
2917 MHD_DLOG (daemon,
2918 "MHD_OPTION_HTTPS_CERT_CALLBACK requires building MHD with GnuTLS >= 3.0\n");
2919#endif
2920 return MHD_NO;
2921#else
2922 if (0 != (daemon->options & MHD_USE_SSL))
2923 daemon->cert_callback = va_arg (ap, gnutls_certificate_retrieve_function2 *);
2924 break;
2925#endif
2903#endif 2926#endif
2904#ifdef DAUTH_SUPPORT 2927#ifdef DAUTH_SUPPORT
2905 case MHD_OPTION_DIGEST_AUTH_RANDOM: 2928 case MHD_OPTION_DIGEST_AUTH_RANDOM:
@@ -2974,6 +2997,7 @@ parse_options_va (struct MHD_Daemon *daemon,
2974 case MHD_OPTION_HTTPS_MEM_TRUST: 2997 case MHD_OPTION_HTTPS_MEM_TRUST:
2975 case MHD_OPTION_HTTPS_PRIORITIES: 2998 case MHD_OPTION_HTTPS_PRIORITIES:
2976 case MHD_OPTION_ARRAY: 2999 case MHD_OPTION_ARRAY:
3000 case MHD_OPTION_HTTPS_CERT_CALLBACK:
2977 if (MHD_YES != parse_options (daemon, 3001 if (MHD_YES != parse_options (daemon,
2978 servaddr, 3002 servaddr,
2979 opt, 3003 opt,
diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h
index 861ff39f..9d50de25 100644
--- a/src/microhttpd/internal.h
+++ b/src/microhttpd/internal.h
@@ -31,6 +31,9 @@
31#include "microhttpd.h" 31#include "microhttpd.h"
32#if HTTPS_SUPPORT 32#if HTTPS_SUPPORT
33#include <gnutls/gnutls.h> 33#include <gnutls/gnutls.h>
34#if GNUTLS_VERSION_MAJOR >= 3
35#include <gnutls/abstract.h>
36#endif
34#endif 37#endif
35#if EPOLL_SUPPORT 38#if EPOLL_SUPPORT
36#include <sys/epoll.h> 39#include <sys/epoll.h>
@@ -1161,6 +1164,14 @@ struct MHD_Daemon
1161 */ 1164 */
1162 gnutls_dh_params_t dh_params; 1165 gnutls_dh_params_t dh_params;
1163 1166
1167#if GNUTLS_VERSION_MAJOR >= 3
1168 /**
1169 * Function that can be used to obtain the certificate. Needed
1170 * for SNI support. See #MHD_OPTION_HTTPS_CERT_CALLBACK.
1171 */
1172 gnutls_certificate_retrieve_function2 *cert_callback;
1173#endif
1174
1164 /** 1175 /**
1165 * Pointer to our SSL/TLS key (in ASCII) in memory. 1176 * Pointer to our SSL/TLS key (in ASCII) in memory.
1166 */ 1177 */
diff --git a/src/testcurl/https/Makefile.am b/src/testcurl/https/Makefile.am
index 1a1cc232..f73eb182 100644
--- a/src/testcurl/https/Makefile.am
+++ b/src/testcurl/https/Makefile.am
@@ -19,6 +19,7 @@ check_PROGRAMS = \
19 test_tls_authentication \ 19 test_tls_authentication \
20 test_https_multi_daemon \ 20 test_https_multi_daemon \
21 test_https_get \ 21 test_https_get \
22 test_https_sni \
22 test_https_get_select \ 23 test_https_get_select \
23 test_https_get_parallel \ 24 test_https_get_parallel \
24 test_https_get_parallel_threads \ 25 test_https_get_parallel_threads \
@@ -32,6 +33,7 @@ TESTS = \
32 test_tls_options \ 33 test_tls_options \
33 test_https_multi_daemon \ 34 test_https_multi_daemon \
34 test_https_get \ 35 test_https_get \
36 test_https_sni \
35 test_https_get_select \ 37 test_https_get_select \
36 test_https_get_parallel \ 38 test_https_get_parallel \
37 test_https_get_parallel_threads \ 39 test_https_get_parallel_threads \
@@ -113,6 +115,14 @@ test_https_get_LDADD = \
113 $(top_builddir)/src/microhttpd/libmicrohttpd.la \ 115 $(top_builddir)/src/microhttpd/libmicrohttpd.la \
114 @LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@ 116 @LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@
115 117
118test_https_sni_SOURCES = \
119 test_https_sni.c \
120 tls_test_common.c
121test_https_sni_LDADD = \
122 $(top_builddir)/src/testcurl/libcurl_version_check.a \
123 $(top_builddir)/src/microhttpd/libmicrohttpd.la \
124 @LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@
125
116test_https_get_select_SOURCES = \ 126test_https_get_select_SOURCES = \
117 test_https_get_select.c \ 127 test_https_get_select.c \
118 tls_test_common.c 128 tls_test_common.c
diff --git a/src/testcurl/https/host1.crt b/src/testcurl/https/host1.crt
new file mode 100644
index 00000000..d9b8b999
--- /dev/null
+++ b/src/testcurl/https/host1.crt
@@ -0,0 +1,15 @@
1-----BEGIN CERTIFICATE-----
2MIICWTCCAcICCQDc4McLp7j56DANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa
3WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
4cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MTEZMBcGCSqGSIb3DQEJARYKdGVzdEBo
5b3N0MTAgFw0xMzExMTcxNTE2MzdaGA8yMTEzMTAyNDE1MTYzN1owcDELMAkGA1UE
6BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
7ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDExGTAXBgkqhkiG9w0BCQEWCnRl
8c3RAaG9zdDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKxYiRUzfQnekQn3
96e+hP/mt/JEkiFzX5TV+E19ue2v4tc7lf+SoLEk2dVt5tGQkHjIGeFFNwCLrgXoi
10h3KfP4R1IYe7NFbM+lFVwPceF3inJ75dZD80BxaXQANeh0yC/DhaVJUFNaof2S4+
117xd8zTL6M11gME+XmR8uaDvW7EBtAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAf62m
12Nstj9p9u8T5A5fRnJWfoglH/zfm7IHzht0Wi047O3NFZJ0pOPqV97HuErUA5oBGg
13qswnyRGyGMcvL08Bki7Q6NkY7K0ON3lq+ofTkIAHlOKMF+Y/otbjuIDHBfo63tmE
14uOcr8XDQGu9R0cfh+qLgicJQd/8cFBhxsL0ls6I=
15-----END CERTIFICATE-----
diff --git a/src/testcurl/https/host1.key b/src/testcurl/https/host1.key
new file mode 100644
index 00000000..f549a4dc
--- /dev/null
+++ b/src/testcurl/https/host1.key
@@ -0,0 +1,15 @@
1-----BEGIN RSA PRIVATE KEY-----
2MIICXQIBAAKBgQCsWIkVM30J3pEJ9+nvoT/5rfyRJIhc1+U1fhNfbntr+LXO5X/k
3qCxJNnVbebRkJB4yBnhRTcAi64F6Iodynz+EdSGHuzRWzPpRVcD3Hhd4pye+XWQ/
4NAcWl0ADXodMgvw4WlSVBTWqH9kuPu8XfM0y+jNdYDBPl5kfLmg71uxAbQIDAQAB
5AoGBAJvq9QmjLSnymtCj4pYSEai2iNpebKdiAlEkoC4j67DArupgohWhN398ryt0
6rYgzTMYBKHSVnI969AYkmtlNzM1yNckRQb/G/tWrkl9re28y2nbAExtHbvLoTk2C
7a/EEl1Op+JZNzLoSje7IQMVZoArD3d4aUbfux4XzlO2eRNmZAkEA2pV49QgcOTOJ
8PrR5cgekonNdeMtkbZm9dhxgDk9IsYkC0iOxjn/IbeCQN3wuTQ5/yLoiiQ/CQ8w5
9JndF/XpICwJBAMnY37BSRb+XKZeJWP0yjqyFJwzHXkh6IsoSF2OOXSixdiMpthLh
10IPzvo6Qxsnha4VvwuDxljHzQFPgMT//CTGcCQQDMs9S+LKU50JDEX4Goj43X8RBl
11cp0Poz3yYap3XDqowLYalADRgcvzUq3cuHgoA98Z3W9ASrjUg2o2ItcyBhV3AkAK
12bCBgwl7Hnc6P/I+Tw2CKl/WEO2cq5uOU+4opodg9maw39JdqMiW56cXRXJ+Sh17L
13mIpq0/OFHll21WvsEORRAkAnDDn/vmW25PSxPVY7tKKJCCkmtBeLQpySfpDgBF+O
14QvvokKs2COivc50rmOYNvD1WSsAOspdaSoZUgFw5ikti
15-----END RSA PRIVATE KEY-----
diff --git a/src/testcurl/https/host2.crt b/src/testcurl/https/host2.crt
new file mode 100644
index 00000000..ac28b0e9
--- /dev/null
+++ b/src/testcurl/https/host2.crt
@@ -0,0 +1,15 @@
1-----BEGIN CERTIFICATE-----
2MIICWTCCAcICCQCJ9nhDYTUBKjANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa
3WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
4cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MjEZMBcGCSqGSIb3DQEJARYKdGVzdEBo
5b3N0MjAgFw0xMzExMTcxNTE2NDNaGA8yMTEzMTAyNDE1MTY0M1owcDELMAkGA1UE
6BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
7ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDIxGTAXBgkqhkiG9w0BCQEWCnRl
8c3RAaG9zdDIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVK8QKMvU96iNL2
966PKm6xXw9NPHDn+o1TLF1CQRxXMrBYUrObk0961+3n3Z3BXOFHKfSV4E55CpVyz
10D1Wcadlt3B9z3ke3HOi0lEa1xNJTMQK/QT3Fx/NURmNg5s9HAsqY4ocb9KHaF5Ex
110TgC0L0aRP0cK1x2TgPEHBNcgGl9AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAEXOi
129rSmVrTN5olIdowctr1vWbGwRCjCnAFXDsqakcDASNthr15LB5kr/mrA3olJjbZh
13o+JDvWMY6FN8r1QXW0RL9/obbHxtJpwvAmYVMY9jrR8Rpo38p4RfXlN85g3q9PVx
145IGLaOqLf4hSnKArFL/fzXwxX9b5HBCKlXfiuqM=
15-----END CERTIFICATE-----
diff --git a/src/testcurl/https/host2.key b/src/testcurl/https/host2.key
new file mode 100644
index 00000000..4bbd6178
--- /dev/null
+++ b/src/testcurl/https/host2.key
@@ -0,0 +1,15 @@
1-----BEGIN RSA PRIVATE KEY-----
2MIICWwIBAAKBgQC1SvECjL1PeojS9uujypusV8PTTxw5/qNUyxdQkEcVzKwWFKzm
35NPetft592dwVzhRyn0leBOeQqVcsw9VnGnZbdwfc95HtxzotJRGtcTSUzECv0E9
4xcfzVEZjYObPRwLKmOKHG/Sh2heRMdE4AtC9GkT9HCtcdk4DxBwTXIBpfQIDAQAB
5AoGAR5Do6TfDt69IefdNeCAQKg2PWUg+fUpfEacGciAyX5GnUSQiSReF58HxHumi
6ZL+ZlPgZRQRMwknO23Q4FnSjd66A3E9iHLqkWxRFJWME6E7zgtBrIjctnNu9uYM9
7cw4R6qmXOL7C5sK00KXF2ep8+s+JjrZz61o85QnGGRYA94ECQQDbG6f1B8NKY9T1
81GDR/++rJbdTVQlZQcKSXMumpU6V3mEV0O9GkYaZzoYvWa3kx6c0np4karrm3QWa
9u5E0q1YdAkEA09FPcmzVvIR0+sMWca8QJ/tJUxD6qYo8vLOpO4wt4iTPhGBEU+Q5
10cgXmde3/plVsp0vYxK/NG5XZkoC1fbuC4QJATRGxRlLwsl3jLoUBeVxY5Q5jKYCj
11xS2ITwss5vUGa1jJNW9EesH9YmRudoFI1UwU2EFixtRz4Xik3ARV0vzhUQJAfabT
1250ASxqMYtczW2peMEPurMqCG4d4ES7iUMqPkcBuAErn8rntbbH19igWmOyi/rLp8
13m6jiFnQdPiAmCbEbYQJAFAKiQl2ZOe3gkSh8MaQilD8Ppog6rod4SQiSmRNsDWPi
14IxqXneaGDWhzynC9xr4SwuJ9D5VxW1phNyiveDuYXw==
15-----END RSA PRIVATE KEY-----
diff --git a/src/testcurl/https/test_https_sni.c b/src/testcurl/https/test_https_sni.c
new file mode 100644
index 00000000..408d4c10
--- /dev/null
+++ b/src/testcurl/https/test_https_sni.c
@@ -0,0 +1,289 @@
1/*
2 This file is part of libmicrohttpd
3 (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 */
43struct 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 */
55static struct Hosts *hosts;
56
57/* Load the certificate and the private key.
58 * (This code is largely taken from GnuTLS).
59 */
60static void
61load_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 host->hostname = hostname;
71 host->next = hosts;
72 hosts = host;
73
74 ret = gnutls_load_file (CERT_FILE, &data);
75 if (ret < 0)
76 {
77 fprintf (stderr,
78 "*** Error loading certificate file %s.\n",
79 CERT_FILE);
80 exit (1);
81 }
82 ret =
83 gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
84 0);
85 if (ret < 0)
86 {
87 fprintf (stderr,
88 "*** Error loading certificate file: %s\n",
89 gnutls_strerror (ret));
90 exit (1);
91 }
92 gnutls_free (data.data);
93
94 ret = gnutls_load_file (KEY_FILE, &data);
95 if (ret < 0)
96 {
97 fprintf (stderr,
98 "*** Error loading key file %s.\n",
99 KEY_FILE);
100 exit (1);
101 }
102
103 gnutls_privkey_init (&host->key);
104 ret =
105 gnutls_privkey_import_x509_raw (host->key,
106 &data, GNUTLS_X509_FMT_PEM,
107 NULL, 0);
108 if (ret < 0)
109 {
110 fprintf (stderr,
111 "*** Error loading key file: %s\n",
112 gnutls_strerror (ret));
113 exit (1);
114 }
115 gnutls_free (data.data);
116}
117
118
119
120/**
121 * @param session the session we are giving a cert for
122 * @param req_ca_dn NULL on server side
123 * @param nreqs length of req_ca_dn, and thus 0 on server side
124 * @param pk_algos NULL on server side
125 * @param pk_algos_length 0 on server side
126 * @param pcert list of certificates (to be set)
127 * @param pcert_length length of pcert (to be set)
128 * @param pkey the private key (to be set)
129 */
130static int
131sni_callback (gnutls_session_t session,
132 const gnutls_datum_t* req_ca_dn,
133 int nreqs,
134 const gnutls_pk_algorithm_t* pk_algos,
135 int pk_algos_length,
136 gnutls_pcert_st** pcert,
137 unsigned int *pcert_length,
138 gnutls_privkey_t * pkey)
139{
140 char name[256];
141 size_t name_len;
142 struct Hosts *host;
143 unsigned int type;
144
145 name_len = sizeof (name);
146 if (GNUTLS_E_SUCCESS !=
147 gnutls_server_name_get (session,
148 name,
149 &name_len,
150 &type,
151 0 /* index */))
152 return -1;
153 for (host = hosts; NULL != host; host = host->next)
154 if (0 == strncmp (name, host->hostname, name_len))
155 break;
156 if (NULL == host)
157 {
158 fprintf (stderr,
159 "Need certificate for %.*s\n",
160 (int) name_len,
161 name);
162 return -1;
163 }
164#if 0
165 fprintf (stderr,
166 "Returning certificate for %.*s\n",
167 (int) name_len,
168 name);
169#endif
170 *pkey = host->key;
171 *pcert_length = 1;
172 *pcert = &host->pcrt;
173 return 0;
174}
175
176
177/* perform a HTTP GET request via SSL/TLS */
178static int
179do_get (const char *url)
180{
181 CURL *c;
182 struct CBC cbc;
183 CURLcode errornum;
184 size_t len;
185 struct curl_slist *dns_info;
186
187 len = strlen (test_data);
188 if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
189 {
190 fprintf (stderr, MHD_E_MEM);
191 return -1;
192 }
193 cbc.size = len;
194 cbc.pos = 0;
195
196 c = curl_easy_init ();
197#if DEBUG_HTTPS_TEST
198 curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
199#endif
200 curl_easy_setopt (c, CURLOPT_URL, url);
201 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
202 curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
203 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
204 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
205 curl_easy_setopt (c, CURLOPT_FILE, &cbc);
206
207 /* perform peer authentication */
208 /* TODO merge into send_curl_req */
209 curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
210 curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 2);
211 dns_info = curl_slist_append (NULL, "host1:4233:127.0.0.1");
212 dns_info = curl_slist_append (dns_info, "host2:4233:127.0.0.1");
213 curl_easy_setopt (c, CURLOPT_RESOLVE, dns_info);
214 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
215
216 /* NOTE: use of CONNECTTIMEOUT without also
217 setting NOSIGNAL results in really weird
218 crashes on my system! */
219 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
220 if (CURLE_OK != (errornum = curl_easy_perform (c)))
221 {
222 fprintf (stderr, "curl_easy_perform failed: `%s'\n",
223 curl_easy_strerror (errornum));
224 curl_easy_cleanup (c);
225 free (cbc.buf);
226 curl_slist_free_all (dns_info);
227 return errornum;
228 }
229
230 curl_easy_cleanup (c);
231 curl_slist_free_all (dns_info);
232 if (memcmp (cbc.buf, test_data, len) != 0)
233 {
234 fprintf (stderr, "Error: local file & received file differ.\n");
235 free (cbc.buf);
236 return -1;
237 }
238
239 free (cbc.buf);
240 return 0;
241}
242
243
244int
245main (int argc, char *const *argv)
246{
247 unsigned int error_count = 0;
248 struct MHD_Daemon *d;
249
250 gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
251#ifdef GCRYCTL_INITIALIZATION_FINISHED
252 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
253#endif
254 if (0 != curl_global_init (CURL_GLOBAL_ALL))
255 {
256 fprintf (stderr, "Error: %s\n", strerror (errno));
257 return -1;
258 }
259 load_keys ("host1", "host1.crt", "host1.key");
260 load_keys ("host2", "host2.crt", "host2.key");
261 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL | MHD_USE_DEBUG,
262 4233,
263 NULL, NULL,
264 &http_ahc, NULL,
265 MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
266 MHD_OPTION_END);
267 if (d == NULL)
268 {
269 fprintf (stderr, MHD_E_SERVER_INIT);
270 return -1;
271 }
272 error_count += do_get ("https://host1:4233/");
273 error_count += do_get ("https://host2:4233/");
274
275 MHD_stop_daemon (d);
276 curl_global_cleanup ();
277 return error_count != 0;
278}
279
280
281#else
282
283int main ()
284{
285 fprintf (stderr,
286 "SNI not supported by GnuTLS < 3.0\n");
287 return 0;
288}
289#endif