aboutsummaryrefslogtreecommitdiff
path: root/doc/chapters/tlsauthentication.inc
blob: ebde69a83808f7e1883254e7bbf9e6bba4e92566 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
We left the basic authentication chapter with the unsatisfactory conclusion that
any traffic, including the credentials, could be intercepted by anyone between
the browser client and the server. Protecting the data while it is sent over
unsecured lines will be the goal of this chapter.

Since version 0.4, the @emph{MHD} library includes support for encrypting the
traffic by employing SSL/TSL. If @emph{GNU libmicrohttpd} has been configured to
support these, encryption and decryption can be applied transparently on the
data being sent, with only minimal changes to the actual source code of the example.


@heading Preparation

First, a private key for the server will be generated. With this key, the server
will later be able to authenticate itself to the client---preventing anyone else
from stealing the password by faking its identity. The @emph{OpenSSL} suite, which
is available on many operating systems, can generate such a key. For the scope of
this tutorial, we will be content with a 1024 bit key:
@verbatim
> openssl genrsa -out server.key 1024
@end verbatim
@noindent

In addition to the key, a certificate describing the server in human readable tokens
is also needed. This certificate will be attested with our aforementioned key. In this way,
we obtain a self-signed certificate, valid for one year.

@verbatim
> openssl req -days 365 -out server.pem -new -x509 -key server.key
@end verbatim
@noindent

To avoid unnecessary error messages in the browser, the certificate needs to
have a name that matches the @emph{URI}, for example, "localhost" or the domain.
If you plan to have a publicly reachable server, you will need to ask a trusted third party,
called @emph{Certificate Authority}, or @emph{CA}, to attest the certificate for you. This way,
any visitor can make sure the server's identity is real.

Whether the server's certificate is signed by us or a third party, once it has been accepted
by the client, both sides will be communicating over encrypted channels. From this point on,
it is the client's turn to authenticate itself. But this has already been implemented in the basic
authentication scheme.


@heading Changing the source code

We merely have to extend the server program so that it loads the two files into memory,

@verbatim
int
main ()
{
  struct MHD_Daemon *daemon;
  char *key_pem;
  char *cert_pem;

  key_pem = load_file (SERVERKEYFILE);
  cert_pem = load_file (SERVERCERTFILE);

  if ((key_pem == NULL) || (cert_pem == NULL))
  {
    printf ("The key/certificate files could not be read.\n");
    return 1;
  }
@end verbatim
@noindent

and then we point the @emph{MHD} daemon to it upon initialization.
@verbatim

  daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
  	   		     PORT, NULL, NULL,
                             &answer_to_connection, NULL,
                             MHD_OPTION_HTTPS_MEM_KEY, key_pem,
                             MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
                             MHD_OPTION_END);

  if (NULL == daemon)
    {
      printf ("%s\n", cert_pem);

      free (key_pem);
      free (cert_pem);

      return 1;
    }
@end verbatim
@noindent


The rest consists of little new besides some additional memory cleanups.
@verbatim

  getchar ();

  MHD_stop_daemon (daemon);
  free (key_pem);
  free (cert_pem);

  return 0;
}
@end verbatim
@noindent


The rather unexciting file loader can be found in the complete example @code{tlsauthentication.c}.


@heading Remarks
@itemize @bullet
@item
While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume
standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type
@code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to
handle the answer properly.

@item
The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the
certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured)
that they should not accept certificates of unknown origin.

@item
The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to
hardcode certificates in embedded devices.

@item
The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}.

@end itemize


@heading Client authentication

You can also use MHD to authenticate the client via SSL/TLS certificates
(as an alternative to using the password-based Basic or Digest authentication).
To do this, you will need to link your application against @emph{gnutls}.
Next, when you start the MHD daemon, you must specify the root CA that you're
willing to trust:
@verbatim
  daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
  	   		     PORT, NULL, NULL,
                             &answer_to_connection, NULL,
                             MHD_OPTION_HTTPS_MEM_KEY, key_pem,
                             MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
			     MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem,
                             MHD_OPTION_END);
@end verbatim

