* [PATCH 5/7] server: Add boolean config option "verify_peername" under "[authenticate.server]".
@ 2025-06-08 17:43 Ken Milmore
2025-06-08 18:30 ` Chuck Lever
0 siblings, 1 reply; 3+ messages in thread
From: Ken Milmore @ 2025-06-08 17:43 UTC (permalink / raw)
To: kernel-tls-handshake
If this option is set to true, then the server will pass the client hostname to GnuTLS to be verified against the client certificate.
A forward address lookup using getaddrinfo() is first performed to confirm that the client hostname maps to the client network address.
Should this forward confirmation fail, the client certificate is rejected out of hand.
Signed-off-by: Ken Milmore <ken.milmore@gmail.com>
---
src/tlshd/config.c | 3 ++
src/tlshd/server.c | 124 ++++++++++++++++++++++++++++++++++++++++++++-
src/tlshd/tlshd.h | 1 +
3 files changed, 127 insertions(+), 1 deletion(-)
diff --git a/src/tlshd/config.c b/src/tlshd/config.c
index be5d472..c647681 100644
--- a/src/tlshd/config.c
+++ b/src/tlshd/config.c
@@ -92,6 +92,9 @@ bool tlshd_config_init(const gchar *pathname)
"delay_done", NULL);
tlshd_delay_done = tmp > 0 ? (unsigned int)tmp : 0;
+ tlshd_server_verify_peername = g_key_file_get_string(tlshd_configuration,
+ "authenticate.server", "verify_peername", NULL);
+
keyrings = g_key_file_get_string_list(tlshd_configuration,
"authenticate",
"keyrings", &length, NULL);
diff --git a/src/tlshd/server.c b/src/tlshd/server.c
index 72ff6f5..4a47fe8 100644
--- a/src/tlshd/server.c
+++ b/src/tlshd/server.c
@@ -31,6 +31,7 @@
#include <stdio.h>
#include <errno.h>
#include <keyutils.h>
+#include <netdb.h>
#include <gnutls/gnutls.h>
#include <gnutls/abstract.h>
@@ -42,10 +43,127 @@
#include "tlshd.h"
#include "netlink.h"
+bool tlshd_server_verify_peername = false;
+
static gnutls_privkey_t tlshd_server_privkey;
static unsigned int tlshd_server_certs_len = TLSHD_MAX_CERTS;
static gnutls_pcert_st tlshd_server_certs[TLSHD_MAX_CERTS];
+/**
+ * tlshd_forward_confirm_hostname - Confirm that the given host name can be
+ * mapped to the given socket address by forward address lookup.
+ *
+ * Return values:
+ * %true: Forward lookup successfully mapped @hostname to @hostaddr.
+ * %false: No such mapping was found, or an error occurred.
+ */
+static bool tlshd_forward_confirm_hostname(const char *hostname,
+ const struct sockaddr *hostaddr,
+ socklen_t hostaddr_len)
+{
+ const struct sockaddr_in *ha4, *ai4;
+ const struct sockaddr_in6 *ha6, *ai6;
+ struct addrinfo hints = { 0 };
+ struct addrinfo *res, *ai;
+ bool matched = false;
+ int err;
+
+ /* Refuse to confirm an empty host name. */
+ if (hostname[0] == '\0')
+ return false;
+
+ /* Check that the supplied address is reasonable. */
+ if (hostaddr_len < sizeof(struct sockaddr_in))
+ return false;
+ if (hostaddr->sa_family == AF_INET6) {
+ if (hostaddr_len < sizeof(struct sockaddr_in6))
+ return false;
+ ha6 = (const struct sockaddr_in6 *)hostaddr;
+ /* Refuse to confirm a link-local address. */
+ if (IN6_IS_ADDR_LINKLOCAL(&ha6->sin6_addr))
+ return false;
+ if (IN6_IS_ADDR_MC_LINKLOCAL(&ha6->sin6_addr))
+ return false;
+ }
+
+ hints.ai_family = hostaddr->sa_family;
+ hints.ai_socktype = SOCK_RAW; /* To avoid duplicate results. */
+
+ /* Fetch a list of addresses matching the host name. */
+ err = getaddrinfo(hostname, NULL, &hints, &res);
+ if (err) {
+ tlshd_log_gai_error(err);
+ return false;
+ }
+
+ /* Search the returned addresses for a match against the supplied address. */
+ switch(hostaddr->sa_family) {
+ case AF_INET:
+ ha4 = (const struct sockaddr_in *)hostaddr;
+ for (ai = res; ai && !matched; ai = res->ai_next) {
+ if (ai->ai_family != AF_INET)
+ continue;
+ if (ai->ai_addrlen < sizeof(struct sockaddr_in))
+ continue;
+ ai4 = (const struct sockaddr_in *)ai->ai_addr;
+ if (ha4->sin_addr.s_addr == ai4->sin_addr.s_addr)
+ matched = true;
+ }
+ break;
+
+ case AF_INET6:
+ ha6 = (const struct sockaddr_in6 *)hostaddr;
+ for (ai = res; ai && !matched; ai = res->ai_next) {
+ if (ai->ai_family != AF_INET6)
+ continue;
+ if (ai->ai_addrlen < sizeof(struct sockaddr_in6))
+ continue;
+ ai6 = (const struct sockaddr_in6 *)ai->ai_addr;
+ if (IN6_ARE_ADDR_EQUAL(&ha6->sin6_addr, &ai6->sin6_addr))
+ matched = true;
+ }
+ break;
+ }
+
+ freeaddrinfo(res);
+ return matched;
+}
+
+/**
+ * tlshd_server_get_verify_hostname - Determine the client host name which will
+ * be passed to GnuTLS for client certificate verification.
+ *
+ * @parms: handshake parameters
+ * @hostname_p: Receives a pointer to the host name to use, which may be NULL.
+ *
+ * Return values:
+ * %true: Proceed with certificate verification.
+ * %false: Force a certificate error.
+ */
+static bool tlshd_server_get_verify_hostname(struct tlshd_handshake_parms *parms,
+ const char **hostname_p)
+{
+ const char *hostname = NULL;
+ bool verify_hostname = true;
+
+ if (tlshd_server_verify_peername) {
+ tlshd_log_debug("server: Forward-confirm peer name '%s' against address '%s'",
+ parms->peername, parms->peeraddr_txt);
+ if (tlshd_forward_confirm_hostname(parms->peername, parms->peeraddr,
+ parms->peeraddr_len)) {
+ tlshd_log_debug("server: Verify forward-confirmed peer name '%s'",
+ parms->peername);
+ hostname = parms->peername;
+ } else {
+ tlshd_log_debug("server: Peer name unconfirmed: Forcing error.");
+ verify_hostname = false;
+ }
+ }
+
+ *hostname_p = hostname;
+ return verify_hostname;
+}
+
/*
* XXX: After this point, tlshd_server_certs should be deinited on error.
*/
@@ -146,11 +264,15 @@ static int tlshd_server_x509_verify_function(gnutls_session_t session,
{
const gnutls_datum_t *peercerts;
gnutls_certificate_type_t type;
+ const char *hostname = NULL;
unsigned int i, status;
gnutls_datum_t out;
int ret;
- ret = gnutls_certificate_verify_peers3(session, NULL, &status);
+ if (!tlshd_server_get_verify_hostname(parms, &hostname))
+ goto certificate_error;
+
+ ret = gnutls_certificate_verify_peers3(session, hostname, &status);
switch (ret) {
case GNUTLS_E_SUCCESS:
break;
diff --git a/src/tlshd/tlshd.h b/src/tlshd/tlshd.h
index 29b0715..9e5b221 100644
--- a/src/tlshd/tlshd.h
+++ b/src/tlshd/tlshd.h
@@ -24,6 +24,7 @@ extern int tlshd_debug;
extern int tlshd_tls_debug;
extern unsigned int tlshd_delay_done;
extern int tlshd_stderr;
+extern bool tlshd_server_verify_peername;
struct nl_sock;
--
2.47.2
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH 5/7] server: Add boolean config option "verify_peername" under "[authenticate.server]".
2025-06-08 17:43 [PATCH 5/7] server: Add boolean config option "verify_peername" under "[authenticate.server]" Ken Milmore
@ 2025-06-08 18:30 ` Chuck Lever
2025-06-08 18:45 ` Ken Milmore
0 siblings, 1 reply; 3+ messages in thread
From: Chuck Lever @ 2025-06-08 18:30 UTC (permalink / raw)
To: Ken Milmore, kernel-tls-handshake
On 6/8/25 1:43 PM, Ken Milmore wrote:
> If this option is set to true, then the server will pass the client hostname to GnuTLS to be verified against the client certificate.
> A forward address lookup using getaddrinfo() is first performed to confirm that the client hostname maps to the client network address.
> Should this forward confirmation fail, the client certificate is rejected out of hand.
An NFS server that services both clients with DHCP-assigned addresses
and statically-assigned addresses can't enable this setting, even if
it wanted the deeper checks for it's static clients.
Perhaps a better option would be to test the socket's source address
against the certificate's SAN field, if the SAN field contains one or
more IP addresses. For a client with DHCP-assigned IP addresses, simply
don't put an IP address in its certificate.
That check could then be enabled all the time, and perhaps could be
changed to "warn" instead of "reject".
Still, this feels a little unnecessary. If a client's certificate is
used by another peer, that means both the certificate and the client's
private key are compromised. The usual way to handle that is with a
certificate revocation.
> Signed-off-by: Ken Milmore <ken.milmore@gmail.com>
> ---
> src/tlshd/config.c | 3 ++
> src/tlshd/server.c | 124 ++++++++++++++++++++++++++++++++++++++++++++-
> src/tlshd/tlshd.h | 1 +
> 3 files changed, 127 insertions(+), 1 deletion(-)
>
> diff --git a/src/tlshd/config.c b/src/tlshd/config.c
> index be5d472..c647681 100644
> --- a/src/tlshd/config.c
> +++ b/src/tlshd/config.c
> @@ -92,6 +92,9 @@ bool tlshd_config_init(const gchar *pathname)
> "delay_done", NULL);
> tlshd_delay_done = tmp > 0 ? (unsigned int)tmp : 0;
>
> + tlshd_server_verify_peername = g_key_file_get_string(tlshd_configuration,
> + "authenticate.server", "verify_peername", NULL);
> +
> keyrings = g_key_file_get_string_list(tlshd_configuration,
> "authenticate",
> "keyrings", &length, NULL);
> diff --git a/src/tlshd/server.c b/src/tlshd/server.c
> index 72ff6f5..4a47fe8 100644
> --- a/src/tlshd/server.c
> +++ b/src/tlshd/server.c
> @@ -31,6 +31,7 @@
> #include <stdio.h>
> #include <errno.h>
> #include <keyutils.h>
> +#include <netdb.h>
>
> #include <gnutls/gnutls.h>
> #include <gnutls/abstract.h>
> @@ -42,10 +43,127 @@
> #include "tlshd.h"
> #include "netlink.h"
>
> +bool tlshd_server_verify_peername = false;
> +
> static gnutls_privkey_t tlshd_server_privkey;
> static unsigned int tlshd_server_certs_len = TLSHD_MAX_CERTS;
> static gnutls_pcert_st tlshd_server_certs[TLSHD_MAX_CERTS];
>
> +/**
> + * tlshd_forward_confirm_hostname - Confirm that the given host name can be
> + * mapped to the given socket address by forward address lookup.
> + *
> + * Return values:
> + * %true: Forward lookup successfully mapped @hostname to @hostaddr.
> + * %false: No such mapping was found, or an error occurred.
> + */
> +static bool tlshd_forward_confirm_hostname(const char *hostname,
> + const struct sockaddr *hostaddr,
> + socklen_t hostaddr_len)
> +{
> + const struct sockaddr_in *ha4, *ai4;
> + const struct sockaddr_in6 *ha6, *ai6;
> + struct addrinfo hints = { 0 };
> + struct addrinfo *res, *ai;
> + bool matched = false;
> + int err;
> +
> + /* Refuse to confirm an empty host name. */
> + if (hostname[0] == '\0')
> + return false;
> +
> + /* Check that the supplied address is reasonable. */
> + if (hostaddr_len < sizeof(struct sockaddr_in))
> + return false;
> + if (hostaddr->sa_family == AF_INET6) {
> + if (hostaddr_len < sizeof(struct sockaddr_in6))
> + return false;
> + ha6 = (const struct sockaddr_in6 *)hostaddr;
> + /* Refuse to confirm a link-local address. */
> + if (IN6_IS_ADDR_LINKLOCAL(&ha6->sin6_addr))
> + return false;
> + if (IN6_IS_ADDR_MC_LINKLOCAL(&ha6->sin6_addr))
> + return false;
> + }
> +
> + hints.ai_family = hostaddr->sa_family;
> + hints.ai_socktype = SOCK_RAW; /* To avoid duplicate results. */
> +
> + /* Fetch a list of addresses matching the host name. */
> + err = getaddrinfo(hostname, NULL, &hints, &res);
> + if (err) {
> + tlshd_log_gai_error(err);
> + return false;
> + }
> +
> + /* Search the returned addresses for a match against the supplied address. */
> + switch(hostaddr->sa_family) {
> + case AF_INET:
> + ha4 = (const struct sockaddr_in *)hostaddr;
> + for (ai = res; ai && !matched; ai = res->ai_next) {
> + if (ai->ai_family != AF_INET)
> + continue;
> + if (ai->ai_addrlen < sizeof(struct sockaddr_in))
> + continue;
> + ai4 = (const struct sockaddr_in *)ai->ai_addr;
> + if (ha4->sin_addr.s_addr == ai4->sin_addr.s_addr)
> + matched = true;
> + }
> + break;
> +
> + case AF_INET6:
> + ha6 = (const struct sockaddr_in6 *)hostaddr;
> + for (ai = res; ai && !matched; ai = res->ai_next) {
> + if (ai->ai_family != AF_INET6)
> + continue;
> + if (ai->ai_addrlen < sizeof(struct sockaddr_in6))
> + continue;
> + ai6 = (const struct sockaddr_in6 *)ai->ai_addr;
> + if (IN6_ARE_ADDR_EQUAL(&ha6->sin6_addr, &ai6->sin6_addr))
> + matched = true;
> + }
> + break;
> + }
> +
> + freeaddrinfo(res);
> + return matched;
> +}
> +
> +/**
> + * tlshd_server_get_verify_hostname - Determine the client host name which will
> + * be passed to GnuTLS for client certificate verification.
> + *
> + * @parms: handshake parameters
> + * @hostname_p: Receives a pointer to the host name to use, which may be NULL.
> + *
> + * Return values:
> + * %true: Proceed with certificate verification.
> + * %false: Force a certificate error.
> + */
> +static bool tlshd_server_get_verify_hostname(struct tlshd_handshake_parms *parms,
> + const char **hostname_p)
> +{
> + const char *hostname = NULL;
> + bool verify_hostname = true;
> +
> + if (tlshd_server_verify_peername) {
> + tlshd_log_debug("server: Forward-confirm peer name '%s' against address '%s'",
> + parms->peername, parms->peeraddr_txt);
> + if (tlshd_forward_confirm_hostname(parms->peername, parms->peeraddr,
> + parms->peeraddr_len)) {
> + tlshd_log_debug("server: Verify forward-confirmed peer name '%s'",
> + parms->peername);
> + hostname = parms->peername;
> + } else {
> + tlshd_log_debug("server: Peer name unconfirmed: Forcing error.");
> + verify_hostname = false;
> + }
> + }
> +
> + *hostname_p = hostname;
> + return verify_hostname;
> +}
> +
> /*
> * XXX: After this point, tlshd_server_certs should be deinited on error.
> */
> @@ -146,11 +264,15 @@ static int tlshd_server_x509_verify_function(gnutls_session_t session,
> {
> const gnutls_datum_t *peercerts;
> gnutls_certificate_type_t type;
> + const char *hostname = NULL;
> unsigned int i, status;
> gnutls_datum_t out;
> int ret;
>
> - ret = gnutls_certificate_verify_peers3(session, NULL, &status);
> + if (!tlshd_server_get_verify_hostname(parms, &hostname))
> + goto certificate_error;
> +
> + ret = gnutls_certificate_verify_peers3(session, hostname, &status);
> switch (ret) {
> case GNUTLS_E_SUCCESS:
> break;
> diff --git a/src/tlshd/tlshd.h b/src/tlshd/tlshd.h
> index 29b0715..9e5b221 100644
> --- a/src/tlshd/tlshd.h
> +++ b/src/tlshd/tlshd.h
> @@ -24,6 +24,7 @@ extern int tlshd_debug;
> extern int tlshd_tls_debug;
> extern unsigned int tlshd_delay_done;
> extern int tlshd_stderr;
> +extern bool tlshd_server_verify_peername;
>
> struct nl_sock;
>
--
Chuck Lever
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH 5/7] server: Add boolean config option "verify_peername" under "[authenticate.server]".
2025-06-08 18:30 ` Chuck Lever
@ 2025-06-08 18:45 ` Ken Milmore
0 siblings, 0 replies; 3+ messages in thread
From: Ken Milmore @ 2025-06-08 18:45 UTC (permalink / raw)
To: Chuck Lever, kernel-tls-handshake
On 08/06/2025 19:30, Chuck Lever wrote:
> Perhaps a better option would be to test the socket's source address
> against the certificate's SAN field, if the SAN field contains one or
> more IP addresses. For a client with DHCP-assigned IP addresses, simply
> don't put an IP address in its certificate.
Well that's effectively what happens if you pass a textualized IP address to gnutls_certificate_verify_peers3(),
it is checked against the SAN as per RFC6125. But see the "verify_peeraddr" patch for that.
>
> That check could then be enabled all the time, and perhaps could be
> changed to "warn" instead of "reject".
>
> Still, this feels a little unnecessary. If a client's certificate is
> used by another peer, that means both the certificate and the client's
> private key are compromised. The usual way to handle that is with a
> certificate revocation.
>
Yes, but again, this is not to intended to guard against certificate leakage.
It is to enable secure discrimination between peers, otherwise clients can access each other's shares if they simply change IP address.
Clearly if you implement tags or some other mechanism of securely linking the certificate to the client, then that would also work
and would be more flexible.
This works and is useful for some limited use cases, which happen to include my use case! ;-)
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2025-06-08 18:45 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-08 17:43 [PATCH 5/7] server: Add boolean config option "verify_peername" under "[authenticate.server]" Ken Milmore
2025-06-08 18:30 ` Chuck Lever
2025-06-08 18:45 ` Ken Milmore
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox