mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
499 lines
15 KiB
PHP
499 lines
15 KiB
PHP
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.
|
|
|
|
The @emph{MHD} library includes support for encrypting the traffic by employing
|
|
TLS (the protocol formerly known as SSL). If @emph{GNU libmicrohttpd} has been configured to
|
|
support this, 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. 1024 bit keys are
|
|
no longer considered secure, so we use a 2048 bit key:
|
|
@verbatim
|
|
> openssl genrsa -out server.key 2048
|
|
@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.
|
|
|
|
To avoid unnecessary error messages in the browser, the certificate needs to carry a
|
|
@emph{subjectAltName} that matches the @emph{URI}, for example, "localhost" or the domain
|
|
(current browsers ignore the common name and look at the @emph{subjectAltName} only).
|
|
|
|
@verbatim
|
|
> openssl req -days 365 -out server.pem -new -x509 -key server.key \
|
|
-addext "subjectAltName=DNS:localhost"
|
|
@end verbatim
|
|
@noindent
|
|
|
|
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 (void)
|
|
{
|
|
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_TLS,
|
|
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_TLS,
|
|
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;
|
|
const union MHD_ConnectionInfo *ci;
|
|
|
|
ci = MHD_get_connection_info (connection,
|
|
MHD_CONNECTION_INFO_GNUTLS_SESSION);
|
|
tls_session = (gnutls_session_t) 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;
|
|
if (0 != client_cert_status)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed client certificate invalid: %d\n",
|
|
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_t 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_TLS,
|
|
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_dn
|
|
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
|
|
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.
|