From c9cebf6ed67d61736bd075419a6cf4b4a2f3a64f Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Tue, 10 Jul 2018 10:03:49 +0200
Subject: [PATCH] Remember accepted PEM cert to avoid unnecessary user input.

---
 include/freerdp/crypto/tls.h |  16 +--
 include/freerdp/settings.h   |  20 ++--
 libfreerdp/core/settings.c   |  10 +-
 libfreerdp/crypto/tls.c      | 188 +++++++++++++++++++++--------------
 4 files changed, 140 insertions(+), 94 deletions(-)

diff --git a/include/freerdp/crypto/tls.h b/include/freerdp/crypto/tls.h
index 06dc97c68..340737502 100644
--- a/include/freerdp/crypto/tls.h
+++ b/include/freerdp/crypto/tls.h
@@ -85,24 +85,24 @@ struct rdp_tls
 };
 
 #ifdef __cplusplus
- extern "C" {
+extern "C" {
 #endif
 
-FREERDP_API int tls_connect(rdpTls* tls, BIO *underlying);
-FREERDP_API BOOL tls_accept(rdpTls* tls, BIO *underlying, rdpSettings *settings);
+FREERDP_API int tls_connect(rdpTls* tls, BIO* underlying);
+FREERDP_API BOOL tls_accept(rdpTls* tls, BIO* underlying, rdpSettings* settings);
 FREERDP_API BOOL tls_send_alert(rdpTls* tls);
 
 FREERDP_API int tls_write_all(rdpTls* tls, const BYTE* data, int length);
 
 FREERDP_API int tls_set_alert_code(rdpTls* tls, int level, int description);
 
-FREERDP_API BOOL tls_match_hostname(char *pattern, int pattern_length, char *hostname);
+FREERDP_API BOOL tls_match_hostname(char* pattern, int pattern_length, char* hostname);
 FREERDP_API int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname, int port);
 FREERDP_API void tls_print_certificate_error(char* hostname, UINT16 port,
-                                             char* fingerprint, char* hosts_file);
+        char* fingerprint, char* hosts_file);
 FREERDP_API void tls_print_certificate_name_mismatch_error(
-        char* hostname, UINT16 port, char* common_name, char** alt_names,
-        int alt_names_count);
+    char* hostname, UINT16 port, char* common_name, char** alt_names,
+    int alt_names_count);
 
 FREERDP_API BOOL tls_print_error(char* func, SSL* connection, int value);
 
@@ -110,7 +110,7 @@ FREERDP_API rdpTls* tls_new(rdpSettings* settings);
 FREERDP_API void tls_free(rdpTls* tls);
 
 #ifdef __cplusplus
- }
+}
 #endif
 
 #endif /* FREERDP_CRYPTO_TLS_H */
diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h
index bf4e375a2..5970b8f82 100644
--- a/include/freerdp/settings.h
+++ b/include/freerdp/settings.h
@@ -868,7 +868,9 @@ struct rdp_settings
 	ALIGN64 char*  PasswordHash;             /* 24 */
 	ALIGN64 BOOL   WaitForOutputBufferFlush; /* 25 */
 	ALIGN64 UINT32 MaxTimeInCheckLoop;       /* 26 */
-	UINT64 padding0064[64 - 27]; /* 27 */
+	ALIGN64 char*  AcceptedCert;             /* 27 */
+	ALIGN64 UINT32 AcceptedCertLength;       /* 28 */
+	UINT64 padding0064[64 - 29]; /* 29 */
 	UINT64 padding0128[128 - 64]; /* 64 */
 
 	/**
@@ -1073,7 +1075,9 @@ struct rdp_settings
 	ALIGN64 UINT32  TargetNetAddressCount;        /* 1228 */
 	ALIGN64 char**  TargetNetAddresses;           /* 1229 */
 	ALIGN64 UINT32* TargetNetPorts;               /* 1230 */
-	UINT64 padding1280[1280 - 1231]; /* 1231 */
+	ALIGN64 char*   RedirectionAcceptedCert;      /* 1231 */
+	ALIGN64 UINT32  RedirectionAcceptedCertLength;/* 1232 */
+	UINT64 padding1280[1280 - 1233]; /* 1233 */
 
 	/**
 	 * Security
@@ -1185,7 +1189,9 @@ struct rdp_settings
 	ALIGN64 BOOL   GatewayHttpTransport;      /* 1995 */
 	ALIGN64 BOOL   GatewayUdpTransport;       /* 1996 */
 	ALIGN64 char*  GatewayAccessToken;        /* 1997 */
