Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions etc/tlshd/config
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ nl=0
#x509.crl= <pathname>
#x509.certificate= <pathname>
#x509.private_key= <pathname>
#x509.client_auth= <0|1>
#x509.client_allowed_sans= <client1.example.com;client2.example.com>
#x509.pq.certificate= <pathname>
#x509.pq.private_key= <pathname>
7 changes: 7 additions & 0 deletions man/man5/tlshd.conf.5
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ a handshake request when no other certificate is available.
This option specifies the pathname of a file containing
a PEM-encoded private key associated with the above certificate.
.TP
.B x509.client_auth
This option enables client certificate validation through mutual TLS.
.TP
.B x509.client_allowed_sans
This option provides a list of Subject Alternative Names (SANs) which
are to be accepted by the server as valid and allowed to connect.
.TP
.B x509.pq.certificate
This option specifies the pathname of a file containing
a PEM-encoded x.509 certificate that is to be presented during
Expand Down
36 changes: 36 additions & 0 deletions src/tlshd/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,42 @@ bool tlshd_config_get_truststore(int peer_type, char **bundle)
return true;
}

/**
* @brief Get whether mutual TLS is required
* @retval true Mutual TLS is required
* @retval false Mutual TLS is not required
*/
bool tlshd_config_get_mutual_tls_required(void)
{
return g_key_file_get_boolean(tlshd_configuration,
"authenticate.server",
"x509.client_auth", NULL);

}

/**
* @brief Get list of allowed SANs from configuration
* @param[out] sans Array of allowed SANs
* @param[out] length Number of allowed SANs
* @retval true SANs retrieved successfully
* @retval false SANs not retrieved
*/
bool tlshd_config_get_allowed_sans(gchar ***sans, gsize *length)
{
gchar **result;
result = g_key_file_get_string_list(tlshd_configuration,
"authenticate.server",
"x509.client_allowed_sans", length, NULL);

if (!result || *length == 0) {
g_strfreev(result);
return false;
}

*sans = result;
return true;
}

