libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

tlsauthentication.inc (14808B)


      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 initialization.
     69 @verbatim
     70 
     71   daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | 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_INTERNAL_POLLING_THREAD | 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 = (gnutls_session_t) 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   if (0 != client_cert_status)
    191   {
    192     fprintf (stderr,
    193             "Failed client certificate invalid: %d\n",
    194             client_cert_status);
    195     return NULL;
    196   }
    197   pcert = gnutls_certificate_get_peers(tls_session,
    198 				       &listsize);
    199   if ( (pcert == NULL) ||
    200        (listsize == 0))
    201     {
    202       fprintf (stderr,
    203 	       "Failed to retrieve client certificate chain\n");
    204       return NULL;
    205     }
    206   if (gnutls_x509_crt_init(&client_cert))
    207     {
    208       fprintf (stderr,
    209 	       "Failed to initialize client certificate\n");
    210       return NULL;
    211     }
    212   /* Note that by passing values between 0 and listsize here, you
    213      can get access to the CA's certs */
    214   if (gnutls_x509_crt_import(client_cert,
    215 			     &pcert[0],
    216 			     GNUTLS_X509_FMT_DER))
    217     {
    218       fprintf (stderr,
    219 	       "Failed to import client certificate\n");
    220       gnutls_x509_crt_deinit(client_cert);
    221       return NULL;
    222     }
    223   return client_cert;
    224 }
    225 @end verbatim
    226 
    227 Using the client certificate, you can then get the client's distinguished name
    228 and alternative names:
    229 
    230 @verbatim
    231 /**
    232  * Get the distinguished name from the client's certificate
    233  *
    234  * @param client_cert the client certificate
    235  * @return NULL if no dn or certificate could be found, a pointer
    236  * 			to the dn if found
    237  */
    238 char *
    239 cert_auth_get_dn(gnutls_x509_crt_t client_cert)
    240 {
    241   char* buf;
    242   size_t lbuf;
    243 
    244   lbuf = 0;
    245   gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
    246   buf = malloc(lbuf);
    247   if (buf == NULL)
    248     {
    249       fprintf (stderr,
    250 	       "Failed to allocate memory for certificate dn\n");
    251       return NULL;
    252     }
    253   gnutls_x509_crt_get_dn(client_cert, buf, &lbuf);
    254   return buf;
    255 }
    256 
    257 
    258 /**
    259  * Get the alternative name of specified type from the client's certificate
    260  *
    261  * @param client_cert the client certificate
    262  * @param nametype The requested name type
    263  * @param index The position of the alternative name if multiple names are
    264  * 			matching the requested type, 0 for the first matching name
    265  * @return NULL if no matching alternative name could be found, a pointer
    266  * 			to the alternative name if found
    267  */
    268 char *
    269 MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
    270 			   int nametype,
    271 			   unsigned int index)
    272 {
    273   char* buf;
    274   size_t lbuf;
    275   unsigned int seq;
    276   unsigned int subseq;
    277   unsigned int type;
    278   int result;
    279 
    280   subseq = 0;
    281   for (seq=0;;seq++)
    282     {
    283       lbuf = 0;
    284       result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
    285 						     &type, NULL);
    286       if (result == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
    287 	return NULL;
    288       if (nametype != (int) type)
    289 	continue;
    290       if (subseq == index)
    291 	break;
    292       subseq++;
    293     }
    294   buf = malloc(lbuf);
    295   if (buf == NULL)
    296     {
    297       fprintf (stderr,
    298 	       "Failed to allocate memory for certificate alt name\n");
    299       return NULL;
    300     }
    301   result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
    302 						 seq,
    303 						 buf,
    304 						 &lbuf,
    305 						 NULL, NULL);
    306   if (result != nametype)
    307     {
    308       fprintf (stderr,
    309 	       "Unexpected return value from gnutls: %d\n",
    310 	       result);
    311       free (buf);
    312       return NULL;
    313     }
    314   return buf;
    315 }
    316 @end verbatim
    317 
    318 Finally, you should release the memory associated with the client
    319 certificate:
    320 
    321 @verbatim
    322 gnutls_x509_crt_deinit (client_cert);
    323 @end verbatim
    324 
    325 
    326 
    327 @heading Using TLS Server Name Indication (SNI)
    328 
    329 SNI enables hosting multiple domains under one IP address with TLS.  So
    330 SNI is the TLS-equivalent of virtual hosting.  To use SNI with MHD, you
    331 need at least GnuTLS 3.0.  The main change compared to the simple hosting
    332 of one domain is that you need to provide a callback instead of the key
    333 and certificate.  For example, when you start the MHD daemon, you could
    334 do this:
    335 @verbatim
    336   daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
    337   	   		     PORT, NULL, NULL,
    338                              &answer_to_connection, NULL,
    339                              MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
    340                              MHD_OPTION_END);
    341 @end verbatim
    342 Here, @code{sni_callback} is the name of a function that you will have to
    343 implement to retrieve the X.509 certificate for an incoming connection.
    344 The callback has type @code{gnutls_certificate_retrieve_function2} and
    345 is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2}
    346 as follows:
    347 
    348 @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)
    349 
    350 @table @var
    351 @item req_ca_cert
    352 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()}.
    353 
    354 @item pk_algos
    355 contains a list with server’s acceptable signature algorithms. The certificate returned should support the server’s given algorithms.
    356 
    357 @item pcert
    358 should contain a single certificate and public or a list of them.
    359 
    360 @item pcert_length
    361 is the size of the previous list.
    362 
    363 @item pkey
    364 is the private key.
    365 @end table
    366 @end deftypefn
    367 
    368 A possible implementation of this callback would look like this:
    369 
    370 @verbatim
    371 struct Hosts
    372 {
    373   struct Hosts *next;
    374   const char *hostname;
    375   gnutls_pcert_st pcrt;
    376   gnutls_privkey_t key;
    377 };
    378 
    379 static struct Hosts *hosts;
    380 
    381 int
    382 sni_callback (gnutls_session_t session,
    383               const gnutls_datum_t* req_ca_dn,
    384               int nreqs,
    385               const gnutls_pk_algorithm_t* pk_algos,
    386               int pk_algos_length,
    387               gnutls_pcert_st** pcert,
    388               unsigned int *pcert_length,
    389               gnutls_privkey_t * pkey)
    390 {
    391   char name[256];
    392   size_t name_len;
    393   struct Hosts *host;
    394   unsigned int type;
    395 
    396   name_len = sizeof (name);
    397   if (GNUTLS_E_SUCCESS !=
    398       gnutls_server_name_get (session,
    399                               name,
    400                               &name_len,
    401                               &type,
    402                               0 /* index */))
    403     return -1;
    404   for (host = hosts; NULL != host; host = host->next)
    405     if (0 == strncmp (name, host->hostname, name_len))
    406       break;
    407   if (NULL == host)
    408     {
    409       fprintf (stderr,
    410                "Need certificate for %.*s\n",
    411                (int) name_len,
    412                name);
    413       return -1;
    414     }
    415   fprintf (stderr,
    416            "Returning certificate for %.*s\n",
    417            (int) name_len,
    418            name);
    419   *pkey = host->key;
    420   *pcert_length = 1;
    421   *pcert = &host->pcrt;
    422   return 0;
    423 }
    424 @end verbatim
    425 
    426 Note that MHD cannot offer passing a closure or any other additional information
    427 to this callback, as the GnuTLS API unfortunately does not permit this at this
    428 point.
    429 
    430 The @code{hosts} list can be initialized by loading the private keys and X.509
    431 certificates from disk as follows:
    432 
    433 @verbatim
    434 static void
    435 load_keys(const char *hostname,
    436           const char *CERT_FILE,
    437           const char *KEY_FILE)
    438 {
    439   int ret;
    440   gnutls_datum_t data;
    441   struct Hosts *host;
    442 
    443   host = malloc (sizeof (struct Hosts));
    444   host->hostname = hostname;
    445   host->next = hosts;
    446   hosts = host;
    447 
    448   ret = gnutls_load_file (CERT_FILE, &data);
    449   if (ret < 0)
    450   {
    451     fprintf (stderr,
    452              "*** Error loading certificate file %s.\n",
    453              CERT_FILE);
    454     exit(1);
    455   }
    456   ret =
    457     gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
    458                                   0);
    459   if (ret < 0)
    460   {
    461     fprintf(stderr,
    462             "*** Error loading certificate file: %s\n",
    463             gnutls_strerror (ret));
    464     exit(1);
    465   }
    466   gnutls_free (data.data);
    467 
    468   ret = gnutls_load_file (KEY_FILE, &data);
    469   if (ret < 0)
    470   {
    471     fprintf (stderr,
    472              "*** Error loading key file %s.\n",
    473              KEY_FILE);
    474     exit(1);
    475   }
    476 
    477   gnutls_privkey_init (&host->key);
    478   ret =
    479     gnutls_privkey_import_x509_raw (host->key,
    480                                     &data, GNUTLS_X509_FMT_PEM,
    481                                     NULL, 0);
    482   if (ret < 0)
    483   {
    484     fprintf (stderr,
    485              "*** Error loading key file: %s\n",
    486              gnutls_strerror (ret));
    487     exit(1);
    488   }
    489   gnutls_free (data.data);
    490 }
    491 @end verbatim
    492 
    493 The code above was largely lifted from GnuTLS.  You can find other
    494 methods for initializing certificates and keys in the GnuTLS manual
    495 and source code.