diff options
author | Christian Grothoff <christian@grothoff.org> | 2013-12-21 16:28:54 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2013-12-21 16:28:54 +0000 |
commit | 9889fd9eeec2c8d40f6a627720fb1afeac044ab0 (patch) | |
tree | fc91c30734fbc0113e139b0ea05ec499d300a198 | |
parent | 00a9277f498260289d89f0de6783829b6391394b (diff) | |
download | libmicrohttpd-9889fd9eeec2c8d40f6a627720fb1afeac044ab0.tar.gz libmicrohttpd-9889fd9eeec2c8d40f6a627720fb1afeac044ab0.zip |
add support for SNI
-rw-r--r-- | ChangeLog | 2 | ||||
-rw-r--r-- | doc/chapters/tlsauthentication.inc | 247 | ||||
-rw-r--r-- | doc/libmicrohttpd-tutorial.texi | 18 | ||||
-rw-r--r-- | doc/libmicrohttpd.texi | 16 | ||||
-rw-r--r-- | src/examples/demo.c | 106 | ||||
-rw-r--r-- | src/include/microhttpd.h | 19 | ||||
-rw-r--r-- | src/microhttpd/daemon.c | 24 | ||||
-rw-r--r-- | src/microhttpd/internal.h | 11 | ||||
-rw-r--r-- | src/testcurl/https/Makefile.am | 10 | ||||
-rw-r--r-- | src/testcurl/https/host1.crt | 15 | ||||
-rw-r--r-- | src/testcurl/https/host1.key | 15 | ||||
-rw-r--r-- | src/testcurl/https/host2.crt | 15 | ||||
-rw-r--r-- | src/testcurl/https/host2.key | 15 | ||||
-rw-r--r-- | src/testcurl/https/test_https_sni.c | 289 |
14 files changed, 700 insertions, 102 deletions
@@ -1,7 +1,7 @@ | |||
1 | Sat Dec 21 17:26:08 CET 2013 | 1 | Sat 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 | ||
6 | Mon Dec 9 21:41:57 CET 2013 | 6 | Mon 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 @@ | |||
1 | We left the basic authentication chapter with the unsatisfactory conclusion that | 1 | We left the basic authentication chapter with the unsatisfactory conclusion that |
2 | any traffic, including the credentials, could be intercepted by anyone between | 2 | any traffic, including the credentials, could be intercepted by anyone between |
3 | the browser client and the server. Protecting the data while it is sent over | 3 | the browser client and the server. Protecting the data while it is sent over |
4 | unsecured lines will be the goal of this chapter. | 4 | unsecured lines will be the goal of this chapter. |
5 | 5 | ||
6 | Since version 0.4, the @emph{MHD} library includes support for encrypting the | 6 | Since 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 | ||
24 | In addition to the key, a certificate describing the server in human readable tokens | 24 | In addition to the key, a certificate describing the server in human readable tokens |
25 | is also needed. This certificate will be attested with our aforementioned key. In this way, | 25 | is also needed. This certificate will be attested with our aforementioned key. In this way, |
26 | we obtain a self-signed certificate, valid for one year. | 26 | we 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 | ||
39 | Whether the server's certificate is signed by us or a third party, once it has been accepted | 39 | Whether the server's certificate is signed by us or a third party, once it has been accepted |
40 | by the client, both sides will be communicating over encrypted channels. From this point on, | 40 | by the client, both sides will be communicating over encrypted channels. From this point on, |
41 | it is the client's turn to authenticate itself. But this has already been implemented in the basic | 41 | it is the client's turn to authenticate itself. But this has already been implemented in the basic |
42 | authentication scheme. | 42 | authentication scheme. |
43 | 43 | ||
44 | 44 | ||
@@ -65,12 +65,12 @@ main () | |||
65 | @end verbatim | 65 | @end verbatim |
66 | @noindent | 66 | @noindent |
67 | 67 | ||
68 | and then we point the @emph{MHD} daemon to it upon initalization. | 68 | and 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 |
112 | While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume | 112 | While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume |
113 | standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type | 113 | standard @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 |
115 | handle the answer properly. | 115 | handle the answer properly. |
116 | 116 | ||
117 | @item | 117 | @item |
118 | The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the | 118 | The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the |
119 | certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured) | 119 | certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured) |
120 | that they should not accept certificates of unknown origin. | 120 | that they should not accept certificates of unknown origin. |
121 | 121 | ||
122 | @item | 122 | @item |
123 | The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to | 123 | The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to |
124 | hardcode certificates in embedded devices. | 124 | hardcode certificates in embedded devices. |
125 | 125 | ||
126 | @item | 126 | @item |
127 | The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists | 127 | The 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}. | |||
135 | You can also use MHD to authenticate the client via SSL/TLS certificates | 135 | You 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). |
137 | To do this, you will need to link your application against @emph{gnutls}. | 137 | To do this, you will need to link your application against @emph{gnutls}. |
138 | Next, when you start the MHD daemon, you must specify the root CA that you're | 138 | Next, when you start the MHD daemon, you must specify the root CA that you're |
139 | willing to trust: | 139 | willing 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 | |||
159 | gnutls_session_t tls_session; | 159 | gnutls_session_t tls_session; |
160 | union MHD_ConnectionInfo *ci; | 160 | union MHD_ConnectionInfo *ci; |
161 | 161 | ||
162 | ci = MHD_get_connection_info (connection, | 162 | ci = MHD_get_connection_info (connection, |
163 | MHD_CONNECTION_INFO_GNUTLS_SESSION); | 163 | MHD_CONNECTION_INFO_GNUTLS_SESSION); |
164 | tls_session = ci->tls_session; | 164 | tls_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 | */ |
177 | static gnutls_x509_crt_t | 177 | static gnutls_x509_crt_t |
178 | get_client_certificate (gnutls_session_t tls_session) | 178 | get_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 | */ |
231 | char * | 231 | char * |
232 | cert_auth_get_dn(gnutls_x509_crt_c client_cert) | 232 | cert_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 | */ |
261 | char * | 261 | char * |
262 | MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert, | 262 | MHD_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: | |||
315 | gnutls_x509_crt_deinit (client_cert); | 315 | gnutls_x509_crt_deinit (client_cert); |
316 | @end verbatim | 316 | @end verbatim |
317 | 317 | ||
318 | |||
319 | |||
320 | @heading Using TLS Server Name Indication (SNI) | ||
321 | |||
322 | SNI enables hosting multiple domains under one IP address with TLS. So | ||
323 | SNI is the TLS-equivalent of virtual hosting. To use SNI with MHD, you | ||
324 | need at least GnuTLS 3.0. The main change compared to the simple hosting | ||
325 | of one domain is that you need to provide a callback instead of the key | ||
326 | and certificate. For example, when you start the MHD daemon, you could | ||
327 | do 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 | ||
335 | Here, @code{sni_callback} is the name of a function that you will have to | ||
336 | implement to retrieve the X.509 certificate for an incoming connection. | ||
337 | The callback has type @code{gnutls_certificate_retrieve_function2} and | ||
338 | is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2} | ||
339 | as 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 | ||
345 | is 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 | ||
348 | contains a list with server’s acceptable signature algorithms. The certificate returned should support the server’s given algorithms. | ||
349 | |||
350 | @item pcert | ||
351 | should contain a single certificate and public or a list of them. | ||
352 | |||
353 | @item pcert_length | ||
354 | is the size of the previous list. | ||
355 | |||
356 | @item pkey | ||
357 | is the private key. | ||
358 | @end table | ||
359 | @end deftypefn | ||
360 | |||
361 | A possible implementation of this callback would look like this: | ||
362 | |||
363 | @verbatim | ||
364 | struct Hosts | ||
365 | { | ||
366 | struct Hosts *next; | ||
367 | const char *hostname; | ||
368 | gnutls_pcert_st pcrt; | ||
369 | gnutls_privkey_t key; | ||
370 | }; | ||
371 | |||
372 | static struct Hosts *hosts; | ||
373 | |||
374 | int | ||
375 | sni_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 | |||
419 | Note that MHD cannot offer passing a closure or any other additional information | ||
420 | to this callback, as the GnuTLS API unfortunately does not permit this at this | ||
421 | point. | ||
422 | |||
423 | The @code{hosts} list can be initialized by loading the private keys and X.509 | ||
424 | certificats from disk as follows: | ||
425 | |||
426 | @verbatim | ||
427 | static void | ||
428 | load_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 | |||
486 | The code above was largely lifted from GnuTLS. You can find other | ||
487 | methods for initializing certificates and keys in the GnuTLS manual | ||
488 | and 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 |
22 | This tutorial documents GNU libmicrohttpd version @value{VERSION}, last | 22 | This tutorial documents GNU libmicrohttpd version @value{VERSION}, last |
23 | updated @value{UPDATED}. | 23 | updated @value{UPDATED}. |
24 | 24 | ||
25 | Copyright (c) 2008 Sebastian Gerhardt. | 25 | Copyright (c) 2008 Sebastian Gerhardt. |
26 | 26 | ||
27 | Copyright (c) 2010, 2011, 2012 Christian Grothoff. | 27 | Copyright (c) 2010, 2011, 2012, 2013 Christian Grothoff. |
28 | @quotation | 28 | @quotation |
29 | Permission is granted to copy, distribute and/or modify this document | 29 | Permission is granted to copy, distribute and/or modify this document |
30 | under the terms of the GNU Free Documentation License, Version 1.3 | 30 | under 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 | |||
701 | unchanged to gnutls_priority_init. If this option is not | 701 | unchanged to gnutls_priority_init. If this option is not |
702 | specified, ``NORMAL'' is used. | 702 | specified, ``NORMAL'' is used. |
703 | 703 | ||
704 | @item MHD_OPTION_HTTPS_CERT_CALLBACK | ||
705 | @cindex SSL | ||
706 | @cindex TLS | ||
707 | @cindex SNI | ||
708 | Use a callback to determine which X.509 certificate should be used for | ||
709 | a given HTTPS connection. This option should be followed by a | ||
710 | argument of type "gnutls_certificate_retrieve_function2 *". This | ||
711 | option provides an alternative to MHD_OPTION_HTTPS_MEM_KEY and | ||
712 | MHD_OPTION_HTTPS_MEM_CERT. You must use this version if multiple | ||
713 | domains are to be hosted at the same IP address using TLS's Server | ||
714 | Name Indication (SNI) extension. In this case, the callback is | ||
715 | expected to select the correct certificate based on the SNI | ||
716 | information provided. The callback is expected to access the SNI data | ||
717 | using gnutls_server_name_get(). Using this option requires GnuTLS 3.0 | ||
718 | or 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 | */ |
663 | static int | 663 | static 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 | ||
118 | test_https_sni_SOURCES = \ | ||
119 | test_https_sni.c \ | ||
120 | tls_test_common.c | ||
121 | test_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 | |||
116 | test_https_get_select_SOURCES = \ | 126 | test_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----- | ||
2 | MIICWTCCAcICCQDc4McLp7j56DANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa | ||
3 | WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 | ||
4 | cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MTEZMBcGCSqGSIb3DQEJARYKdGVzdEBo | ||
5 | b3N0MTAgFw0xMzExMTcxNTE2MzdaGA8yMTEzMTAyNDE1MTYzN1owcDELMAkGA1UE | ||
6 | BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp | ||
7 | ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDExGTAXBgkqhkiG9w0BCQEWCnRl | ||
8 | c3RAaG9zdDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKxYiRUzfQnekQn3 | ||
9 | 6e+hP/mt/JEkiFzX5TV+E19ue2v4tc7lf+SoLEk2dVt5tGQkHjIGeFFNwCLrgXoi | ||
10 | h3KfP4R1IYe7NFbM+lFVwPceF3inJ75dZD80BxaXQANeh0yC/DhaVJUFNaof2S4+ | ||
11 | 7xd8zTL6M11gME+XmR8uaDvW7EBtAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAf62m | ||
12 | Nstj9p9u8T5A5fRnJWfoglH/zfm7IHzht0Wi047O3NFZJ0pOPqV97HuErUA5oBGg | ||
13 | qswnyRGyGMcvL08Bki7Q6NkY7K0ON3lq+ofTkIAHlOKMF+Y/otbjuIDHBfo63tmE | ||
14 | uOcr8XDQGu9R0cfh+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----- | ||
2 | MIICXQIBAAKBgQCsWIkVM30J3pEJ9+nvoT/5rfyRJIhc1+U1fhNfbntr+LXO5X/k | ||
3 | qCxJNnVbebRkJB4yBnhRTcAi64F6Iodynz+EdSGHuzRWzPpRVcD3Hhd4pye+XWQ/ | ||
4 | NAcWl0ADXodMgvw4WlSVBTWqH9kuPu8XfM0y+jNdYDBPl5kfLmg71uxAbQIDAQAB | ||
5 | AoGBAJvq9QmjLSnymtCj4pYSEai2iNpebKdiAlEkoC4j67DArupgohWhN398ryt0 | ||
6 | rYgzTMYBKHSVnI969AYkmtlNzM1yNckRQb/G/tWrkl9re28y2nbAExtHbvLoTk2C | ||
7 | a/EEl1Op+JZNzLoSje7IQMVZoArD3d4aUbfux4XzlO2eRNmZAkEA2pV49QgcOTOJ | ||
8 | PrR5cgekonNdeMtkbZm9dhxgDk9IsYkC0iOxjn/IbeCQN3wuTQ5/yLoiiQ/CQ8w5 | ||
9 | JndF/XpICwJBAMnY37BSRb+XKZeJWP0yjqyFJwzHXkh6IsoSF2OOXSixdiMpthLh | ||
10 | IPzvo6Qxsnha4VvwuDxljHzQFPgMT//CTGcCQQDMs9S+LKU50JDEX4Goj43X8RBl | ||
11 | cp0Poz3yYap3XDqowLYalADRgcvzUq3cuHgoA98Z3W9ASrjUg2o2ItcyBhV3AkAK | ||
12 | bCBgwl7Hnc6P/I+Tw2CKl/WEO2cq5uOU+4opodg9maw39JdqMiW56cXRXJ+Sh17L | ||
13 | mIpq0/OFHll21WvsEORRAkAnDDn/vmW25PSxPVY7tKKJCCkmtBeLQpySfpDgBF+O | ||
14 | QvvokKs2COivc50rmOYNvD1WSsAOspdaSoZUgFw5ikti | ||
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----- | ||
2 | MIICWTCCAcICCQCJ9nhDYTUBKjANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa | ||
3 | WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 | ||
4 | cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MjEZMBcGCSqGSIb3DQEJARYKdGVzdEBo | ||
5 | b3N0MjAgFw0xMzExMTcxNTE2NDNaGA8yMTEzMTAyNDE1MTY0M1owcDELMAkGA1UE | ||
6 | BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp | ||
7 | ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDIxGTAXBgkqhkiG9w0BCQEWCnRl | ||
8 | c3RAaG9zdDIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVK8QKMvU96iNL2 | ||
9 | 66PKm6xXw9NPHDn+o1TLF1CQRxXMrBYUrObk0961+3n3Z3BXOFHKfSV4E55CpVyz | ||
10 | D1Wcadlt3B9z3ke3HOi0lEa1xNJTMQK/QT3Fx/NURmNg5s9HAsqY4ocb9KHaF5Ex | ||
11 | 0TgC0L0aRP0cK1x2TgPEHBNcgGl9AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAEXOi | ||
12 | 9rSmVrTN5olIdowctr1vWbGwRCjCnAFXDsqakcDASNthr15LB5kr/mrA3olJjbZh | ||
13 | o+JDvWMY6FN8r1QXW0RL9/obbHxtJpwvAmYVMY9jrR8Rpo38p4RfXlN85g3q9PVx | ||
14 | 5IGLaOqLf4hSnKArFL/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----- | ||
2 | MIICWwIBAAKBgQC1SvECjL1PeojS9uujypusV8PTTxw5/qNUyxdQkEcVzKwWFKzm | ||
3 | 5NPetft592dwVzhRyn0leBOeQqVcsw9VnGnZbdwfc95HtxzotJRGtcTSUzECv0E9 | ||
4 | xcfzVEZjYObPRwLKmOKHG/Sh2heRMdE4AtC9GkT9HCtcdk4DxBwTXIBpfQIDAQAB | ||
5 | AoGAR5Do6TfDt69IefdNeCAQKg2PWUg+fUpfEacGciAyX5GnUSQiSReF58HxHumi | ||
6 | ZL+ZlPgZRQRMwknO23Q4FnSjd66A3E9iHLqkWxRFJWME6E7zgtBrIjctnNu9uYM9 | ||
7 | cw4R6qmXOL7C5sK00KXF2ep8+s+JjrZz61o85QnGGRYA94ECQQDbG6f1B8NKY9T1 | ||
8 | 1GDR/++rJbdTVQlZQcKSXMumpU6V3mEV0O9GkYaZzoYvWa3kx6c0np4karrm3QWa | ||
9 | u5E0q1YdAkEA09FPcmzVvIR0+sMWca8QJ/tJUxD6qYo8vLOpO4wt4iTPhGBEU+Q5 | ||
10 | cgXmde3/plVsp0vYxK/NG5XZkoC1fbuC4QJATRGxRlLwsl3jLoUBeVxY5Q5jKYCj | ||
11 | xS2ITwss5vUGa1jJNW9EesH9YmRudoFI1UwU2EFixtRz4Xik3ARV0vzhUQJAfabT | ||
12 | 50ASxqMYtczW2peMEPurMqCG4d4ES7iUMqPkcBuAErn8rntbbH19igWmOyi/rLp8 | ||
13 | m6jiFnQdPiAmCbEbYQJAFAKiQl2ZOe3gkSh8MaQilD8Ppog6rod4SQiSmRNsDWPi | ||
14 | IxqXneaGDWhzynC9xr4SwuJ9D5VxW1phNyiveDuYXw== | ||
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 | */ | ||
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 | 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 | */ | ||
130 | static int | ||
131 | sni_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 */ | ||
178 | static int | ||
179 | do_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, ©Buffer); | ||
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 | |||
244 | int | ||
245 | main (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 | |||
283 | int main () | ||
284 | { | ||
285 | fprintf (stderr, | ||
286 | "SNI not supported by GnuTLS < 3.0\n"); | ||
287 | return 0; | ||
288 | } | ||
289 | #endif | ||