diff --git a/etc/tlshd/config b/etc/tlshd/config index 1d4220e..db98130 100644 --- a/etc/tlshd/config +++ b/etc/tlshd/config @@ -41,5 +41,7 @@ nl=0 #x509.crl= #x509.certificate= #x509.private_key= +#x509.client_auth= <0|1> +#x509.client_allowed_sans= #x509.pq.certificate= #x509.pq.private_key= diff --git a/man/man5/tlshd.conf.5 b/man/man5/tlshd.conf.5 index 1d7f628..93e5dc7 100644 --- a/man/man5/tlshd.conf.5 +++ b/man/man5/tlshd.conf.5 @@ -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 diff --git a/src/tlshd/config.c b/src/tlshd/config.c index efb8bb2..191ed04 100644 --- a/src/tlshd/config.c +++ b/src/tlshd/config.c @@ -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 diff --git a/src/tlshd/log.c b/src/tlshd/log.c index a890b60..3b140fb 100644 --- a/src/tlshd/log.c +++ b/src/tlshd/log.c @@ -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) diff --git a/src/tlshd/server.c b/src/tlshd/server.c index e8fe5c8..9ac44ab 100644 --- a/src/tlshd/server.c +++ b/src/tlshd/server.c @@ -41,6 +41,8 @@ #include #include +#include + #include #include "tlshd.h" @@ -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); @@ -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 @@ -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: @@ -447,7 +519,11 @@ 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); @@ -455,7 +531,7 @@ static void tlshd_tls13_server_x509_handshake(struct tlshd_handshake_parms *parm 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; diff --git a/src/tlshd/tlshd.h b/src/tlshd/tlshd.h index 02ccab2..7c1e8f8 100644 --- a/src/tlshd/tlshd.h +++ b/src/tlshd/tlshd.h @@ -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,