-	UINT64 padding2015[2015 - 1998]; /* 1998 */
+	ALIGN64 char*  GatewayAcceptedCert;       /* 1998 */
+	ALIGN64 UINT32 GatewayAcceptedCertLength; /* 1999 */
+	UINT64 padding2015[2015 - 2000]; /* 2000 */
 
 	/* Proxy */
 	ALIGN64 UINT32 ProxyType; 	 /* 2015 */
@@ -1501,7 +1507,7 @@ FREERDP_API int freerdp_addin_set_argument(ADDIN_ARGV* args, char* argument);
 FREERDP_API int freerdp_addin_replace_argument(ADDIN_ARGV* args, char* previous, char* argument);
 FREERDP_API int freerdp_addin_set_argument_value(ADDIN_ARGV* args, char* option, char* value);
 FREERDP_API int freerdp_addin_replace_argument_value(ADDIN_ARGV* args, char* previous, char* option,
-	char* value);
+        char* value);
 
 FREERDP_API BOOL freerdp_device_collection_add(rdpSettings* settings, RDPDR_DEVICE* device);
 FREERDP_API RDPDR_DEVICE* freerdp_device_collection_find(rdpSettings* settings, const char* name);
@@ -1511,13 +1517,13 @@ FREERDP_API void freerdp_device_collection_free(rdpSettings* settings);
 
 FREERDP_API BOOL freerdp_static_channel_collection_add(rdpSettings* settings, ADDIN_ARGV* channel);
 FREERDP_API ADDIN_ARGV* freerdp_static_channel_collection_find(rdpSettings* settings,
-	const char* name);
+        const char* name);
 FREERDP_API ADDIN_ARGV* freerdp_static_channel_clone(ADDIN_ARGV* channel);
 FREERDP_API void freerdp_static_channel_collection_free(rdpSettings* settings);
 
 FREERDP_API BOOL freerdp_dynamic_channel_collection_add(rdpSettings* settings, ADDIN_ARGV* channel);
 FREERDP_API ADDIN_ARGV* freerdp_dynamic_channel_collection_find(rdpSettings* settings,
-	const char* name);
+        const char* name);
 FREERDP_API ADDIN_ARGV* freerdp_dynamic_channel_clone(ADDIN_ARGV* channel);
 FREERDP_API void freerdp_dynamic_channel_collection_free(rdpSettings* settings);
 
@@ -1528,7 +1534,7 @@ FREERDP_API void freerdp_performance_flags_split(rdpSettings* settings);
 
 FREERDP_API void freerdp_set_gateway_usage_method(rdpSettings* settings, UINT32 GatewayUsageMethod);
 FREERDP_API void freerdp_update_gateway_usage_method(rdpSettings* settings, UINT32 GatewayEnabled,
-	UINT32 GatewayBypassLocal);
+        UINT32 GatewayBypassLocal);
 
 FREERDP_API BOOL freerdp_get_param_bool(rdpSettings* settings, int id);
 FREERDP_API int freerdp_set_param_bool(rdpSettings* settings, int id, BOOL param);
diff --git a/libfreerdp/core/settings.c b/libfreerdp/core/settings.c
index d76fb0ccf..d73251eb0 100644
--- a/libfreerdp/core/settings.c
+++ b/libfreerdp/core/settings.c
@@ -653,6 +653,7 @@ rdpSettings* freerdp_settings_clone(rdpSettings* settings)
 		CHECKED_STRDUP(Password); /* 22 */
 		CHECKED_STRDUP(Domain); /* 23 */
 		CHECKED_STRDUP(PasswordHash); /* 24 */
+		CHECKED_STRDUP(AcceptedCert); /* 27 */
 		_settings->ClientHostname = NULL; /* 134 */
 		_settings->ClientProductId = NULL; /* 135 */
 		CHECKED_STRDUP(AlternateShell); /* 640 */
@@ -668,6 +669,7 @@ rdpSettings* freerdp_settings_clone(rdpSettings* settings)
 		CHECKED_STRDUP(AllowedTlsCiphers); /* 1101 */
 		CHECKED_STRDUP(NtlmSamFile); /* 1103 */
 		CHECKED_STRDUP(PreconnectionBlob); /* 1155 */
+		CHECKED_STRDUP(RedirectionAcceptedCert); /* 1231 */
 		CHECKED_STRDUP(KerberosKdc); /* 1344 */
 		CHECKED_STRDUP(KerberosRealm); /* 1345 */
 		CHECKED_STRDUP(CertificateName); /* 1409 */
@@ -692,6 +694,7 @@ rdpSettings* freerdp_settings_clone(rdpSettings* settings)
 		CHECKED_STRDUP(GatewayPassword); /* 1988 */
 		CHECKED_STRDUP(GatewayDomain); /* 1989 */
 		CHECKED_STRDUP(GatewayAccessToken); /* 1997 */
