From 36e23859759bc2f8fdc6ed74c8c3a5b4d2fcd126 Mon Sep 17 00:00:00 2001
From: Apollon Oikonomopoulos <apoikos@debian.org>
Date: Fri, 25 Jan 2019 12:42:28 +0200
Subject: [PATCH] CVE-2019-3814: TLS client cert auth username handling

commit c5e19ffb0cdf5fb76c9d55c4c493e6fb0699b088
Author: Aki Tuomi <aki.tuomi@open-xchange.com>
Date:   Wed Jan 16 18:28:57 2019 +0200

    auth: Do not import empty certificate username

commit 66615c5ecab741436af5c7885e2f1710b189c7a5
Author: Aki Tuomi <aki.tuomi@open-xchange.com>
Date:   Mon Jan 21 10:54:06 2019 +0200

    auth: Fail authentication if certificate username was unexpectedly missing

commit 7c401d25a10f91591aa83ed51bfb227f88f3d2f1
Author: Aki Tuomi <aki.tuomi@open-xchange.com>
Date:   Mon Jan 21 11:36:30 2019 +0200

    login-common: Ensure we get username from certificate
---
 src/auth/auth-request-handler.c |  8 +++++++
 src/auth/auth-request.c         |  2 +-
 src/login-common/sasl-server.c  | 42 +++++++++++++++++++++++++++++++--
 3 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/src/auth/auth-request-handler.c b/src/auth/auth-request-handler.c
index 880d1cb9c..40a25ccde 100644
--- a/src/auth/auth-request-handler.c
+++ b/src/auth/auth-request-handler.c
@@ -558,6 +558,14 @@ bool auth_request_handler_auth_begin(struct auth_request_handler *handler,
 		return TRUE;
 	}
 
+	if (request->set->ssl_require_client_cert &&
+	    request->set->ssl_username_from_cert &&
+	    !request->cert_username) {
+		 auth_request_handler_auth_fail(handler, request,
+			"SSL certificate didn't contain username");
+		return TRUE;
+	}
+
 	/* Empty initial response is a "=" base64 string. Completely empty
 	   string shouldn't really be sent, but at least Exim does it,
 	   so just allow it for backwards compatibility.. */
diff --git a/src/auth/auth-request.c b/src/auth/auth-request.c
index 8bd18888b..380c474e9 100644
--- a/src/auth/auth-request.c
+++ b/src/auth/auth-request.c
@@ -412,7 +412,7 @@ bool auth_request_import_auth(struct auth_request *request,
 	else if (strcmp(key, "valid-client-cert") == 0)
 		request->valid_client_cert = TRUE;
 	else if (strcmp(key, "cert_username") == 0) {
-		if (request->set->ssl_username_from_cert) {
+		if (request->set->ssl_username_from_cert && *value != '\0') {
 			/* get username from SSL certificate. it overrides
 			   the username given by the auth mechanism. */
 			request->user = p_strdup(request->pool, value);
diff --git a/src/login-common/sasl-server.c b/src/login-common/sasl-server.c
index 5f1ffcd87..0ed7229cd 100644
--- a/src/login-common/sasl-server.c
+++ b/src/login-common/sasl-server.c
@@ -320,6 +320,37 @@ authenticate_callback(struct auth_client_request *request,
 	}
 }
 
+static bool get_cert_username(struct client *client, const char **username_r,
+			      const char **error_r)
+{
+	/* no SSL */
+	if (client->ssl_proxy == NULL) {
+		*username_r = NULL;
+		return TRUE;
+	}
+
+	/* no client certificate */
+	if (!ssl_proxy_has_valid_client_cert(client->ssl_proxy)) {
+		*username_r = NULL;
+		return TRUE;
+	}
+
+	/* get peer name */
+	const char *username = ssl_proxy_get_peer_name(client->ssl_proxy);
+
+	/* if we wanted peer name, but it was not there, fail */
+	if (client->set->auth_ssl_username_from_cert &&
+	    (username == NULL || *username == '\0')) {
+		if (client->set->auth_ssl_require_client_cert) {
+			*error_r = "Missing username in certificate";
+			return FALSE;
+		}
+	}
+
+	*username_r = username;
+	return TRUE;
+}
+
 void sasl_server_auth_begin(struct client *client,
 			    const char *service, const char *mech_name,
 			    const char *initial_resp_base64,
@@ -358,8 +389,15 @@ void sasl_server_auth_begin(struct client *client,
 	info.mech = mech->name;
 	info.service = service;
 	info.session_id = client_get_session_id(client);
-	info.cert_username = client->ssl_proxy == NULL ? NULL :
-		ssl_proxy_get_peer_name(client->ssl_proxy);
+	if (client->set->auth_ssl_username_from_cert) {
+		const char *error;
+		if (!get_cert_username(client, &info.cert_username, &error)) {
+			client_log_err(client, t_strdup_printf("Cannot get username "
+							       "from certificate: %s", error));
+			sasl_server_auth_failed(client, "Unable to validate certificate");
+			return;
+		}
+	}
 	info.flags = client_get_auth_flags(client);
 	info.local_ip = client->local_ip;
 	info.remote_ip = client->ip;
-- 
2.20.1

