Skip to content

Commit 18bc625

Browse files
committed
Add TLS-PSK authentication support via callback mechanism
This adds support for TLS Pre-Shared Key (PSK) authentication, allowing secure connections without certificates using a shared secret key. Changes: - Add psk_cred_handler() builder method to create_webserver - Add psk_cred_handler_callback typedef for PSK credential lookup - Implement psk_cred_handler_func() static callback using GnuTLS - Add MHD_OPTION_GNUTLS_PSK_CRED_HANDLER option when PSK is configured - Add AM_CONDITIONAL for HAVE_GNUTLS in configure.ac - Remove deprecated AC_HEADER_STDC macro - Add minimal_https_psk example demonstrating PSK usage - Add conditional GnuTLS linking in test/Makefile.am - Update README.md with PSK documentation and example The callback receives a username and returns the hex-encoded PSK, or an empty string for unknown users.
1 parent dff6af2 commit 18bc625

File tree

8 files changed

+190
-1
lines changed

8 files changed

+190
-1
lines changed

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver
313313
* _.https_mem_cert(**const std::string&** filename):_ String representing the path to a file containing the certificate to be used by the HTTPS daemon. This must be used in conjunction with `https_mem_key`.
314314
* _.https_mem_trust(**const std::string&** filename):_ String representing the path to a file containing the CA certificate to be used by the HTTPS daemon to authenticate and trust clients certificates. The presence of this option activates the request of certificate to the client. The request to the client is marked optional, and it is the responsibility of the server to check the presence of the certificate if needed. Note that most browsers will only present a client certificate only if they have one matching the specified CA, not sending any certificate otherwise.
315315
* _.https_priorities(**const std::string&** priority_string):_ SSL/TLS protocol version and ciphers. Must be followed by a string specifying the SSL/TLS protocol versions and ciphers that are acceptable for the application. The string is passed unchanged to gnutls_priority_init. If this option is not specified, `"NORMAL"` is used.
316+
* _.psk_cred_handler(**psk_cred_handler_callback** handler):_ Sets a callback function for TLS-PSK (Pre-Shared Key) authentication. The callback receives a username and should return the corresponding hex-encoded PSK, or an empty string if the user is unknown. This option requires `use_ssl()`, `cred_type(http::http_utils::PSK)`, and an appropriate `https_priorities()` string that enables PSK cipher suites. PSK authentication allows TLS without certificates by using a shared secret key.
316317