+		CHECKED_STRDUP(GatewayAcceptedCert); /* 1998 */
 		CHECKED_STRDUP(ProxyHostname); /* 2016 */
 		CHECKED_STRDUP(RemoteApplicationName); /* 2113 */
 		CHECKED_STRDUP(RemoteApplicationIcon); /* 2114 */
@@ -772,7 +775,7 @@ rdpSettings* freerdp_settings_clone(rdpSettings* settings)
 		if (_settings->ChannelDefArraySize > 0)
 		{
 			_settings->ChannelDefArray = (CHANNEL_DEF*) calloc(settings->ChannelDefArraySize,
-									sizeof(CHANNEL_DEF));
+			                             sizeof(CHANNEL_DEF));
 
 			if (!_settings->ChannelDefArray)
 				goto out_fail;
@@ -789,7 +792,7 @@ rdpSettings* freerdp_settings_clone(rdpSettings* settings)
 		if (_settings->MonitorDefArraySize > 0)
 		{
 			_settings->MonitorDefArray = (rdpMonitor*) calloc(settings->MonitorDefArraySize,
-									sizeof(rdpMonitor));
+			                             sizeof(rdpMonitor));
 
 			if (!_settings->MonitorDefArray)
 				goto out_fail;
@@ -1032,6 +1035,7 @@ void freerdp_settings_free(rdpSettings* settings)
 	free(settings->Password);
 	free(settings->Domain);
 	free(settings->PasswordHash);
+	free(settings->AcceptedCert);
 	free(settings->AlternateShell);
 	free(settings->ShellWorkingDirectory);
 	free(settings->ComputerName);
@@ -1076,6 +1080,7 @@ void freerdp_settings_free(rdpSettings* settings)
 	free(settings->RedirectionDomain);
 	free(settings->RedirectionPassword);
 	free(settings->RedirectionTsvUrl);
+	free(settings->RedirectionAcceptedCert);
 	free(settings->RemoteAssistanceSessionId);
 	free(settings->RemoteAssistancePassword);
 	free(settings->RemoteAssistancePassStub);
@@ -1086,6 +1091,7 @@ void freerdp_settings_free(rdpSettings* settings)
 	free(settings->GatewayPassword);
 	free(settings->GatewayDomain);
 	free(settings->GatewayAccessToken);
+	free(settings->GatewayAcceptedCert);
 	free(settings->CertificateName);
 	free(settings->DynamicDSTTimeZoneKeyName);
 	free(settings->PreconnectionBlob);
diff --git a/libfreerdp/crypto/tls.c b/libfreerdp/crypto/tls.c
index 42be8e20f..4a3b14862 100644
--- a/libfreerdp/crypto/tls.c
+++ b/libfreerdp/crypto/tls.c
@@ -1109,6 +1109,82 @@ BOOL tls_match_hostname(char* pattern, int pattern_length, char* hostname)
 	return FALSE;
 }
 
