diff options
Diffstat (limited to 'doc/chapters/tlsauthentication.inc')
-rw-r--r-- | doc/chapters/tlsauthentication.inc | 247 |
1 files changed, 209 insertions, 38 deletions
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. | ||