317318
#### Minimal example using HTTPS
318319
```cpp
@@ -346,6 +347,59 @@ To test the above example, you can run the following command from a terminal:
346347

347348
You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_https.cpp).
348349

350+
#### Minimal example using TLS-PSK
351+
```cpp
352+
#include <httpserver.hpp>
353+
#include <map>
354+
#include <string>
355+
356+
using namespace httpserver;
357+
358+
// Simple PSK database - in production, use secure storage
359+
std::map<std::string, std::string> psk_database = {
360+
{"client1", "0123456789abcdef0123456789abcdef"},
361+
{"client2", "fedcba9876543210fedcba9876543210"}
362+
};
363+
364+
// PSK credential handler callback
365+
std::string psk_handler(const std::string& username) {
366+
auto it = psk_database.find(username);
367+
if (it != psk_database.end()) {
368+
return it->second;
369+
}
370+
return ""; // Return empty string for unknown users
371+
}
372+
373+
class hello_world_resource : public http_resource {
374+
public:
375+
std::shared_ptr<http_response> render(const http_request&) {
376+
return std::shared_ptr<http_response>(
377+
new string_response("Hello, World (via TLS-PSK)!"));
378+
}
379+
};
380+
381+
int main(int argc, char** argv) {
382+
webserver ws = create_webserver(8080)
383+
.use_ssl()
384+
.cred_type(http::http_utils::PSK)
385+
.psk_cred_handler(psk_handler)
386+
.https_priorities("NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:+PSK:+DHE-PSK");
387+
388+
hello_world_resource hwr;
389+
ws.register_resource("/hello", &hwr);
390+
ws.start(true);
391+
392+
return 0;
393+
}
394+
```
395+
To test the above example, you can run the following command from a terminal using gnutls-cli:
396+
397+
gnutls-cli --pskusername=client1 --pskkey=0123456789abcdef0123456789abcdef -p 8080 localhost
398+
399+
Then type `GET /hello HTTP/1.1` followed by `Host: localhost` and two newlines.
400+
401+
You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_https_psk.cpp).
402+
349403
### IP Blacklisting/Whitelisting
350404
libhttpserver supports IP blacklisting and whitelisting as an internal feature. This section explains the startup options related with IP blacklisting/whitelisting. See the [specific section](#ip-blacklisting-and-whitelisting) to read more about the topic.
351405
* _.ban_system() and .no_ban_system:_ Can be used to enable/disable the ban system. `on` by default.

configure.ac

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ case "$host" in
8383
esac
8484

8585
# Checks for header files.
86-
AC_HEADER_STDC
8786
AC_CHECK_HEADER([stdint.h],[],[AC_MSG_ERROR("stdint.h not found")])
8887
AC_CHECK_HEADER([inttypes.h],[],[AC_MSG_ERROR("inttypes.h not found")])
8988
AC_CHECK_HEADER([errno.h],[],[AC_MSG_ERROR("errno.h not found")])
@@ -250,6 +249,8 @@ if test x"$have_gnutls" = x"yes"; then
250249
AM_CFLAGS="$AM_CXXFLAGS -DHAVE_GNUTLS"
251250
fi
252251

252+
AM_CONDITIONAL([HAVE_GNUTLS],[test x"$have_gnutls" = x"yes"])
253+
253254
DX_HTML_FEATURE(ON)
254255
DX_CHM_FEATURE(OFF)
255256
DX_CHI_FEATURE(OFF)

examples/Makefile.am

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,9 @@ benchmark_select_SOURCES = benchmark_select.cpp
4242
benchmark_threads_SOURCES = benchmark_threads.cpp
4343
benchmark_nodelay_SOURCES = benchmark_nodelay.cpp
4444
file_upload_SOURCES = file_upload.cpp
45+
46+
if HAVE_GNUTLS
47+
noinst_PROGRAMS += minimal_https_psk
48+
minimal_https_psk_SOURCES = minimal_https_psk.cpp
49+
minimal_https_psk_LDADD = $(LDADD) -lgnutls
50+
endif

examples/minimal_https_psk.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011-2024 Sebastiano Merlino
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18+
USA
19+
*/
20+
21+
#include <map>
22+
#include <memory>
23+
#include <string>
24+
25+
#include <httpserver.hpp>
26+
27+
// Simple PSK database - in production, use secure storage
28+
std::map<std::string, std::string> psk_database = {
29+
{"client1", "0123456789abcdef0123456789abcdef"},
30+
{"client2", "fedcba9876543210fedcba9876543210"}
31+
};
32+
33+
// PSK credential handler callback
34+
// Returns the hex-encoded PSK for the given username, or empty string if not found
35+
std::string psk_handler(const std::string& username) {
36+
auto it = psk_database.find(username);
37+
if (it != psk_database.end()) {
38+
return it->second;
39+
}
40+
return ""; // Return empty string for unknown users
41+
}
42+
43+
class hello_world_resource : public httpserver::http_resource {
44+
public:
45+
std::shared_ptr<httpserver::http_response> render(const httpserver::http_request&) {
46+
return std::shared_ptr<httpserver::http_response>(
47+
new httpserver::string_response("Hello, World (via TLS-PSK)!"));
48+
}
49+
};
50+
51+
int main() {
52+
httpserver::webserver ws = httpserver::create_webserver(8080)
53+
.use_ssl()
54+
.cred_type(httpserver::http::http_utils::PSK)
55+
.psk_cred_handler(psk_handler)
56+
.https_priorities("NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:+PSK:+DHE-PSK");
57+
58+
hello_world_resource hwr;
59+
ws.register_resource("/hello", &hwr);
60+
ws.start(true);
61+
62+
return 0;
63+
}

src/httpserver/create_webserver.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ typedef std::function<std::shared_ptr<http_response>(const http_request&)> rende
4646
typedef std::function<bool(const std::string&)> validator_ptr;
4747
typedef std::function<void(const std::string&)> log_access_ptr;
4848
typedef std::function<void(const std::string&)> log_error_ptr;
49+
typedef std::function<std::string(const std::string&)> psk_cred_handler_callback;
4950

5051
class create_webserver {
5152
public:
@@ -223,6 +224,11 @@ class create_webserver {
223224
return *this;
224225
}
225226

227+
create_webserver& psk_cred_handler(psk_cred_handler_callback handler) {
228+
_psk_cred_handler = handler;
229+
return *this;
230+
}
231+
226232
create_webserver& digest_auth_random(const std::string& digest_auth_random) {
227233
_digest_auth_random = digest_auth_random;
228234
return *this;
@@ -384,6 +390,7 @@ class create_webserver {
384390
std::string _https_mem_trust = "";
385391
std::string _https_priorities = "";
386392
http::http_utils::cred_type_T _cred_type = http::http_utils::NONE;
393+
psk_cred_handler_callback _psk_cred_handler = nullptr;
387394
std::string _digest_auth_random = "";
388395
int _nonce_nc_size = 0;
389396
http::http_utils::policy_T _default_policy = http::http_utils::ACCEPT;

src/httpserver/webserver.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
#include <set>
4646
#include <string>
4747

48+
#ifdef HAVE_GNUTLS
49+
#include <gnutls/gnutls.h>
50+
#endif // HAVE_GNUTLS
51+
4852
#include "httpserver/http_utils.hpp"
4953
#include "httpserver/create_webserver.hpp"
5054
#include "httpserver/details/http_endpoint.hpp"
@@ -153,6 +157,7 @@ class webserver {
153157
const std::string https_mem_trust;
154158
const std::string https_priorities;
155159
const http::http_utils::cred_type_T cred_type;
160+
const psk_cred_handler_callback psk_cred_handler;
156161
const std::string digest_auth_random;
157162
const int nonce_nc_size;
158163
bool running;
@@ -210,6 +215,12 @@ class webserver {
210215

211216
MHD_Result complete_request(MHD_Connection* connection, struct details::modded_request* mr, const char* version, const char* method);
212217

218+
#ifdef HAVE_GNUTLS
219+
static int psk_cred_handler_func(gnutls_session_t session,
220+
const char* username,
221+
gnutls_datum_t* key);
222+
#endif // HAVE_GNUTLS
223+
213224
friend MHD_Result policy_callback(void *cls, const struct sockaddr* addr, socklen_t addrlen);
214225
friend void error_log(void* cls, const char* fmt, va_list ap);
215226
friend void access_log(webserver* cls, std::string uri);

src/webserver.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ webserver::webserver(const create_webserver& params):
142142
https_mem_trust(params._https_mem_trust),
143143
https_priorities(params._https_priorities),
144144
cred_type(params._cred_type),
145+
psk_cred_handler(params._psk_cred_handler),
145146
digest_auth_random(params._digest_auth_random),
146147
nonce_nc_size(params._nonce_nc_size),
147148
running(false),
@@ -276,6 +277,11 @@ bool webserver::start(bool blocking) {
276277
if (cred_type != http_utils::NONE) {
277278
iov.push_back(gen(MHD_OPTION_HTTPS_CRED_TYPE, cred_type));
278279
}
280+
281+
if (psk_cred_handler != nullptr && use_ssl) {
282+
iov.push_back(gen(MHD_OPTION_GNUTLS_PSK_CRED_HANDLER,
283+
(intptr_t)&psk_cred_handler_func, this));
284+
}
279285
#endif // HAVE_GNUTLS
280286

281287
iov.push_back(gen(MHD_OPTION_END, 0, nullptr));
@@ -396,6 +402,43 @@ void webserver::disallow_ip(const string& ip) {
396402
allowances.erase(ip_representation(ip));
397403
}
398404

405+
#ifdef HAVE_GNUTLS
406+
int webserver::psk_cred_handler_func(gnutls_session_t session,
407+
const char* username,
408+
gnutls_datum_t* key) {
409+
webserver* ws = static_cast<webserver*>(
410+
gnutls_session_get_ptr(session));
411+
412+
if (ws == nullptr || ws->psk_cred_handler == nullptr) {
413+
return -1;
414+
}
415+
416+
std::string psk_hex = ws->psk_cred_handler(std::string(username));
417+
if (psk_hex.empty()) {
418+
return -1;
419+
}
420+
421+
// Convert hex string to binary
422+
size_t psk_len = psk_hex.size() / 2;
423+
key->data = static_cast<unsigned char*>(gnutls_malloc(psk_len));
424+
if (key->data == nullptr) {
425+
return -1;
426+
}
427+
428+
size_t output_size = psk_len;
429+
int ret = gnutls_hex2bin(psk_hex.c_str(), psk_hex.size(),
430+
key->data, &output_size);
431+
if (ret < 0) {
432+
gnutls_free(key->data);
433+
key->data = nullptr;
434+
return -1;
435+
}
436+
437+
key->size = static_cast<unsigned int>(output_size);
438+
return 0;
439+
}
440+
#endif // HAVE_GNUTLS
441+
399442
MHD_Result policy_callback(void *cls, const struct sockaddr* addr, socklen_t addrlen) {
400443
// Parameter needed to respect MHD interface, but not needed here.
401444
std::ignore = addrlen;

test/Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ http_resource_SOURCES = unit/http_resource_test.cpp
3939
noinst_HEADERS = littletest.hpp
4040
AM_CXXFLAGS += -lcurl -Wall -fPIC
4141

42+
if HAVE_GNUTLS
43+
AM_CXXFLAGS += -lgnutls
44+
endif
45+
4246
if COND_GCOV
4347
AM_CFLAGS += -O0 --coverage --no-inline
4448
AM_CXXFLAGS += -O0 --coverage --no-inline

0 commit comments

Comments
 (0)