libmicrohttpd

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

tlsauthentication.inc (14977B)


      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 The @emph{MHD} library includes support for encrypting the traffic by employing
      7 TLS (the protocol formerly known as SSL). If @emph{GNU libmicrohttpd} has been configured to
      8 support this, 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. 1024 bit keys are
     18 no longer considered secure, so we use a 2048 bit key:
     19 @verbatim
     20 > openssl genrsa -out server.key 2048
     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 To avoid unnecessary error messages in the browser, the certificate needs to carry a
     29 @emph{subjectAltName} that matches the @emph{URI}, for example, "localhost" or the domain
     30 (current browsers ignore the common name and look at the @emph{subjectAltName} only).
     31 
     32 @verbatim
     33 > openssl req -days 365 -out server.pem -new -x509 -key server.key \
     34       -addext "subjectAltName=DNS:localhost"
     35 @end verbatim
     36 @noindent
     37 
     38 If you plan to have a publicly reachable server, you will need to ask a trusted third party,
     39 called @emph{Certificate Authority}, or @emph{CA}, to attest the certificate for you. This way,
     40 any visitor can make sure the server's identity is real.
     41 
     42 Whether the server's certificate is signed by us or a third party, once it has been accepted
     43 by the client, both sides will be communicating over encrypted channels. From this point on,
     44 it is the client's turn to authenticate itself. But this has already been implemented in the basic
     45 authentication scheme.
     46 
     47 
     48 @heading Changing the source code
     49 
     50 We merely have to extend the server program so that it loads the two files into memory,
     51 
     52 @verbatim
     53 int
     54 main (void)
     55 {
     56   struct MHD_Daemon *daemon;
     57   char *key_pem;
     58   char *cert_pem;
     59 
     60   key_pem = load_file (SERVERKEYFILE);
     61   cert_pem = load_file (SERVERCERTFILE);
     62 
     63   if ((key_pem == NULL) || (cert_pem == NULL))
     64   {
     65     printf ("The key/certificate files could not be read.\n");
     66     return 1;
     67   }
     68 @end verbatim
     69 @noindent
     70 
     71 and then we point the @emph{MHD} daemon to it upon initialization.
     72 @verbatim
     73 
     74   daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS,
     75   	   		     PORT, NULL, NULL,
     76                              &answer_to_connection, NULL,
     77                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
     78                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
     79                              MHD_OPTION_END);
     80 
     81   if (NULL == daemon)
     82     {
     83       printf ("%s\n", cert_pem);
     84 
     85       free (key_pem);
     86       free (cert_pem);
     87 
     88       return 1;
     89     }
     90 @end verbatim
     91 @noindent
     92 
     93 
     94 The rest consists of little new besides some additional memory cleanups.
     95 @verbatim
     96 
     97   getchar ();
     98 
     99   MHD_stop_daemon (daemon);
    100   free (key_pem);
    101   free (cert_pem);
    102 
    103   return 0;
    104 }
    105 @end verbatim
    106 @noindent
    107 
    108 
    109 The rather unexciting file loader can be found in the complete example @code{tlsauthentication.c}.
    110 
    111 
    112 @heading Remarks
    113 @itemize @bullet
    114 @item
    115 While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume
    116 standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type
    117 @code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to
    118 handle the answer properly.
    119 
    120 @item
    121 The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the
    122 certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured)
    123 that they should not accept certificates of unknown origin.
    124 
    125 @item
    126 The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to
    127 hardcode certificates in embedded devices.
    128 
    129 @item
    130 The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
    131 both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}.
    132 
    133 @end itemize
    134 
    135 
    136 @heading Client authentication
    137 
    138 You can also use MHD to authenticate the client via SSL/TLS certificates
    139 (as an alternative to using the password-based Basic or Digest authentication).
    140 To do this, you will need to link your application against @emph{gnutls}.
    141 Next, when you start the MHD daemon, you must specify the root CA that you're
    142 willing to trust:
    143 @verbatim
    144   daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS,
    145   	   		     PORT, NULL, NULL,
    146                              &answer_to_connection, NULL,
    147                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
    148                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
    149 			     MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem,
    150                              MHD_OPTION_END);
    151 @end verbatim
    152 
    153 With this, you can then obtain client certificates for each session.
    154 In order to obtain the identity of the client, you first need to
    155 obtain the raw GnuTLS session handle from @emph{MHD} using
    156 @code{MHD_get_connection_info}.
    157 
    158 @verbatim
    159 #include <gnutls/gnutls.h>
    160 #include <gnutls/x509.h>
    161 
    162 gnutls_session_t tls_session;
    163 const union MHD_ConnectionInfo *ci;
    164 
    165 ci = MHD_get_connection_info (connection,
    166                               MHD_CONNECTION_INFO_GNUTLS_SESSION);
    167 tls_session = (gnutls_session_t) ci->tls_session;
    168 @end verbatim
    169 
    170 You can then extract the client certificate:
    171 
    172 @verbatim
    173 /**
    174  * Get the client's certificate
    175  *
    176  * @param tls_session the TLS session
    177  * @return NULL if no valid client certificate could be found, a pointer
    178  *  	to the certificate if found
    179  */
    180 static gnutls_x509_crt_t
    181 get_client_certificate (gnutls_session_t tls_session)
    182 {
    183   unsigned int listsize;
    184   const gnutls_datum_t * pcert;
    185   gnutls_certificate_status_t client_cert_status;
    186   gnutls_x509_crt_t client_cert;
    187 
    188   if (tls_session == NULL)
    189     return NULL;
    190   if (gnutls_certificate_verify_peers2(tls_session,
    191 				       &client_cert_status))
    192     return NULL;
    193   if (0 != client_cert_status)
    194   {
    195     fprintf (stderr,
    196             "Failed client certificate invalid: %d\n",
    197             client_cert_status);
    198     return NULL;
    199   }
    200   pcert = gnutls_certificate_get_peers(tls_session,
    201 				       &listsize);
    202   if ( (pcert == NULL) ||
    203        (listsize == 0))
    204     {
    205       fprintf (stderr,
    206 	       "Failed to retrieve client certificate chain\n");
    207       return NULL;
    208     }
    209   if (gnutls_x509_crt_init(&client_cert))
    210     {
    211       fprintf (stderr,
    212 	       "Failed to initialize client certificate\n");
    213       return NULL;
    214     }
    215   /* Note that by passing values between 0 and listsize here, you
    216      can get access to the CA's certs */
    217   if (gnutls_x509_crt_import(client_cert,
    218 			     &pcert[0],
    219 			     GNUTLS_X509_FMT_DER))
    220     {
    221       fprintf (stderr,
    222 	       "Failed to import client certificate\n");
    223       gnutls_x509_crt_deinit(client_cert);
    224       return NULL;
    225     }
    226   return client_cert;
    227 }
    228 @end verbatim
    229 
    230 Using the client certificate, you can then get the client's distinguished name
    231 and alternative names:
    232 
    233 @verbatim
    234 /**
    235  * Get the distinguished name from the client's certificate
    236  *
    237  * @param client_cert the client certificate
    238  * @return NULL if no dn or certificate could be found, a pointer
    239  * 			to the dn if found
    240  */
    241 char *
    242 cert_auth_get_dn(gnutls_x509_crt_t client_cert)
    243 {
    244   char* buf;
    245   size_t lbuf;
    246 
    247   lbuf = 0;
    248   gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
    249   buf = malloc(lbuf);
    250   if (buf == NULL)
    251     {
    252       fprintf (stderr,
    253 	       "Failed to allocate memory for certificate dn\n");
    254       return NULL;
    255     }
    256   gnutls_x509_crt_get_dn(client_cert, buf, &lbuf);
    257   return buf;
    258 }
    259 
    260 
    261 /**
    262  * Get the alternative name of specified type from the client's certificate
    263  *
    264  * @param client_cert the client certificate
    265  * @param nametype The requested name type
    266  * @param index The position of the alternative name if multiple names are
    267  * 			matching the requested type, 0 for the first matching name
    268  * @return NULL if no matching alternative name could be found, a pointer
    269  * 			to the alternative name if found
    270  */
    271 char *
    272 MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
    273 			   int nametype,
    274 			   unsigned int index)
    275 {
    276   char* buf;
    277   size_t lbuf;
    278   unsigned int seq;
    279   unsigned int subseq;
    280   unsigned int type;
    281   int result;
    282 
    283   subseq = 0;
    284   for (seq=0;;seq++)
    285     {
    286       lbuf = 0;
    287       result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
    288 						     &type, NULL);
    289       if (result == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
    290 	return NULL;
    291       if (nametype != (int) type)
    292 	continue;
    293       if (subseq == index)
    294 	break;
    295       subseq++;
    296     }
    297   buf = malloc(lbuf);
    298   if (buf == NULL)
    299     {
    300       fprintf (stderr,
    301 	       "Failed to allocate memory for certificate alt name\n");
    302       return NULL;
    303     }
    304   result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
    305 						 seq,
    306 						 buf,
    307 						 &lbuf,
    308 						 NULL, NULL);
    309   if (result != nametype)
    310     {
    311       fprintf (stderr,
    312 	       "Unexpected return value from gnutls: %d\n",
    313 	       result);
    314       free (buf);
    315       return NULL;
    316     }
    317   return buf;
    318 }
    319 @end verbatim
    320 
    321 Finally, you should release the memory associated with the client
    322 certificate:
    323 
    324 @verbatim
    325 gnutls_x509_crt_deinit (client_cert);
    326 @end verbatim
    327 
    328 
    329 
    330 @heading Using TLS Server Name Indication (SNI)
    331 
    332 SNI enables hosting multiple domains under one IP address with TLS.  So
    333 SNI is the TLS-equivalent of virtual hosting.  To use SNI with MHD, you
    334 need at least GnuTLS 3.0.  The main change compared to the simple hosting
    335 of one domain is that you need to provide a callback instead of the key
    336 and certificate.  For example, when you start the MHD daemon, you could
    337 do this:
    338 @verbatim
    339   daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS,
    340   	   		     PORT, NULL, NULL,
    341                              &answer_to_connection, NULL,
    342                              MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
    343                              MHD_OPTION_END);
    344 @end verbatim
    345 Here, @code{sni_callback} is the name of a function that you will have to
    346 implement to retrieve the X.509 certificate for an incoming connection.
    347 The callback has type @code{gnutls_certificate_retrieve_function2} and
    348 is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2}
    349 as follows:
    350 
    351 @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)
    352 
    353 @table @var
    354 @item req_ca_dn
    355 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()}.
    356 
    357 @item pk_algos
    358 contains a list with server's acceptable signature algorithms. The certificate returned should support the server's given algorithms.
    359 
    360 @item pcert
    361 should contain a single certificate and public or a list of them.
    362 
    363 @item pcert_length
    364 is the size of the previous list.
    365 
    366 @item pkey
    367 is the private key.
    368 @end table
    369 @end deftypefn
    370 
    371 A possible implementation of this callback would look like this:
    372 
    373 @verbatim
    374 struct Hosts
    375 {
    376   struct Hosts *next;
    377   const char *hostname;
    378   gnutls_pcert_st pcrt;
    379   gnutls_privkey_t key;
    380 };
    381 
    382 static struct Hosts *hosts;
    383 
    384 int
    385 sni_callback (gnutls_session_t session,
    386               const gnutls_datum_t* req_ca_dn,
    387               int nreqs,
    388               const gnutls_pk_algorithm_t* pk_algos,
    389               int pk_algos_length,
    390               gnutls_pcert_st** pcert,
    391               unsigned int *pcert_length,
    392               gnutls_privkey_t * pkey)
    393 {
    394   char name[256];
    395   size_t name_len;
    396   struct Hosts *host;
    397   unsigned int type;
    398 
    399   name_len = sizeof (name);
    400   if (GNUTLS_E_SUCCESS !=
    401       gnutls_server_name_get (session,
    402                               name,
    403                               &name_len,
    404                               &type,
    405                               0 /* index */))
    406     return -1;
    407   for (host = hosts; NULL != host; host = host->next)
    408     if (0 == strncmp (name, host->hostname, name_len))
    409       break;
    410   if (NULL == host)
    411     {
    412       fprintf (stderr,
    413                "Need certificate for %.*s\n",
    414                (int) name_len,
    415                name);
    416       return -1;
    417     }
    418   fprintf (stderr,
    419            "Returning certificate for %.*s\n",
    420            (int) name_len,
    421            name);
    422   *pkey = host->key;
    423   *pcert_length = 1;
    424   *pcert = &host->pcrt;
    425   return 0;
    426 }
    427 @end verbatim
    428 
    429 Note that MHD cannot offer passing a closure or any other additional information
    430 to this callback, as the GnuTLS API unfortunately does not permit this at this
    431 point.
    432 
    433 The @code{hosts} list can be initialized by loading the private keys and X.509
    434 certificates from disk as follows:
    435 
    436 @verbatim
    437 static void
    438 load_keys(const char *hostname,
    439           const char *CERT_FILE,
    440           const char *KEY_FILE)
    441 {
    442   int ret;
    443   gnutls_datum_t data;
    444   struct Hosts *host;
    445 
    446   host = malloc (sizeof (struct Hosts));
    447   host->hostname = hostname;
    448   host->next = hosts;
    449   hosts = host;
    450 
    451   ret = gnutls_load_file (CERT_FILE, &data);
    452   if (ret < 0)
    453   {
    454     fprintf (stderr,
    455              "*** Error loading certificate file %s.\n",
    456              CERT_FILE);
    457     exit(1);
    458   }
    459   ret =
    460     gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
    461                                   0);
    462   if (ret < 0)
    463   {
    464     fprintf(stderr,
    465             "*** Error loading certificate file: %s\n",
    466             gnutls_strerror (ret));
    467     exit(1);
    468   }
    469   gnutls_free (data.data);
    470 
    471   ret = gnutls_load_file (KEY_FILE, &data);
    472   if (ret < 0)
    473   {
    474     fprintf (stderr,
    475              "*** Error loading key file %s.\n",
    476              KEY_FILE);
    477     exit(1);
    478   }
    479 
    480   gnutls_privkey_init (&host->key);
    481   ret =
    482     gnutls_privkey_import_x509_raw (host->key,
    483                                     &data, GNUTLS_X509_FMT_PEM,
    484                                     NULL, 0);
    485   if (ret < 0)
    486   {
    487     fprintf (stderr,
    488              "*** Error loading key file: %s\n",
    489              gnutls_strerror (ret));
    490     exit(1);
    491   }
    492   gnutls_free (data.data);
    493 }
    494 @end verbatim
    495 
    496 The code above was largely lifted from GnuTLS.  You can find other
    497 methods for initializing certificates and keys in the GnuTLS manual
    498 and source code.