With this, you can then obtain client certificates for each session.
In order to obtain the identity of the client, you first need to
obtain the raw GnuTLS session handle from @emph{MHD} using
@code{MHD_get_connection_info}.

@verbatim
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>

gnutls_session_t tls_session;
union MHD_ConnectionInfo *ci;

ci = MHD_get_connection_info (connection,
                              MHD_CONNECTION_INFO_GNUTLS_SESSION);
tls_session = ci->tls_session;
@end verbatim

You can then extract the client certificate:

@verbatim
/**
 * Get the client's certificate
 *
 * @param tls_session the TLS session
 * @return NULL if no valid client certificate could be found, a pointer
 *  	to the certificate if found
 */
static gnutls_x509_crt_t
get_client_certificate (gnutls_session_t tls_session)
{
  unsigned int listsize;
  const gnutls_datum_t * pcert;
  gnutls_certificate_status_t client_cert_status;
  gnutls_x509_crt_t client_cert;

  if (tls_session == NULL)
    return NULL;
  if (gnutls_certificate_verify_peers2(tls_session,
				       &client_cert_status))
    return NULL;
  pcert = gnutls_certificate_get_peers(tls_session,
				       &listsize);
  if ( (pcert == NULL) ||
       (listsize == 0))
    {
      fprintf (stderr,
	       "Failed to retrieve client certificate chain\n");
      return NULL;
    }
  if (gnutls_x509_crt_init(&client_cert))
    {
      fprintf (stderr,
	       "Failed to initialize client certificate\n");
      return NULL;
    }
  /* Note that by passing values between 0 and listsize here, you
     can get access to the CA's certs */
  if (gnutls_x509_crt_import(client_cert,
			     &pcert[0],
			     GNUTLS_X509_FMT_DER))
    {
      fprintf (stderr,
	       "Failed to import client certificate\n");
      gnutls_x509_crt_deinit(client_cert);
      return NULL;
    }
  return client_cert;
}
@end verbatim

Using the client certificate, you can then get the client's distinguished name
and alternative names:

@verbatim
/**
 * Get the distinguished name from the client's certificate
 *
 * @param client_cert the client certificate
 * @return NULL if no dn or certificate could be found, a pointer
 * 			to the dn if found
 */
char *
cert_auth_get_dn(gnutls_x509_crt_c client_cert)
{
  char* buf;
  size_t lbuf;

  lbuf = 0;
  gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
  buf = malloc(lbuf);
  if (buf == NULL)
    {
      fprintf (stderr,
	       "Failed to allocate memory for certificate dn\n");
      return NULL;
    }
  gnutls_x509_crt_get_dn(client_cert, buf, &lbuf);
  return buf;
}


/**
 * Get the alternative name of specified type from the client's certificate
 *
 * @param client_cert the client certificate
 * @param nametype The requested name type
 * @param index The position of the alternative name if multiple names are
 * 			matching the requested type, 0 for the first matching name
 * @return NULL if no matching alternative name could be found, a pointer
 * 			to the alternative name if found
 */
char *
MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
			   int nametype,
			   unsigned int index)
{
  char* buf;
  size_t lbuf;
  unsigned int seq;
  unsigned int subseq;
  unsigned int type;
  int result;

  subseq = 0;
  for (seq=0;;seq++)
    {
      lbuf = 0;
      result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
						     &type, NULL);
      if (result == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
	return NULL;
      if (nametype != (int) type)
	continue;
      if (subseq == index)
	break;
      subseq++;
    }
  buf = malloc(lbuf);
  if (buf == NULL)
    {
      fprintf (stderr,
	       "Failed to allocate memory for certificate alt name\n");
      return NULL;
    }
  result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
						 seq,
						 buf,
						 &lbuf,
						 NULL, NULL);
  if (result != nametype)
    {
      fprintf (stderr,
	       "Unexpected return value from gnutls: %d\n",
	       result);
      free (buf);
      return NULL;
    }
  return buf;
}
@end verbatim

Finally, you should release the memory associated with the client
certificate:

@verbatim
gnutls_x509_crt_deinit (client_cert);
@end verbatim



@heading Using TLS Server Name Indication (SNI)