+static BOOL is_accepted(rdpTls* tls, const BYTE* pem, size_t length)
+{
+	rdpSettings* settings = tls->settings;
+	char* AccpetedKey;
+	UINT32 AcceptedKeyLength;
+
+	if (tls->isGatewayTransport)
+	{
+		AccpetedKey = settings->GatewayAcceptedCert;
+		AcceptedKeyLength = settings->GatewayAcceptedCertLength;
+	}
+	else if (settings->RedirectionFlags != 0)
+	{
+		AccpetedKey = settings->RedirectionAcceptedCert;
+		AcceptedKeyLength = settings->RedirectionAcceptedCertLength;
+	}
+	else
+	{
+		AccpetedKey = settings->AcceptedCert;
+		AcceptedKeyLength = settings->AcceptedCertLength;
+	}
+
+	if (AcceptedKeyLength > 0)
+	{
+		if (AcceptedKeyLength == length)
+		{
+			if (memcmp(AccpetedKey, pem, AcceptedKeyLength) == 0)
+				return TRUE;
+		}
+	}
+
+	if (tls->isGatewayTransport)
+	{
+		free(settings->GatewayAcceptedCert);
+		settings->GatewayAcceptedCert = NULL;
+		settings->GatewayAcceptedCertLength = 0;
+	}
+	else if (settings->RedirectionFlags != 0)
+	{
+		free(settings->RedirectionAcceptedCert);
+		settings->RedirectionAcceptedCert = NULL;
+		settings->RedirectionAcceptedCertLength = 0;
+	}
+	else
+	{
+		free(settings->AcceptedCert);
+		settings->AcceptedCert = NULL;
+		settings->AcceptedCertLength = 0;
+	}
+
+	return FALSE;
+}
+
+static BOOL accept_cert(rdpTls* tls, const BYTE* pem, size_t length)
+{
+	rdpSettings* settings = tls->settings;
+
+	if (tls->isGatewayTransport)
+	{
+		settings->GatewayAcceptedCert = pem;
+		settings->GatewayAcceptedCertLength = length;
+	}
+	else if (settings->RedirectionFlags != 0)
+	{
+		settings->RedirectionAcceptedCert = pem;
+		settings->RedirectionAcceptedCertLength = length;
+	}
+	else
+	{
+		settings->AcceptedCert = pem;
+		settings->AcceptedCertLength = length;
+	}
+
+	return TRUE;
+}
+
 int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
                            int port)
 {
@@ -1123,83 +1199,23 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
 	BOOL hostname_match = FALSE;
 	BOOL verification_status = FALSE;
 	rdpCertificateData* certificate_data;
+	freerdp* instance = (freerdp*) tls->settings->instance;
+	DWORD length;
+	BYTE* pemCert;
+
+	if (!crypto_cert_get_public_key(cert, &pemCert, &length))
+		return -1;
+
+	/* Check, if we already accepted this key. */
+	if (is_accepted(tls, pemCert, length))
+	{
+		free(pemCert);
+		return 1;
+	}
 
 	if (tls->settings->ExternalCertificateManagement)
 	{
-		BIO* bio;
-		int status;
-		int length;
-		int offset;
-		BYTE* pemCert;
-		freerdp* instance = (freerdp*) tls->settings->instance;
-		/**
-		 * Don't manage certificates internally, leave it up entirely to the external client implementation
-		 */
-		bio = BIO_new(BIO_s_mem());
-
-		if (!bio)
-		{
-			WLog_ERR(TAG, "BIO_new() failure");
-			return -1;
-		}
-
-		status = PEM_write_bio_X509(bio, cert->px509);
-
-		if (status < 0)
-		{
-			WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
-			return -1;
-		}
-
-		offset = 0;
-		length = 2048;
-		pemCert = (BYTE*) malloc(length + 1);
-
-		if (!pemCert)
-		{
-			WLog_ERR(TAG, "error allocating pemCert");
-			return -1;
-		}
-
-		status = BIO_read(bio, pemCert, length);
-
-		if (status < 0)
-		{
-			WLog_ERR(TAG, "failed to read certificate");
-			return -1;
-		}
-
-		offset += status;
-
-		while (offset >= length)
-		{
-			int new_len;
-			BYTE* new_cert;
-			new_len = length * 2;
-			new_cert = (BYTE*) realloc(pemCert, new_len + 1);
-
-			if (!new_cert)
-				return -1;
-
-			length = new_len;
-			pemCert = new_cert;
-			status = BIO_read(bio, &pemCert[offset], length);
-
-			if (status < 0)
-				break;
-
-			offset += status;
-		}
-
-		if (status < 0)
-		{
-			WLog_ERR(TAG, "failed to read certificate");
-			return -1;
-		}
-
-		length = offset;
-		pemCert[length] = '\0';
-		status = -1;
+		int status = -1;
 
 		if (instance->VerifyX509Certificate)
 			status = instance->VerifyX509Certificate(instance, pemCert, length, hostname,
@@ -1207,8 +1223,12 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
 		else
 			WLog_ERR(TAG, "No VerifyX509Certificate callback registered!");
 
-		free(pemCert);
-		BIO_free(bio);
+		if (status > 0)
+		{
+			accept_cert(tls, pemCert, length);
+		}
+		else
+			free(pemCert);
 
 		if (status < 0)
 		{
@@ -1222,10 +1242,16 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
 
 	/* ignore certificate verification if user explicitly required it (discouraged) */
 	if (tls->settings->IgnoreCertificate)
+	{
+		free(pemCert);
 		return 1;  /* success! */
+	}
 
 	if (!tls->isGatewayTransport && tls->settings->AuthenticationLevel == 0)
+	{
+		free(pemCert);
 		return 1;  /* success! */
+	}
 
 	/* if user explicitly specified a certificate name, use it instead of the hostname */
 	if (!tls->isGatewayTransport && tls->settings->CertificateName)
@@ -1273,7 +1299,6 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
 		char* issuer;
 		char* subject;
 		char* fingerprint;
-		freerdp* instance = (freerdp*) tls->settings->instance;
 		DWORD accept_certificate = 0;
 		issuer = crypto_cert_issuer(cert->px509);
 		subject = crypto_cert_subject(cert->px509);
@@ -1384,6 +1409,15 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
 		crypto_cert_subject_alt_name_free(alt_names_count, alt_names_lengths,
 		                                  alt_names);
 
+	if (verification_status > 0)
+	{
+		accept_cert(tls, pemCert, length);
+	}
+	else
+	{
+		free(pemCert);
+	}
+
 	return (verification_status == 0) ? 0 : 1;
 }