add support for SNI

This commit is contained in:
Christian Grothoff committed 2013-12-21 16:28:54 +00:00
1 parent 00a9277f49
commit 9889fd9eee
14 files changed
+700 -102

No files matched your search

+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