SNI enables hosting multiple domains under one IP address with TLS.  So
SNI is the TLS-equivalent of virtual hosting.  To use SNI with MHD, you
need at least GnuTLS 3.0.  The main change compared to the simple hosting
of one domain is that you need to provide a callback instead of the key
and certificate.  For example, when you start the MHD daemon, you could
do this:
@verbatim
  daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
  	   		     PORT, NULL, NULL,
                             &answer_to_connection, NULL,
                             MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
                             MHD_OPTION_END);
@end verbatim
Here, @code{sni_callback} is the name of a function that you will have to
implement to retrieve the X.509 certificate for an incoming connection.
The callback has type @code{gnutls_certificate_retrieve_function2} and
is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2}
as follows:

@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)

@table @var
@item req_ca_cert
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()}.

@item pk_algos
contains a list with servers acceptable signature algorithms. The certificate returned should support the servers given algorithms.

@item pcert
should contain a single certificate and public or a list of them.

@item pcert_length
is the size of the previous list.

@item pkey
is the private key.
@end table
@end deftypefn

A possible implementation of this callback would look like this:

@verbatim
struct Hosts
{
  struct Hosts *next;
  const char *hostname;
  gnutls_pcert_st pcrt;
  gnutls_privkey_t key;
};

static struct Hosts *hosts;

int
sni_callback (gnutls_session_t session,
              const gnutls_datum_t* req_ca_dn,
              int nreqs,
              const gnutls_pk_algorithm_t* pk_algos,
              int pk_algos_length,
              gnutls_pcert_st** pcert,
              unsigned int *pcert_length,
              gnutls_privkey_t * pkey)
{
  char name[256];
  size_t name_len;
  struct Hosts *host;
  unsigned int type;

  name_len = sizeof (name);
  if (GNUTLS_E_SUCCESS !=
      gnutls_server_name_get (session,
                              name,
                              &name_len,
                              &type,
                              0 /* index */))
    return -1;
  for (host = hosts; NULL != host; host = host->next)
    if (0 == strncmp (name, host->hostname, name_len))
      break;
  if (NULL == host)
    {
      fprintf (stderr,
               "Need certificate for %.*s\n",
               (int) name_len,
               name);
      return -1;
    }
  fprintf (stderr,
           "Returning certificate for %.*s\n",
           (int) name_len,
           name);
  *pkey = host->key;
  *pcert_length = 1;
  *pcert = &host->pcrt;
  return 0;
}
@end verbatim

Note that MHD cannot offer passing a closure or any other additional information
to this callback, as the GnuTLS API unfortunately does not permit this at this
point.

The @code{hosts} list can be initialized by loading the private keys and X.509
certificates from disk as follows:

@verbatim
static void
load_keys(const char *hostname,
          const char *CERT_FILE,
          const char *KEY_FILE)
{
  int ret;
  gnutls_datum_t data;
  struct Hosts *host;

  host = malloc (sizeof (struct Hosts));
  host->hostname = hostname;
  host->next = hosts;
  hosts = host;

  ret = gnutls_load_file (CERT_FILE, &data);
  if (ret < 0)
  {
    fprintf (stderr,
             "*** Error loading certificate file %s.\n",
             CERT_FILE);
    exit(1);
  }
  ret =
    gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
                                  0);
  if (ret < 0)
  {
    fprintf(stderr,
            "*** Error loading certificate file: %s\n",
            gnutls_strerror (ret));
    exit(1);
  }
  gnutls_free (data.data);

  ret = gnutls_load_file (KEY_FILE, &data);
  if (ret < 0)
  {
    fprintf (stderr,
             "*** Error loading key file %s.\n",
             KEY_FILE);
    exit(1);
  }

  gnutls_privkey_init (&host->key);
  ret =
    gnutls_privkey_import_x509_raw (host->key,
                                    &data, GNUTLS_X509_FMT_PEM,
                                    NULL, 0);
  if (ret < 0)
  {
    fprintf (stderr,
             "*** Error loading key file: %s\n",
             gnutls_strerror (ret));
    exit(1);
  }
  gnutls_free (data.data);
}
@end verbatim

The code above was largely lifted from GnuTLS.  You can find other
methods for initializing certificates and keys in the GnuTLS manual
and source code.