| 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 initalization. |
| @verbatim |
| |
| daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | 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_SELECT_INTERNALLY | 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_SELECT_INTERNALLY | 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 server’s acceptable signature algorithms. The certificate returned should support the server’s 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 |
| certificats 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. |