add support for SNI

This commit is contained in:
Christian Grothoff
2013-12-21 16:28:54 +00:00
parent 00a9277f49
commit 9889fd9eee
14 changed files with 700 additions and 102 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
Sat Dec 21 17:26:08 CET 2013
Fixed an issue with a missing argument in the postexample.
Fixed issue with bogus offset increment involving sendfile
on GNU/Linux.
on GNU/Linux. Adding support for SNI. -CG
Mon Dec 9 21:41:57 CET 2013
Fix for per-worker daemon pipes enabled with
+209 -38
View File
@@ -1,6 +1,6 @@
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
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
@@ -23,7 +23,7 @@ this tutorial, we will be content with a 1024 bit key:
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.
we obtain a self-signed certificate, valid for one year.
@verbatim
> openssl req -days 365 -out server.pem -new -x509 -key server.key
@@ -38,7 +38,7 @@ 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
it is the client's turn to authenticate itself. But this has already been implemented in the basic
authentication scheme.
@@ -65,12 +65,12 @@ main ()
@end verbatim
@noindent
and then we point the @emph{MHD} daemon to it upon initalization.
and then we point the @emph{MHD} daemon to it upon initalization.
@verbatim
daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
PORT, NULL, NULL,
&answer_to_connection, NULL,
&answer_to_connection, NULL,
MHD_OPTION_HTTPS_MEM_KEY, key_pem,
MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
MHD_OPTION_END);
@@ -78,10 +78,10 @@ and then we point the @emph{MHD} daemon to it upon initalization.
if (NULL == daemon)
{
printf ("%s\n", cert_pem);
free (key_pem);
free (cert_pem);
return 1;
}
@end verbatim
@@ -96,7 +96,7 @@ The rest consists of little new besides some additional memory cleanups.
MHD_stop_daemon (daemon);
free (key_pem);
free (cert_pem);
return 0;
}
@end verbatim
@@ -110,18 +110,18 @@ The rather unexciting file loader can be found in the complete example @code{tls
@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
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)
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.
hardcode certificates in embedded devices.
@item
The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
@@ -135,12 +135,12 @@ both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}.
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
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,
daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
PORT, NULL, NULL,
&answer_to_connection, 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,
@@ -159,7 +159,7 @@ obtain the raw GnuTLS session handle from @emph{MHD} using
gnutls_session_t tls_session;
union MHD_ConnectionInfo *ci;
ci = MHD_get_connection_info (connection,
ci = MHD_get_connection_info (connection,
MHD_CONNECTION_INFO_GNUTLS_SESSION);
tls_session = ci->tls_session;
@end verbatim
@@ -172,31 +172,31 @@ You can then extract the client certificate:
*
* @param tls_session the TLS session
* @return NULL if no valid client certificate could be found, a pointer
* to the certificate if found
* to the certificate if found
*/
static gnutls_x509_crt_t
get_client_certificate (gnutls_session_t tls_session)
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)
if (tls_session == NULL)
return NULL;
if (gnutls_certificate_verify_peers2(tls_session,
&client_cert_status))
&client_cert_status))
return NULL;
pcert = gnutls_certificate_get_peers(tls_session,
pcert = gnutls_certificate_get_peers(tls_session,
&listsize);
if ( (pcert == NULL) ||
(listsize == 0))
if ( (pcert == NULL) ||
(listsize == 0))
{
fprintf (stderr,
"Failed to retrieve client certificate chain\n");
return NULL;
}
if (gnutls_x509_crt_init(&client_cert))
}
if (gnutls_x509_crt_init(&client_cert))
{
fprintf (stderr,
"Failed to initialize client certificate\n");
@@ -204,15 +204,15 @@ get_client_certificate (gnutls_session_t tls_session)
}
/* 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,
if (gnutls_x509_crt_import(client_cert,
&pcert[0],
GNUTLS_X509_FMT_DER))
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
@@ -229,15 +229,15 @@ and alternative names:
* to the dn if found
*/
char *
cert_auth_get_dn(gnutls_x509_crt_c client_cert)
cert_auth_get_dn(gnutls_x509_crt_c client_cert)
{
char* buf;
size_t lbuf;
size_t lbuf;
lbuf = 0;
gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
buf = malloc(lbuf);
if (buf == NULL)
if (buf == NULL)
{
fprintf (stderr,
"Failed to allocate memory for certificate dn\n");
@@ -260,8 +260,8 @@ cert_auth_get_dn(gnutls_x509_crt_c client_cert)
*/
char *
MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
int nametype,
unsigned int index)
int nametype,
unsigned int index)
{
char* buf;
size_t lbuf;
@@ -271,7 +271,7 @@ MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
int result;
subseq = 0;
for (seq=0;;seq++)
for (seq=0;;seq++)
{
lbuf = 0;
result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
@@ -280,21 +280,21 @@ MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
return NULL;
if (nametype != (int) type)
continue;
if (subseq == index)
if (subseq == index)
break;
subseq++;
}
buf = malloc(lbuf);
if (buf == NULL)
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,
result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
seq,
buf,
&lbuf,
&lbuf,
NULL, NULL);
if (result != nametype)
{
@@ -315,3 +315,174 @@ certificate:
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.
+9 -9
View File
@@ -1,10 +1,10 @@
\input texinfo @c -*-texinfo-*-
@finalout
@setfilename libmicrohttpd-tutorial.info
@set UPDATED 17 July 2012
@set UPDATED-MONTH July 2012
@set EDITION 0.9.22
@set VERSION 0.9.22
@set UPDATED 17 November 2013
@set UPDATED-MONTH November 2013
@set EDITION 0.9.23
@set VERSION 0.9.23
@settitle A tutorial for GNU libmicrohttpd
@c Unify all the indices into concept index.
@syncodeindex fn cp
@@ -20,11 +20,11 @@
@copying
This tutorial documents GNU libmicrohttpd version @value{VERSION}, last
updated @value{UPDATED}.
updated @value{UPDATED}.
Copyright (c) 2008 Sebastian Gerhardt.
Copyright (c) 2010, 2011, 2012 Christian Grothoff.
Copyright (c) 2010, 2011, 2012, 2013 Christian Grothoff.
@quotation
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
@@ -56,7 +56,7 @@ Free Documentation License".
@node Top
@top A Tutorial for GNU libmicrohttpd
@insertcopying
@end ifnottex
@end ifnottex
@menu
* Introduction::
@@ -97,8 +97,8 @@ Free Documentation License".
@chapter Processing POST data
@include chapters/processingpost.inc
@node Improved processing of POST data
@chapter Improved processing of POST data
@node Improved processing of POST data
@chapter Improved processing of POST data
@include chapters/largerpost.inc
@node Session management
+16
View File
@@ -701,6 +701,22 @@ are acceptable for the application. The string is passed
unchanged to gnutls_priority_init. If this option is not
specified, ``NORMAL'' is used.
@item MHD_OPTION_HTTPS_CERT_CALLBACK
@cindex SSL
@cindex TLS
@cindex SNI
Use a callback to determine which X.509 certificate should be used for
a given HTTPS connection. This option should be followed by a
argument of type "gnutls_certificate_retrieve_function2 *". This
option provides an alternative to MHD_OPTION_HTTPS_MEM_KEY and
MHD_OPTION_HTTPS_MEM_CERT. You must use this version if multiple
domains are to be hosted at the same IP address using TLS's Server
Name Indication (SNI) extension. In this case, the callback is
expected to select the correct certificate based on the SNI
information provided. The callback is expected to access the SNI data
using gnutls_server_name_get(). Using this option requires GnuTLS 3.0
or higher.
@item MHD_OPTION_DIGEST_AUTH_RANDOM
@cindex digest auth
@cindex random
+53 -53
View File
@@ -21,7 +21,7 @@
* @file demo.c
* @brief complex demonstration site: create directory index, offer
* upload via form and HTTP POST, download with mime type detection
* and error reporting (403, etc.) --- and all of this with
* and error reporting (403, etc.) --- and all of this with
* high-performance settings (large buffers, thread pool).
* If you want to benchmark MHD, this code should be used to
* run tests against. Note that the number of threads may need
@@ -222,7 +222,7 @@ struct ResponseDataContext
* Response data string.
*/
char *buf;
/**
* Number of bytes allocated for 'buf'.
*/
@@ -253,12 +253,12 @@ list_directory (struct ResponseDataContext *rdc,
struct dirent *de;
if (NULL == (dir = opendir (dirname)))
return MHD_NO;
return MHD_NO;
while (NULL != (de = readdir (dir)))
{
if ('.' == de->d_name[0])
continue;
if (sizeof (fullname) <=
if (sizeof (fullname) <=
snprintf (fullname, sizeof (fullname),
"%s/%s",
dirname, de->d_name))
@@ -278,7 +278,7 @@ list_directory (struct ResponseDataContext *rdc,
break; /* out of memory */
rdc->buf = r;
}
rdc->off += snprintf (&rdc->buf[rdc->off],
rdc->off += snprintf (&rdc->buf[rdc->off],
rdc->buf_len - rdc->off,
"<li><a href=\"/%s\">%s</a></li>\n",
fullname,
@@ -305,11 +305,11 @@ update_directory ()
char dir_name[128];
struct stat sbuf;
rdc.buf_len = initial_allocation;
rdc.buf_len = initial_allocation;
if (NULL == (rdc.buf = malloc (rdc.buf_len)))
{
update_cached_response (NULL);
return;
return;
}
rdc.off = snprintf (rdc.buf, rdc.buf_len,
"%s",
@@ -342,7 +342,7 @@ update_directory ()
rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
"<h3>%s</h3>\n",
category);
if (MHD_NO == list_directory (&rdc, dir_name))
{
free (rdc.buf);
@@ -352,7 +352,7 @@ update_directory ()
}
}
/* we ensured always +1k room, filenames are ~256 bytes,
so there is always still enough space for the footer
so there is always still enough space for the footer
without need for a final reallocation check. */
rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
"%s",
@@ -427,7 +427,7 @@ do_append (char **ret,
{
char *buf;
size_t old_len;
if (NULL == *ret)
old_len = 0;
else
@@ -471,8 +471,8 @@ process_upload_data (void *cls,
const char *filename,
const char *content_type,
const char *transfer_encoding,
const char *data,
uint64_t off,
const char *data,
uint64_t off,
size_t size)
{
struct UploadContext *uc = cls;
@@ -484,10 +484,10 @@ process_upload_data (void *cls,
return do_append (&uc->language, data, size);
if (0 != strcmp (key, "upload"))
{
fprintf (stderr,
fprintf (stderr,
"Ignoring unexpected form value `%s'\n",
key);
return MHD_YES; /* ignore */
return MHD_YES; /* ignore */
}
if (NULL == filename)
{
@@ -497,7 +497,7 @@ process_upload_data (void *cls,
if ( (NULL == uc->category) ||
(NULL == uc->language) )
{
fprintf (stderr,
fprintf (stderr,
"Missing form data for upload `%s'\n",
filename);
uc->response = request_refused_response;
@@ -523,8 +523,8 @@ process_upload_data (void *cls,
snprintf (fn, sizeof (fn),
"%s/%s",
uc->language,
uc->category);
#ifdef WINDOWS
uc->category);
#ifdef WINDOWS
(void) mkdir (fn);
#else
(void) mkdir (fn, S_IRWXU);
@@ -534,12 +534,12 @@ process_upload_data (void *cls,
"%s/%s/%s",
uc->language,
uc->category,
filename);
filename);
for (i=strlen (fn)-1;i>=0;i--)
if (! isprint ((int) fn[i]))
fn[i] = '_';
uc->fd = open (fn,
O_CREAT | O_EXCL
uc->fd = open (fn,
O_CREAT | O_EXCL
#if O_LARGEFILE
| O_LARGEFILE
#endif
@@ -547,20 +547,20 @@ process_upload_data (void *cls,
S_IRUSR | S_IWUSR);
if (-1 == uc->fd)
{
fprintf (stderr,
fprintf (stderr,
"Error opening file `%s' for upload: %s\n",
fn,
strerror (errno));
uc->response = request_refused_response;
return MHD_NO;
}
}
uc->filename = strdup (fn);
}
if ( (0 != size) &&
(size != write (uc->fd, data, size)) )
(size != write (uc->fd, data, size)) )
{
/* write failed; likely: disk full */
fprintf (stderr,
fprintf (stderr,
"Error writing to file `%s': %s\n",
uc->filename,
strerror (errno));
@@ -573,7 +573,7 @@ process_upload_data (void *cls,
free (uc->filename);
uc->filename = NULL;
}
return MHD_NO;
return MHD_NO;
}
return MHD_YES;
}
@@ -610,13 +610,13 @@ response_completed_callback (void *cls,
(void) close (uc->fd);
if (NULL != uc->filename)
{
fprintf (stderr,
fprintf (stderr,
"Upload of file `%s' failed (incomplete or aborted), removing file.\n",
uc->filename);
(void) unlink (uc->filename);
}
}
if (NULL != uc->filename)
if (NULL != uc->filename)
free (uc->filename);
free (uc);
}
@@ -624,7 +624,7 @@ response_completed_callback (void *cls,
/**
* Return the current directory listing.
*
*
* @param connection connection to return the directory for
* @return MHD_YES on success, MHD_NO on error
*/
@@ -635,12 +635,12 @@ return_directory_response (struct MHD_Connection *connection)
(void) pthread_mutex_lock (&mutex);
if (NULL == cached_directory_response)
ret = MHD_queue_response (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
ret = MHD_queue_response (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
internal_error_response);
else
ret = MHD_queue_response (connection,
MHD_HTTP_OK,
ret = MHD_queue_response (connection,
MHD_HTTP_OK,
cached_directory_response);
(void) pthread_mutex_unlock (&mutex);
return ret;
@@ -657,7 +657,7 @@ return_directory_response (struct MHD_Connection *connection)
* @param version HTTP version
* @param upload_data data from upload (PUT/POST)
* @param upload_data_size number of bytes in "upload_data"
* @param ptr our context
* @param ptr our context
* @return MHD_YES on success, MHD_NO to drop connection
*/
static int
@@ -668,11 +668,11 @@ generate_page (void *cls,
const char *version,
const char *upload_data,
size_t *upload_data_size, void **ptr)
{
{
struct MHD_Response *response;
int ret;
int fd;
struct stat buf;
struct stat buf;
if (0 != strcmp (url, "/"))
{
@@ -685,13 +685,13 @@ generate_page (void *cls,
return MHD_NO; /* unexpected method (we're not polite...) */
if ( (0 == stat (&url[1], &buf)) &&
(NULL == strstr (&url[1], "..")) &&
('/' != url[1]))
('/' != url[1]))
fd = open (&url[1], O_RDONLY);
else
fd = -1;
if (-1 == fd)
return MHD_queue_response (connection,
MHD_HTTP_NOT_FOUND,
return MHD_queue_response (connection,
MHD_HTTP_NOT_FOUND,
file_not_found_response);
/* read beginning of the file to determine mime type */
got = read (fd, file_data, sizeof (file_data));
@@ -701,12 +701,12 @@ generate_page (void *cls,
mime = NULL;
(void) lseek (fd, 0, SEEK_SET);
if (NULL == (response = MHD_create_response_from_fd (buf.st_size,
if (NULL == (response = MHD_create_response_from_fd (buf.st_size,
fd)))
{
/* internal error (i.e. out of memory) */
(void) close (fd);
return MHD_NO;
return MHD_NO;
}
/* add mime type if we had one */
@@ -714,8 +714,8 @@ generate_page (void *cls,
(void) MHD_add_response_header (response,
MHD_HTTP_HEADER_CONTENT_TYPE,
mime);
ret = MHD_queue_response (connection,
MHD_HTTP_OK,
ret = MHD_queue_response (connection,
MHD_HTTP_OK,
response);
MHD_destroy_response (response);
return ret;
@@ -744,11 +744,11 @@ generate_page (void *cls,
}
*ptr = uc;
return MHD_YES;
}
}
if (0 != *upload_data_size)
{
if (NULL == uc->response)
(void) MHD_post_process (uc->pp,
(void) MHD_post_process (uc->pp,
upload_data,
*upload_data_size);
*upload_data_size = 0;
@@ -764,8 +764,8 @@ generate_page (void *cls,
}
if (NULL != uc->response)
{
return MHD_queue_response (connection,
MHD_HTTP_FORBIDDEN,
return MHD_queue_response (connection,
MHD_HTTP_FORBIDDEN,
uc->response);
}
else
@@ -778,8 +778,8 @@ generate_page (void *cls,
return return_directory_response (connection);
/* unexpected request, refuse */
return MHD_queue_response (connection,
MHD_HTTP_FORBIDDEN,
return MHD_queue_response (connection,
MHD_HTTP_FORBIDDEN,
request_refused_response);
}
@@ -837,7 +837,7 @@ main (int argc, char *const *argv)
if ( (argc != 2) ||
(1 != sscanf (argv[1], "%u", &port)) ||
(UINT16_MAX < port) )
(UINT16_MAX < port) )
{
fprintf (stderr,
"%s PORT\n", argv[0]);
@@ -864,14 +864,14 @@ main (int argc, char *const *argv)
mark_as_html (internal_error_response);
update_directory ();
d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG
#if EPOLL_SUPPORT
#if EPOLL_SUPPORT
| MHD_USE_EPOLL_LINUX_ONLY
#endif
,
port,
NULL, NULL,
&generate_page, NULL,
MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024),
NULL, NULL,
&generate_page, NULL,
MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024),
#if PRODUCTION
MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64),
#endif
+18 -1
View File
@@ -774,7 +774,24 @@ enum MHD_OPTION
* Increment to use for growing the read buffer (followed by a
* `size_t`). Must fit within #MHD_OPTION_CONNECTION_MEMORY_LIMIT.
*/
MHD_OPTION_CONNECTION_MEMORY_INCREMENT = 21
MHD_OPTION_CONNECTION_MEMORY_INCREMENT = 21,
/**
* Use a callback to determine which X.509 certificate should be
* used for a given HTTPS connection. This option should be
* followed by a argument of type `gnutls_certificate_retrieve_function2 *`.
* This option provides an
* alternative to #MHD_OPTION_HTTPS_MEM_KEY,
* #MHD_OPTION_HTTPS_MEM_CERT. You must use this version if
* multiple domains are to be hosted at the same IP address using
* TLS's Server Name Indication (SNI) extension. In this case,
* the callback is expected to select the correct certificate
* based on the SNI information provided. The callback is expected
* to access the SNI data using `gnutls_server_name_get()`.
* Using this option requires GnuTLS 3.0 or higher.
*/
MHD_OPTION_HTTPS_CERT_CALLBACK = 22
};
+24
View File
@@ -471,6 +471,13 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon)
gnutls_datum_t key;
gnutls_datum_t cert;
#if GNUTLS_VERSION_MAJOR >= 3
if (NULL != daemon->cert_callback)
{
gnutls_certificate_set_retrieve_function2 (daemon->x509_cred,
daemon->cert_callback);
}
#endif
if (NULL != daemon->https_mem_trust)
{
cert.data = (unsigned char *) daemon->https_mem_trust;
@@ -499,6 +506,10 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon)
&cert, &key,
GNUTLS_X509_FMT_PEM);
}
#if GNUTLS_VERSION_MAJOR >= 3
if (NULL != daemon->cert_callback)
return 0;
#endif
#if HAVE_MESSAGES
MHD_DLOG (daemon, "You need to specify a certificate and key location\n");
#endif
@@ -2900,6 +2911,18 @@ parse_options_va (struct MHD_Daemon *daemon,
}
}
break;
case MHD_OPTION_HTTPS_CERT_CALLBACK:
#if GNUTLS_VERSION_MAJOR < 3
#if HAVE_MESSAGES
MHD_DLOG (daemon,
"MHD_OPTION_HTTPS_CERT_CALLBACK requires building MHD with GnuTLS >= 3.0\n");
#endif
return MHD_NO;
#else
if (0 != (daemon->options & MHD_USE_SSL))
daemon->cert_callback = va_arg (ap, gnutls_certificate_retrieve_function2 *);
break;
#endif
#endif
#ifdef DAUTH_SUPPORT
case MHD_OPTION_DIGEST_AUTH_RANDOM:
@@ -2974,6 +2997,7 @@ parse_options_va (struct MHD_Daemon *daemon,
case MHD_OPTION_HTTPS_MEM_TRUST:
case MHD_OPTION_HTTPS_PRIORITIES:
case MHD_OPTION_ARRAY:
case MHD_OPTION_HTTPS_CERT_CALLBACK:
if (MHD_YES != parse_options (daemon,
servaddr,
opt,
+11
View File
@@ -31,6 +31,9 @@
#include "microhttpd.h"
#if HTTPS_SUPPORT
#include <gnutls/gnutls.h>
#if GNUTLS_VERSION_MAJOR >= 3
#include <gnutls/abstract.h>
#endif
#endif
#if EPOLL_SUPPORT
#include <sys/epoll.h>
@@ -1161,6 +1164,14 @@ struct MHD_Daemon
*/
gnutls_dh_params_t dh_params;
#if GNUTLS_VERSION_MAJOR >= 3
/**
* Function that can be used to obtain the certificate. Needed
* for SNI support. See #MHD_OPTION_HTTPS_CERT_CALLBACK.
*/
gnutls_certificate_retrieve_function2 *cert_callback;
#endif
/**
* Pointer to our SSL/TLS key (in ASCII) in memory.
*/
+10
View File
@@ -19,6 +19,7 @@ check_PROGRAMS = \
test_tls_authentication \
test_https_multi_daemon \
test_https_get \
test_https_sni \
test_https_get_select \
test_https_get_parallel \
test_https_get_parallel_threads \
@@ -32,6 +33,7 @@ TESTS = \
test_tls_options \
test_https_multi_daemon \
test_https_get \
test_https_sni \
test_https_get_select \
test_https_get_parallel \
test_https_get_parallel_threads \
@@ -113,6 +115,14 @@ test_https_get_LDADD = \
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
@LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@
test_https_sni_SOURCES = \
test_https_sni.c \
tls_test_common.c
test_https_sni_LDADD = \
$(top_builddir)/src/testcurl/libcurl_version_check.a \
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
@LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@
test_https_get_select_SOURCES = \
test_https_get_select.c \
tls_test_common.c
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICWTCCAcICCQDc4McLp7j56DANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa
WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MTEZMBcGCSqGSIb3DQEJARYKdGVzdEBo
b3N0MTAgFw0xMzExMTcxNTE2MzdaGA8yMTEzMTAyNDE1MTYzN1owcDELMAkGA1UE
BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDExGTAXBgkqhkiG9w0BCQEWCnRl
c3RAaG9zdDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKxYiRUzfQnekQn3
6e+hP/mt/JEkiFzX5TV+E19ue2v4tc7lf+SoLEk2dVt5tGQkHjIGeFFNwCLrgXoi
h3KfP4R1IYe7NFbM+lFVwPceF3inJ75dZD80BxaXQANeh0yC/DhaVJUFNaof2S4+
7xd8zTL6M11gME+XmR8uaDvW7EBtAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAf62m
Nstj9p9u8T5A5fRnJWfoglH/zfm7IHzht0Wi047O3NFZJ0pOPqV97HuErUA5oBGg
qswnyRGyGMcvL08Bki7Q6NkY7K0ON3lq+ofTkIAHlOKMF+Y/otbjuIDHBfo63tmE
uOcr8XDQGu9R0cfh+qLgicJQd/8cFBhxsL0ls6I=
-----END CERTIFICATE-----
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCsWIkVM30J3pEJ9+nvoT/5rfyRJIhc1+U1fhNfbntr+LXO5X/k
qCxJNnVbebRkJB4yBnhRTcAi64F6Iodynz+EdSGHuzRWzPpRVcD3Hhd4pye+XWQ/
NAcWl0ADXodMgvw4WlSVBTWqH9kuPu8XfM0y+jNdYDBPl5kfLmg71uxAbQIDAQAB
AoGBAJvq9QmjLSnymtCj4pYSEai2iNpebKdiAlEkoC4j67DArupgohWhN398ryt0
rYgzTMYBKHSVnI969AYkmtlNzM1yNckRQb/G/tWrkl9re28y2nbAExtHbvLoTk2C
a/EEl1Op+JZNzLoSje7IQMVZoArD3d4aUbfux4XzlO2eRNmZAkEA2pV49QgcOTOJ
PrR5cgekonNdeMtkbZm9dhxgDk9IsYkC0iOxjn/IbeCQN3wuTQ5/yLoiiQ/CQ8w5
JndF/XpICwJBAMnY37BSRb+XKZeJWP0yjqyFJwzHXkh6IsoSF2OOXSixdiMpthLh
IPzvo6Qxsnha4VvwuDxljHzQFPgMT//CTGcCQQDMs9S+LKU50JDEX4Goj43X8RBl
cp0Poz3yYap3XDqowLYalADRgcvzUq3cuHgoA98Z3W9ASrjUg2o2ItcyBhV3AkAK
bCBgwl7Hnc6P/I+Tw2CKl/WEO2cq5uOU+4opodg9maw39JdqMiW56cXRXJ+Sh17L
mIpq0/OFHll21WvsEORRAkAnDDn/vmW25PSxPVY7tKKJCCkmtBeLQpySfpDgBF+O
QvvokKs2COivc50rmOYNvD1WSsAOspdaSoZUgFw5ikti
-----END RSA PRIVATE KEY-----
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICWTCCAcICCQCJ9nhDYTUBKjANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa
WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MjEZMBcGCSqGSIb3DQEJARYKdGVzdEBo
b3N0MjAgFw0xMzExMTcxNTE2NDNaGA8yMTEzMTAyNDE1MTY0M1owcDELMAkGA1UE
BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDIxGTAXBgkqhkiG9w0BCQEWCnRl
c3RAaG9zdDIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVK8QKMvU96iNL2
66PKm6xXw9NPHDn+o1TLF1CQRxXMrBYUrObk0961+3n3Z3BXOFHKfSV4E55CpVyz
D1Wcadlt3B9z3ke3HOi0lEa1xNJTMQK/QT3Fx/NURmNg5s9HAsqY4ocb9KHaF5Ex
0TgC0L0aRP0cK1x2TgPEHBNcgGl9AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAEXOi
9rSmVrTN5olIdowctr1vWbGwRCjCnAFXDsqakcDASNthr15LB5kr/mrA3olJjbZh
o+JDvWMY6FN8r1QXW0RL9/obbHxtJpwvAmYVMY9jrR8Rpo38p4RfXlN85g3q9PVx
5IGLaOqLf4hSnKArFL/fzXwxX9b5HBCKlXfiuqM=
-----END CERTIFICATE-----
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC1SvECjL1PeojS9uujypusV8PTTxw5/qNUyxdQkEcVzKwWFKzm
5NPetft592dwVzhRyn0leBOeQqVcsw9VnGnZbdwfc95HtxzotJRGtcTSUzECv0E9
xcfzVEZjYObPRwLKmOKHG/Sh2heRMdE4AtC9GkT9HCtcdk4DxBwTXIBpfQIDAQAB
AoGAR5Do6TfDt69IefdNeCAQKg2PWUg+fUpfEacGciAyX5GnUSQiSReF58HxHumi
ZL+ZlPgZRQRMwknO23Q4FnSjd66A3E9iHLqkWxRFJWME6E7zgtBrIjctnNu9uYM9
cw4R6qmXOL7C5sK00KXF2ep8+s+JjrZz61o85QnGGRYA94ECQQDbG6f1B8NKY9T1
1GDR/++rJbdTVQlZQcKSXMumpU6V3mEV0O9GkYaZzoYvWa3kx6c0np4karrm3QWa
u5E0q1YdAkEA09FPcmzVvIR0+sMWca8QJ/tJUxD6qYo8vLOpO4wt4iTPhGBEU+Q5
cgXmde3/plVsp0vYxK/NG5XZkoC1fbuC4QJATRGxRlLwsl3jLoUBeVxY5Q5jKYCj
xS2ITwss5vUGa1jJNW9EesH9YmRudoFI1UwU2EFixtRz4Xik3ARV0vzhUQJAfabT
50ASxqMYtczW2peMEPurMqCG4d4ES7iUMqPkcBuAErn8rntbbH19igWmOyi/rLp8
m6jiFnQdPiAmCbEbYQJAFAKiQl2ZOe3gkSh8MaQilD8Ppog6rod4SQiSmRNsDWPi
IxqXneaGDWhzynC9xr4SwuJ9D5VxW1phNyiveDuYXw==
-----END RSA PRIVATE KEY-----
+289
View File
@@ -0,0 +1,289 @@
/*
This file is part of libmicrohttpd
(C) 2013 Christian Grothoff
libmicrohttpd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 3, or (at your
option) any later version.
libmicrohttpd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with libmicrohttpd; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
/**
* @file test_https_sni.c
* @brief Testcase for libmicrohttpd HTTPS with SNI operations
* @author Christian Grothoff
*/
#include "platform.h"
#include "microhttpd.h"
#include <limits.h>
#include <sys/stat.h>
#include <curl/curl.h>
#include <gcrypt.h>
#include "tls_test_common.h"
#include <gnutls/gnutls.h>
/* This test only works with GnuTLS >= 3.0 */
#if GNUTLS_VERSION_MAJOR >= 3
#include <gnutls/abstract.h>
/**
* A hostname, server key and certificate.
*/
struct Hosts
{
struct Hosts *next;
const char *hostname;
gnutls_pcert_st pcrt;
gnutls_privkey_t key;
};
/**
* Linked list of supported TLDs and respective certificates.
*/
static struct Hosts *hosts;
/* Load the certificate and the private key.
* (This code is largely taken from GnuTLS).
*/
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);
}
/**
* @param session the session we are giving a cert for
* @param req_ca_dn NULL on server side
* @param nreqs length of req_ca_dn, and thus 0 on server side
* @param pk_algos NULL on server side
* @param pk_algos_length 0 on server side
* @param pcert list of certificates (to be set)
* @param pcert_length length of pcert (to be set)
* @param pkey the private key (to be set)
*/
static 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;
}
#if 0
fprintf (stderr,
"Returning certificate for %.*s\n",
(int) name_len,
name);
#endif
*pkey = host->key;
*pcert_length = 1;
*pcert = &host->pcrt;
return 0;
}
/* perform a HTTP GET request via SSL/TLS */
static int
do_get (const char *url)
{
CURL *c;
struct CBC cbc;
CURLcode errornum;
size_t len;
struct curl_slist *dns_info;
len = strlen (test_data);
if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
{
fprintf (stderr, MHD_E_MEM);
return -1;
}
cbc.size = len;
cbc.pos = 0;
c = curl_easy_init ();
#if DEBUG_HTTPS_TEST
curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
#endif
curl_easy_setopt (c, CURLOPT_URL, url);
curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
curl_easy_setopt (c, CURLOPT_FILE, &cbc);
/* perform peer authentication */
/* TODO merge into send_curl_req */
curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 2);
dns_info = curl_slist_append (NULL, "host1:4233:127.0.0.1");
dns_info = curl_slist_append (dns_info, "host2:4233:127.0.0.1");
curl_easy_setopt (c, CURLOPT_RESOLVE, dns_info);
curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
/* NOTE: use of CONNECTTIMEOUT without also
setting NOSIGNAL results in really weird
crashes on my system! */
curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
if (CURLE_OK != (errornum = curl_easy_perform (c)))
{
fprintf (stderr, "curl_easy_perform failed: `%s'\n",
curl_easy_strerror (errornum));
curl_easy_cleanup (c);
free (cbc.buf);
curl_slist_free_all (dns_info);
return errornum;
}
curl_easy_cleanup (c);
curl_slist_free_all (dns_info);
if (memcmp (cbc.buf, test_data, len) != 0)
{
fprintf (stderr, "Error: local file & received file differ.\n");
free (cbc.buf);
return -1;
}
free (cbc.buf);
return 0;
}
int
main (int argc, char *const *argv)
{
unsigned int error_count = 0;
struct MHD_Daemon *d;
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
#ifdef GCRYCTL_INITIALIZATION_FINISHED
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
#endif
if (0 != curl_global_init (CURL_GLOBAL_ALL))
{
fprintf (stderr, "Error: %s\n", strerror (errno));
return -1;
}
load_keys ("host1", "host1.crt", "host1.key");
load_keys ("host2", "host2.crt", "host2.key");
d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL | MHD_USE_DEBUG,
4233,
NULL, NULL,
&http_ahc, NULL,
MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
MHD_OPTION_END);
if (d == NULL)
{
fprintf (stderr, MHD_E_SERVER_INIT);
return -1;
}
error_count += do_get ("https://host1:4233/");
error_count += do_get ("https://host2:4233/");
MHD_stop_daemon (d);
curl_global_cleanup ();
return error_count != 0;
}
#else
int main ()
{
fprintf (stderr,
"SNI not supported by GnuTLS < 3.0\n");
return 0;
}
#endif