Home | History | Annotate | Download | only in chapters
      1 We left the basic authentication chapter with the unsatisfactory conclusion that
      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
      4 unsecured lines will be the goal of this chapter.
      5 
      6 Since version 0.4, the @emph{MHD} library includes support for encrypting the
      7 traffic by employing SSL/TSL. If @emph{GNU libmicrohttpd} has been configured to
      8 support these, encryption and decryption can be applied transparently on the
      9 data being sent, with only minimal changes to the actual source code of the example.
     10 
     11 
     12 @heading Preparation
     13 
     14 First, a private key for the server will be generated. With this key, the server
     15 will later be able to authenticate itself to the client---preventing anyone else
     16 from stealing the password by faking its identity. The @emph{OpenSSL} suite, which
     17 is available on many operating systems, can generate such a key. For the scope of
     18 this tutorial, we will be content with a 1024 bit key:
     19 @verbatim
     20 > openssl genrsa -out server.key 1024
     21 @end verbatim
     22 @noindent
     23 
     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,
     26 we obtain a self-signed certificate, valid for one year.
     27 
     28 @verbatim
     29 > openssl req -days 365 -out server.pem -new -x509 -key server.key
     30 @end verbatim
     31 @noindent
     32 
     33 To avoid unnecessary error messages in the browser, the certificate needs to
     34 have a name that matches the @emph{URI}, for example, "localhost" or the domain.
     35 If you plan to have a publicly reachable server, you will need to ask a trusted third party,
     36 called @emph{Certificate Authority}, or @emph{CA}, to attest the certificate for you. This way,
     37 any visitor can make sure the server's identity is real.
     38 
     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,
     41 it is the client's turn to authenticate itself. But this has already been implemented in the basic
     42 authentication scheme.
     43 
     44 
     45 @heading Changing the source code
     46 
     47 We merely have to extend the server program so that it loads the two files into memory,
     48 
     49 @verbatim
     50 int
     51 main ()
     52 {
     53   struct MHD_Daemon *daemon;
     54   char *key_pem;
     55   char *cert_pem;
     56 
     57   key_pem = load_file (SERVERKEYFILE);
     58   cert_pem = load_file (SERVERCERTFILE);
     59 
     60   if ((key_pem == NULL) || (cert_pem == NULL))
     61   {
     62     printf ("The key/certificate files could not be read.\n");
     63     return 1;
     64   }
     65 @end verbatim
     66 @noindent
     67 
     68 and then we point the @emph{MHD} daemon to it upon initalization.
     69 @verbatim
     70 
     71   daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
     72   	   		     PORT, NULL, NULL,
     73                              &answer_to_connection, NULL,
     74                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
     75                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
     76                              MHD_OPTION_END);
     77 
     78   if (NULL == daemon)
     79     {
     80       printf ("%s\n", cert_pem);
     81 
     82       free (key_pem);
     83       free (cert_pem);
     84 
     85       return 1;
     86     }
     87 @end verbatim
     88 @noindent
     89 
     90 
     91 The rest consists of little new besides some additional memory cleanups.
     92 @verbatim
     93 
     94   getchar ();
     95 
     96   MHD_stop_daemon (daemon);
     97   free (key_pem);
     98   free (cert_pem);
     99 
    100   return 0;
    101 }
    102 @end verbatim
    103 @noindent
    104 
    105 
    106 The rather unexciting file loader can be found in the complete example @code{tlsauthentication.c}.
    107 
    108 
    109 @heading Remarks
    110 @itemize @bullet
    111 @item
    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
    114 @code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to
    115 handle the answer properly.
    116 
    117 @item
    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)
    120 that they should not accept certificates of unknown origin.
    121 
    122 @item
    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.
    125 
    126 @item
    127 The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
    128 both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}.
    129 
    130 @end itemize
    131 
    132 
    133 @heading Client authentication
    134 
    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).
    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
    139 willing to trust:
    140 @verbatim
    141   daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
    142   	   		     PORT, NULL, NULL,
    143                              &answer_to_connection, NULL,
    144                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
    145                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
    146 			     MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem,
    147                              MHD_OPTION_END);
    148 @end verbatim
    149 
    150 With this, you can then obtain client certificates for each session.
    151 In order to obtain the identity of the client, you first need to
    152 obtain the raw GnuTLS session handle from @emph{MHD} using
    153 @code{MHD_get_connection_info}.
    154 
    155 @verbatim
    156 #include <gnutls/gnutls.h>
    157 #include <gnutls/x509.h>
    158 
    159 gnutls_session_t tls_session;
    160 union MHD_ConnectionInfo *ci;
    161 
    162 ci = MHD_get_connection_info (connection,
    163                               MHD_CONNECTION_INFO_GNUTLS_SESSION);
    164 tls_session = ci->tls_session;
    165 @end verbatim
    166 
    167 You can then extract the client certificate:
    168 
    169 @verbatim
    170 /**
    171  * Get the client's certificate
    172  *
    173  * @param tls_session the TLS session
    174  * @return NULL if no valid client certificate could be found, a pointer
    175  *  	to the certificate if found
    176  */
    177 static gnutls_x509_crt_t
    178 get_client_certificate (gnutls_session_t tls_session)
    179 {
    180   unsigned int listsize;
    181   const gnutls_datum_t * pcert;
    182   gnutls_certificate_status_t client_cert_status;
    183   gnutls_x509_crt_t client_cert;
    184 
    185   if (tls_session == NULL)
    186     return NULL;
    187   if (gnutls_certificate_verify_peers2(tls_session,
    188 				       &client_cert_status))
    189     return NULL;
    190   pcert = gnutls_certificate_get_peers(tls_session,
    191 				       &listsize);
    192   if ( (pcert == NULL) ||
    193        (listsize == 0))
    194     {
    195       fprintf (stderr,
    196 	       "Failed to retrieve client certificate chain\n");
    197       return NULL;
    198     }
    199   if (gnutls_x509_crt_init(&client_cert))
    200     {
    201       fprintf (stderr,
    202 	       "Failed to initialize client certificate\n");
    203       return NULL;
    204     }
    205   /* Note that by passing values between 0 and listsize here, you
    206      can get access to the CA's certs */
    207   if (gnutls_x509_crt_import(client_cert,
    208 			     &pcert[0],
    209 			     GNUTLS_X509_FMT_DER))
    210     {
    211       fprintf (stderr,
    212 	       "Failed to import client certificate\n");
    213       gnutls_x509_crt_deinit(client_cert);
    214       return NULL;
    215     }
    216   return client_cert;
    217 }
    218 @end verbatim
    219 
    220 Using the client certificate, you can then get the client's distinguished name
    221 and alternative names:
    222 
    223 @verbatim
    224 /**
    225  * Get the distinguished name from the client's certificate
    226  *
    227  * @param client_cert the client certificate
    228  * @return NULL if no dn or certificate could be found, a pointer
    229  * 			to the dn if found
    230  */
    231 char *
    232 cert_auth_get_dn(gnutls_x509_crt_c client_cert)
    233 {
    234   char* buf;
    235   size_t lbuf;
    236 
    237   lbuf = 0;
    238   gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
    239   buf = malloc(lbuf);
    240   if (buf == NULL)
    241     {
    242       fprintf (stderr,
    243 	       "Failed to allocate memory for certificate dn\n");
    244       return NULL;
    245     }
    246   gnutls_x509_crt_get_dn(client_cert, buf, &lbuf);
    247   return buf;
    248 }
    249 
    250 
    251 /**
    252  * Get the alternative name of specified type from the client's certificate
    253  *
    254  * @param client_cert the client certificate
    255  * @param nametype The requested name type
    256  * @param index The position of the alternative name if multiple names are
    257  * 			matching the requested type, 0 for the first matching name
    258  * @return NULL if no matching alternative name could be found, a pointer
    259  * 			to the alternative name if found
    260  */
    261 char *
    262 MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
    263 			   int nametype,
    264 			   unsigned int index)
    265 {
    266   char* buf;
    267   size_t lbuf;
    268   unsigned int seq;
    269   unsigned int subseq;
    270   unsigned int type;
    271   int result;
    272 
    273   subseq = 0;
    274   for (seq=0;;seq++)
    275     {
    276       lbuf = 0;
    277       result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
    278 						     &type, NULL);
    279       if (result == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
    280 	return NULL;
    281       if (nametype != (int) type)
    282 	continue;
    283       if (subseq == index)
    284 	break;
    285       subseq++;
    286     }
    287   buf = malloc(lbuf);
    288   if (buf == NULL)
    289     {
    290       fprintf (stderr,
    291 	       "Failed to allocate memory for certificate alt name\n");
    292       return NULL;
    293     }
    294   result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
    295 						 seq,
    296 						 buf,
    297 						 &lbuf,
    298 						 NULL, NULL);
    299   if (result != nametype)
    300     {
    301       fprintf (stderr,
    302 	       "Unexpected return value from gnutls: %d\n",
    303 	       result);
    304       free (buf);
    305       return NULL;
    306     }
    307   return buf;
    308 }
    309 @end verbatim
    310 
    311 Finally, you should release the memory associated with the client
    312 certificate:
    313 
    314 @verbatim
    315 gnutls_x509_crt_deinit (client_cert);
    316 @end verbatim
    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 servers acceptable signature algorithms. The certificate returned should support the servers 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.
    489