/**
* @brief Get CRL for {Client,Server}Hello from .conf
* @param[in] peer_type peer type
Expand Down
5 changes: 5 additions & 0 deletions src/tlshd/log.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ void tlshd_log_cert_verification_error(gnutls_session_t session)
int i;

status = gnutls_session_get_verify_cert_status(session);

if (status == 0xffffffff) {
tlshd_log_debug("Certificate rejected by configuration (check previous error message).");
return;
}

for (i = 0; tlshd_cert_status_names[i].name; i++)
if (status & tlshd_cert_status_names[i].bit)
Expand Down
112 changes: 94 additions & 18 deletions src/tlshd/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
#include <gnutls/x509.h>
#include <linux/tls.h>

#include <arpa/inet.h>

#include <glib.h>

#include "tlshd.h"
Expand Down Expand Up @@ -315,13 +317,15 @@ static int tlshd_server_get_truststore(gnutls_certificate_credentials_t cred)
* based on this information.
*
* A return value of %GNUTLS_E_CERTIFICATE_ERROR means that certificate
* verification failed. The server sends an ALERT to the client.
* verification failed, or that mutual TLS was configured and the certificate
* presented was not trusted or issued to a SAN not in the configured allowlist.
* The server sends an ALERT to the client.
*/
static int tlshd_server_x509_verify_function(gnutls_session_t session,
struct tlshd_handshake_parms *parms)
__attribute__ ((unused)) struct tlshd_handshake_parms *parms)
{
const gnutls_datum_t *peercerts;
unsigned int i, status, num_peercerts;
unsigned int status, num_peercerts;
int ret;

ret = gnutls_certificate_verify_peers3(session, NULL, &status);
Expand All @@ -330,13 +334,20 @@ static int tlshd_server_x509_verify_function(gnutls_session_t session,
break;
case GNUTLS_E_NO_CERTIFICATE_FOUND:
tlshd_log_debug("The peer offered no certificate.");
if (tlshd_config_get_mutual_tls_required())
goto certificate_error;
return GNUTLS_E_SUCCESS;
default:
tlshd_log_gnutls_error(ret);
goto certificate_error;
}
if (status)
if (status) {
gnutls_datum_t gnutls_output;
gnutls_certificate_verification_status_print(status, GNUTLS_CRT_X509, &gnutls_output, 0);
tlshd_log_debug("Certificate verification failed due to: %s", gnutls_output.data);
gnutls_free(gnutls_output.data);
goto certificate_error;
}

/* To do: Examine extended key usage information here, if we want
* to get picky. Kernel would have to tell us what to look for
Expand All @@ -351,23 +362,84 @@ static int tlshd_server_x509_verify_function(gnutls_session_t session,
tlshd_log_debug("The peer offered %u certificate(s).",
num_peercerts);

for (i = 0; i < num_peercerts; i++) {
if (tlshd_config_get_mutual_tls_required()) {
gchar **permitted_sans = NULL;
gsize permitted_sans_length;
gnutls_x509_crt_t cert;
key_serial_t peerid;
bool matched = false;
unsigned int san_id = 0;

gnutls_x509_crt_init(&cert);
ret = gnutls_x509_crt_import(cert, &peercerts[i],
GNUTLS_X509_FMT_DER);
if (ret != GNUTLS_E_SUCCESS) {
tlshd_log_gnutls_error(ret);
bool enforce_sans = tlshd_config_get_allowed_sans(&permitted_sans, &permitted_sans_length);
if (enforce_sans == true) {

gnutls_x509_crt_init(&cert);
ret = gnutls_x509_crt_import(cert, &peercerts[0], GNUTLS_X509_FMT_DER);
if (ret != GNUTLS_E_SUCCESS) {
tlshd_log_gnutls_error(ret);
gnutls_x509_crt_deinit(cert);
g_strfreev(permitted_sans);
goto certificate_error;
}

while (!matched) {
char san[256];
size_t san_length = sizeof(san);
int san_type;

san_type = gnutls_x509_crt_get_subject_alt_name(cert, san_id, san, &san_length, NULL);

if (san_type == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
break;
if (san_type < 0) {
tlshd_log_gnutls_error(san_type);
break;
}
if (san_type == GNUTLS_SAN_DNSNAME) {
tlshd_log_debug("Peer certificate SAN: %s", san);
for (gsize j = 0; j < permitted_sans_length; j++) {
if (strcmp(san, permitted_sans[j]) == 0) {
tlshd_log_debug("Peer certificate SAN %s matches an allowed SAN.", san);
matched = true;
break;
}
}
}
if (san_type == GNUTLS_SAN_IPADDRESS) {
char ip[INET6_ADDRSTRLEN];

if (san_length == 4) {
inet_ntop(AF_INET, san, ip, sizeof(ip));
} else if (san_length == 16) {
inet_ntop(AF_INET6, san, ip, sizeof(ip));
} else {
tlshd_log_debug("Unexpected IP SAN.");
gnutls_x509_crt_deinit(cert);
g_strfreev(permitted_sans);
goto certificate_error;
}

tlshd_log_debug("Peer certificate IP SAN: %s", ip);
for (gsize k = 0; k < permitted_sans_length; k++) {
if (strcmp(ip, permitted_sans[k]) == 0) {
tlshd_log_debug("Peer certificate IP SAN %s matches an allowed SAN.", ip);
matched = true;
break;
}
}
}

san_id++;
}

g_strfreev(permitted_sans);
gnutls_x509_crt_deinit(cert);
return GNUTLS_E_CERTIFICATE_ERROR;

if (!matched) {
tlshd_log_debug("Peer certificate did not contain an allowed SAN. Rejecting request.");
goto certificate_error;
}
}
peerid = UINT_MAX;
g_array_append_val(parms->remote_peerids, peerid);
gnutls_x509_crt_deinit(cert);
}

return GNUTLS_E_SUCCESS;

certificate_error:
Expand Down Expand Up @@ -447,15 +519,19 @@ static void tlshd_tls13_server_x509_handshake(struct tlshd_handshake_parms *parm
}
gnutls_certificate_set_verify_function(xcred,
tlshd_tls13_server_x509_verify_function);
gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUEST);

if (tlshd_config_get_mutual_tls_required())
gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
else
gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUEST);

tlshd_start_tls_handshake(session, parms);

if (tlshd_debug &&
gnutls_certificate_type_get(session) == GNUTLS_CRT_X509) {
const gnutls_datum_t *peercerts;
unsigned int i, num_certs = 0;

peercerts = gnutls_certificate_get_peers(session, &num_certs);
for (i = 0; i < num_certs; i++) {
gnutls_x509_crt_t cert;
Expand Down
2 changes: 2 additions & 0 deletions src/tlshd/tlshd.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ bool tlshd_config_init(const gchar *pathname, bool legacy);
void tlshd_config_shutdown(void);
bool tlshd_config_reload(void);
bool tlshd_config_get_truststore(int peer_type, char **bundle);
bool tlshd_config_get_mutual_tls_required(void);
bool tlshd_config_get_allowed_sans(gchar ***sans, gsize *length);
bool tlshd_config_get_crl(int peer_type, char **result);
bool tlshd_config_get_certs(int peer_type, gnutls_pcert_st *certs,
unsigned int *pq_certs_len,
Expand Down