summaryrefslogtreecommitdiffstats
path: root/libfreerdp/crypto
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libfreerdp/crypto/CMakeLists.txt57
-rw-r--r--libfreerdp/crypto/base64.c250
-rw-r--r--libfreerdp/crypto/ber.c732
-rw-r--r--libfreerdp/crypto/cert_common.c217
-rw-r--r--libfreerdp/crypto/cert_common.h53
-rw-r--r--libfreerdp/crypto/certificate.c1732
-rw-r--r--libfreerdp/crypto/certificate.h65
-rw-r--r--libfreerdp/crypto/certificate_data.c255
-rw-r--r--libfreerdp/crypto/certificate_store.c207
-rw-r--r--libfreerdp/crypto/crypto.c260
-rw-r--r--libfreerdp/crypto/crypto.h55
-rw-r--r--libfreerdp/crypto/der.c104
-rw-r--r--libfreerdp/crypto/er.c445
-rw-r--r--libfreerdp/crypto/opensslcompat.c45
-rw-r--r--libfreerdp/crypto/opensslcompat.h64
-rw-r--r--libfreerdp/crypto/per.c602
-rw-r--r--libfreerdp/crypto/privatekey.c539
-rw-r--r--libfreerdp/crypto/privatekey.h67
-rw-r--r--libfreerdp/crypto/test/CMakeLists.txt33
-rw-r--r--libfreerdp/crypto/test/TestBase64.c178
-rw-r--r--libfreerdp/crypto/test/TestKnownHosts.c394
-rw-r--r--libfreerdp/crypto/test/Test_x509_cert_info.pem41
-rw-r--r--libfreerdp/crypto/test/Test_x509_utils.c241
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha1_cert.pem10
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha256_cert.pem10
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha384_cert.pem10
-rw-r--r--libfreerdp/crypto/test/ecdsa_sha512_cert.pem10
-rw-r--r--libfreerdp/crypto/test/known_hosts/known_hosts2
-rw-r--r--libfreerdp/crypto/test/known_hosts/known_hosts.v22
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha1_cert.pem19
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha256_cert.pem21
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem21
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha384_cert.pem21
-rw-r--r--libfreerdp/crypto/test/rsa_pss_sha512_cert.pem21
-rw-r--r--libfreerdp/crypto/tls.c1887
-rw-r--r--libfreerdp/crypto/tls.h131
-rw-r--r--libfreerdp/crypto/x509_utils.c986
-rw-r--r--libfreerdp/crypto/x509_utils.h64
42 files changed, 9927 insertions, 0 deletions
diff --git a/libfreerdp/crypto/CMakeLists.txt b/libfreerdp/crypto/CMakeLists.txt
new file mode 100644
index 0000000..16a73d6
--- /dev/null
+++ b/libfreerdp/crypto/CMakeLists.txt
@@ -0,0 +1,57 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# libfreerdp-crypto cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(MODULE_NAME "freerdp-crypto")
+set(MODULE_PREFIX "FREERDP_CRYPTO")
+
+freerdp_module_add(
+ er.c
+ der.c
+ ber.c
+ per.c
+ base64.c
+ x509_utils.c
+ x509_utils.h
+ cert_common.h
+ cert_common.c
+ privatekey.c
+ privatekey.h
+ certificate.c
+ certificate.h
+ certificate_data.c
+ certificate_store.c
+ crypto.c
+ tls.c
+ tls.h
+ opensslcompat.c)
+
+freerdp_include_directory_add(${OPENSSL_INCLUDE_DIR})
+
+freerdp_library_add(${OPENSSL_LIBRARIES})
+
+if(MBEDTLS_FOUND)
+ freerdp_include_directory_add(${MBEDTLS_INCLUDE_DIR})
+ freerdp_library_add(${MBEDTLS_LIBRARIES})
+endif()
+
+if(WIN32)
+ freerdp_library_add(ws2_32)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/libfreerdp/crypto/base64.c b/libfreerdp/crypto/base64.c
new file mode 100644
index 0000000..683d297
--- /dev/null
+++ b/libfreerdp/crypto/base64.c
@@ -0,0 +1,250 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Base64 Encoding & Decoding
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/crypto/crypto.h>
+
+static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char base64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+static char* base64_encode_ex(const char* alphabet, const BYTE* data, size_t length, BOOL pad,
+ BOOL crLf, size_t lineSize)
+{
+ int c = 0;
+ const BYTE* q = NULL;
+ char* p = NULL;
+ char* ret = NULL;
+ int blocks = 0;
+ size_t outLen = (length + 3) * 4 / 3;
+ size_t extra = 0;
+ if (crLf)
+ {
+ size_t nCrLf = (outLen + lineSize - 1) / lineSize;
+ extra = nCrLf * 2;
+ }
+ size_t outCounter = 0;
+
+ q = data;
+ p = ret = (char*)malloc(outLen + extra + 1ull);
+ if (!p)
+ return NULL;
+
+ /* b1, b2, b3 are input bytes
+ *
+ * 0 1 2
+ * 012345678901234567890123
+ * | b1 | b2 | b3 |
+ *
+ * [ c1 ] [ c3 ]
+ * [ c2 ] [ c4 ]
+ *
+ * c1, c2, c3, c4 are output chars in base64
+ */
+
+ /* first treat complete blocks */
+ blocks = length - (length % 3);
+ for (int i = 0; i < blocks; i += 3, q += 3)
+ {
+ c = (q[0] << 16) + (q[1] << 8) + q[2];
+
+ *p++ = alphabet[(c & 0x00FC0000) >> 18];
+ *p++ = alphabet[(c & 0x0003F000) >> 12];
+ *p++ = alphabet[(c & 0x00000FC0) >> 6];
+ *p++ = alphabet[c & 0x0000003F];
+
+ outCounter += 4;
+ if (crLf && (outCounter % lineSize == 0))
+ {
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ }
+
+ /* then remainder */
+ switch (length % 3)
+ {
+ case 0:
+ break;
+ case 1:
+ c = (q[0] << 16);
+ *p++ = alphabet[(c & 0x00FC0000) >> 18];
+ *p++ = alphabet[(c & 0x0003F000) >> 12];
+ if (pad)
+ {
+ *p++ = '=';
+ *p++ = '=';
+ }
+ break;
+ case 2:
+ c = (q[0] << 16) + (q[1] << 8);
+ *p++ = alphabet[(c & 0x00FC0000) >> 18];
+ *p++ = alphabet[(c & 0x0003F000) >> 12];
+ *p++ = alphabet[(c & 0x00000FC0) >> 6];
+ if (pad)
+ *p++ = '=';
+ break;
+ }
+
+ if (crLf && length % 3)
+ {
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ *p = 0;
+
+ return ret;
+}
+
+static char* base64_encode(const char* alphabet, const BYTE* data, size_t length, BOOL pad)
+{
+ return base64_encode_ex(alphabet, data, length, pad, FALSE, 64);
+}
+
+static int base64_decode_char(const char* alphabet, char c)
+{
+ char* p = NULL;
+
+ if (c == '\0')
+ return -1;
+
+ if ((p = strchr(alphabet, c)))
+ return p - alphabet;
+
+ return -1;
+}
+
+static void* base64_decode(const char* alphabet, const char* s, size_t length, size_t* data_len,
+ BOOL pad)
+{
+ int n[4];
+ BYTE* q = NULL;
+ BYTE* data = NULL;
+ size_t nBlocks = 0;
+ size_t outputLen = 0;
+ int remainder = length % 4;
+
+ if ((pad && remainder > 0) || (remainder == 1))
+ return NULL;
+
+ if (!pad && remainder)
+ length += 4 - remainder;
+
+ q = data = (BYTE*)malloc(length / 4 * 3 + 1);
+ if (!q)
+ return NULL;
+
+ /* first treat complete blocks */
+ nBlocks = (length / 4);
+ outputLen = 0;
+
+ if (nBlocks < 1)
+ {
+ free(data);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < nBlocks - 1; i++, q += 3)
+ {
+ n[0] = base64_decode_char(alphabet, *s++);
+ n[1] = base64_decode_char(alphabet, *s++);
+ n[2] = base64_decode_char(alphabet, *s++);
+ n[3] = base64_decode_char(alphabet, *s++);
+
+ if ((n[0] == -1) || (n[1] == -1) || (n[2] == -1) || (n[3] == -1))
+ goto out_free;
+
+ q[0] = (n[0] << 2) + (n[1] >> 4);
+ q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
+ q[2] = ((n[2] & 3) << 6) + n[3];
+ outputLen += 3;
+ }
+
+ /* treat last block */
+ n[0] = base64_decode_char(alphabet, *s++);
+ n[1] = base64_decode_char(alphabet, *s++);
+ if ((n[0] == -1) || (n[1] == -1))
+ goto out_free;
+
+ n[2] = remainder == 2 ? -1 : base64_decode_char(alphabet, *s++);
+ n[3] = remainder >= 2 ? -1 : base64_decode_char(alphabet, *s++);
+
+ q[0] = (n[0] << 2) + (n[1] >> 4);
+ if (n[2] == -1)
+ {
+ /* XX== */
+ outputLen += 1;
+ if (n[3] != -1)
+ goto out_free;
+
+ q[1] = ((n[1] & 15) << 4);
+ }
+ else if (n[3] == -1)
+ {
+ /* yyy= */
+ outputLen += 2;
+ q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
+ q[2] = ((n[2] & 3) << 6);
+ }
+ else
+ {
+ /* XXXX */
+ outputLen += 3;
+ q[0] = (n[0] << 2) + (n[1] >> 4);
+ q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
+ q[2] = ((n[2] & 3) << 6) + n[3];
+ }
+
+ if (data_len)
+ *data_len = outputLen;
+ data[outputLen] = '\0';
+
+ return data;
+out_free:
+ free(data);
+ return NULL;
+}
+
+char* crypto_base64_encode_ex(const BYTE* data, size_t length, BOOL withCrLf)
+{
+ return base64_encode_ex(base64, data, length, TRUE, withCrLf, 64);
+}
+
+char* crypto_base64_encode(const BYTE* data, size_t length)
+{
+ return base64_encode(base64, data, length, TRUE);
+}
+
+void crypto_base64_decode(const char* enc_data, size_t length, BYTE** dec_data, size_t* res_length)
+{
+ *dec_data = base64_decode(base64, enc_data, length, res_length, TRUE);
+}
+
+char* crypto_base64url_encode(const BYTE* data, size_t length)
+{
+ return base64_encode(base64url, data, length, FALSE);
+}
+
+void crypto_base64url_decode(const char* enc_data, size_t length, BYTE** dec_data,
+ size_t* res_length)
+{
+ *dec_data = base64_decode(base64url, enc_data, length, res_length, FALSE);
+}
diff --git a/libfreerdp/crypto/ber.c b/libfreerdp/crypto/ber.c
new file mode 100644
index 0000000..6a64e2a
--- /dev/null
+++ b/libfreerdp/crypto/ber.c
@@ -0,0 +1,732 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Basic Encoding Rules (BER)
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/string.h>
+
+#include <freerdp/log.h>
+#include <freerdp/crypto/ber.h>
+
+#define TAG FREERDP_TAG("crypto")
+
+BOOL ber_read_length(wStream* s, size_t* length)
+{
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte & 0x80)
+ {
+ byte &= ~(0x80);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, byte))
+ return FALSE;
+
+ if (byte == 1)
+ Stream_Read_UINT8(s, *length);
+ else if (byte == 2)
+ Stream_Read_UINT16_BE(s, *length);
+ else
+ {
+ WLog_ERR(TAG, "ber: unexpected byte 0x%02" PRIx8 ", expected [1,2]", byte);
+ return FALSE;
+ }
+ }
+ else
+ {
+ *length = byte;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write BER length.
+ * @param s stream
+ * @param length length
+ */
+
+size_t ber_write_length(wStream* s, size_t length)
+{
+ WINPR_ASSERT(s);
+
+ if (length > 0xFF)
+ {
+ WINPR_ASSERT(length <= UINT16_MAX);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 3);
+ Stream_Write_UINT8(s, 0x80 ^ 2);
+ Stream_Write_UINT16_BE(s, (UINT16)length);
+ return 3;
+ }
+
+ WINPR_ASSERT(length <= UINT8_MAX);
+ if (length > 0x7F)
+ {
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 2);
+ Stream_Write_UINT8(s, 0x80 ^ 1);
+ Stream_Write_UINT8(s, (UINT8)length);
+ return 2;
+ }
+
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 1);
+ Stream_Write_UINT8(s, (UINT8)length);
+ return 1;
+}
+
+size_t _ber_sizeof_length(size_t length)
+{
+ if (length > 0xFF)
+ return 3;
+
+ if (length > 0x7F)
+ return 2;
+
+ return 1;
+}
+
+/**
+ * Read BER Universal tag.
+ *
+ * @param s The stream to read from
+ * @param tag BER universally-defined tag
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL ber_read_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ BYTE byte = 0;
+ const BYTE expect = (BER_CLASS_UNIV | BER_PC(pc) | (BER_TAG_MASK & tag));
+
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write BER Universal tag.
+ * @param s stream
+ * @param tag BER universally-defined tag
+ * @param pc primitive (FALSE) or constructed (TRUE)
+ */
+
+size_t ber_write_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ WINPR_ASSERT(s);
+ Stream_Write_UINT8(s, (BER_CLASS_UNIV | BER_PC(pc)) | (BER_TAG_MASK & tag));
+ return 1;
+}
+
+/**
+ * Read BER Application tag.
+ * @param s stream
+ * @param tag BER application-defined tag
+ * @param length length
+ */
+
+BOOL ber_read_application_tag(wStream* s, BYTE tag, size_t* length)
+{
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+
+ if (tag > 30)
+ {
+ const BYTE expect = ((BER_CLASS_APPL | BER_CONSTRUCT) | BER_TAG_MASK);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != tag)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, tag);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+ }
+ else
+ {
+ const BYTE expect = ((BER_CLASS_APPL | BER_CONSTRUCT) | (BER_TAG_MASK & tag));
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write BER Application tag.
+ * @param s stream
+ * @param tag BER application-defined tag
+ * @param length length
+ */
+
+void ber_write_application_tag(wStream* s, BYTE tag, size_t length)
+{
+ WINPR_ASSERT(s);
+
+ if (tag > 30)
+ {
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 2);
+ Stream_Write_UINT8(s, (BER_CLASS_APPL | BER_CONSTRUCT) | BER_TAG_MASK);
+ Stream_Write_UINT8(s, tag);
+ ber_write_length(s, length);
+ }
+ else
+ {
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 1);
+ Stream_Write_UINT8(s, (BER_CLASS_APPL | BER_CONSTRUCT) | (BER_TAG_MASK & tag));
+ ber_write_length(s, length);
+ }
+}
+
+BOOL ber_read_contextual_tag(wStream* s, BYTE tag, size_t* length, BOOL pc)
+{
+ const BYTE expect = ((BER_CLASS_CTXT | BER_PC(pc)) | (BER_TAG_MASK & tag));
+ BYTE byte = 0;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(length);
+
+ if (Stream_GetRemainingLength(s) < 1)
+ {
+ WLog_VRB(TAG, "short data, got %" PRIuz ", expected %" PRIuz, Stream_GetRemainingLength(s),
+ 1);
+ return FALSE;
+ }
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_VRB(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ Stream_Rewind(s, 1);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+}
+
+size_t ber_write_contextual_tag(wStream* s, BYTE tag, size_t length, BOOL pc)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) >= 1);
+ Stream_Write_UINT8(s, (BER_CLASS_CTXT | BER_PC(pc)) | (BER_TAG_MASK & tag));
+ return 1 + ber_write_length(s, length);
+}
+
+size_t ber_sizeof_contextual_tag(size_t length)
+{
+ return 1 + _ber_sizeof_length(length);
+}
+
+BOOL ber_read_sequence_tag(wStream* s, size_t* length)
+{
+ const BYTE expect = ((BER_CLASS_UNIV | BER_CONSTRUCT) | (BER_TAG_SEQUENCE_OF));
+ BYTE byte = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != expect)
+ {
+ WLog_WARN(TAG, "invalid tag, got 0x%02" PRIx8 ", expected 0x%02" PRIx8, byte, expect);
+ return FALSE;
+ }
+
+ return ber_read_length(s, length);
+}
+
+/**
+ * Write BER SEQUENCE tag.
+ * @param s stream
+ * @param length length
+ */
+
+size_t ber_write_sequence_tag(wStream* s, size_t length)
+{
+ Stream_Write_UINT8(s, (BER_CLASS_UNIV | BER_CONSTRUCT) | (BER_TAG_MASK & BER_TAG_SEQUENCE));
+ return 1 + ber_write_length(s, length);
+}
+
+size_t ber_sizeof_sequence(size_t length)
+{
+ return 1 + _ber_sizeof_length(length) + length;
+}
+
+size_t ber_sizeof_sequence_tag(size_t length)
+{
+ return 1 + _ber_sizeof_length(length);
+}
+
+BOOL ber_read_enumerated(wStream* s, BYTE* enumerated, BYTE count)
+{
+ size_t length = 0;
+
+ WINPR_ASSERT(enumerated);
+
+ if (!ber_read_universal_tag(s, BER_TAG_ENUMERATED, FALSE) || !ber_read_length(s, &length))
+ return FALSE;
+
+ if (length != 1)
+ {
+ WLog_WARN(TAG, "short data, got %" PRIuz ", expected %" PRIuz, length, 1);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *enumerated);
+
+ /* check that enumerated value falls within expected range */
+ if (*enumerated + 1 > count)
+ {
+ WLog_WARN(TAG, "invalid data, expected %" PRIu8 " < %" PRIu8, *enumerated, count);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void ber_write_enumerated(wStream* s, BYTE enumerated, BYTE count)
+{
+ ber_write_universal_tag(s, BER_TAG_ENUMERATED, FALSE);
+ ber_write_length(s, 1);
+ Stream_Write_UINT8(s, enumerated);
+}
+
+BOOL ber_read_bit_string(wStream* s, size_t* length, BYTE* padding)
+{
+ if (!ber_read_universal_tag(s, BER_TAG_BIT_STRING, FALSE) || !ber_read_length(s, length))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *padding);
+ return TRUE;
+}
+
+/**
+ * Write a BER OCTET_STRING
+ * @param s stream
+ * @param oct_str octet string
+ * @param length string length
+ */
+
+size_t ber_write_octet_string(wStream* s, const BYTE* oct_str, size_t length)
+{
+ size_t size = 0;
+
+ WINPR_ASSERT(oct_str || (length == 0));
+ size += ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ size += ber_write_length(s, length);
+ Stream_Write(s, oct_str, length);
+ size += length;
+ return size;
+}
+
+size_t ber_write_contextual_octet_string(wStream* s, BYTE tag, const BYTE* oct_str, size_t length)
+{
+ size_t inner = ber_sizeof_octet_string(length);
+ size_t ret = 0;
+ size_t r = 0;
+
+ ret = ber_write_contextual_tag(s, tag, inner, TRUE);
+ if (!ret)
+ return 0;
+
+ r = ber_write_octet_string(s, oct_str, length);
+ if (!r)
+ return 0;
+ return ret + r;
+}
+
+size_t ber_write_char_to_unicode_octet_string(wStream* s, const char* str)
+{
+ WINPR_ASSERT(str);
+ size_t size = 0;
+ size_t length = strlen(str) + 1;
+ size += ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ size += ber_write_length(s, length * sizeof(WCHAR));
+
+ if (Stream_Write_UTF16_String_From_UTF8(s, length, str, length, TRUE) < 0)
+ return 0;
+ return size + length * sizeof(WCHAR);
+}
+
+size_t ber_write_contextual_unicode_octet_string(wStream* s, BYTE tag, LPWSTR str)
+{
+ WINPR_ASSERT(str);
+ size_t len = _wcslen(str) * sizeof(WCHAR);
+ size_t inner_len = ber_sizeof_octet_string(len);
+ size_t ret = 0;
+
+ ret = ber_write_contextual_tag(s, tag, inner_len, TRUE);
+ return ret + ber_write_octet_string(s, (const BYTE*)str, len);
+}
+
+size_t ber_write_contextual_char_to_unicode_octet_string(wStream* s, BYTE tag, const char* str)
+{
+ size_t ret = 0;
+ size_t len = strlen(str);
+ size_t inner_len = ber_sizeof_octet_string(len * 2);
+
+ WINPR_ASSERT(Stream_GetRemainingCapacity(s) < ber_sizeof_contextual_tag(inner_len) + inner_len);
+
+ ret = ber_write_contextual_tag(s, tag, inner_len, TRUE);
+ ret += ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ ret += ber_write_length(s, len * sizeof(WCHAR));
+
+ if (Stream_Write_UTF16_String_From_UTF8(s, len, str, len, TRUE) < 0)
+ return 0;
+
+ return ret + len;
+}
+
+BOOL ber_read_unicode_octet_string(wStream* s, LPWSTR* str)
+{
+ LPWSTR ret = NULL;
+ size_t length = 0;
+
+ if (!ber_read_octet_string_tag(s, &length))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ ret = calloc(1, length + 2);
+ if (!ret)
+ return FALSE;
+
+ memcpy(ret, Stream_ConstPointer(s), length);
+ ret[length / 2] = 0;
+ Stream_Seek(s, length);
+ *str = ret;
+ return TRUE;
+}
+
+BOOL ber_read_char_from_unicode_octet_string(wStream* s, char** str)
+{
+ size_t length = 0;
+ char* ptr = NULL;
+
+ *str = NULL;
+ if (!ber_read_octet_string_tag(s, &length))
+ return FALSE;
+
+ ptr = Stream_Read_UTF16_String_As_UTF8(s, length / sizeof(WCHAR), NULL);
+ if (!ptr)
+ return FALSE;
+ *str = ptr;
+ return TRUE;
+}
+
+BOOL ber_read_octet_string_tag(wStream* s, size_t* length)
+{
+ return ber_read_universal_tag(s, BER_TAG_OCTET_STRING, FALSE) && ber_read_length(s, length);
+}
+
+BOOL ber_read_octet_string(wStream* s, BYTE** content, size_t* length)
+{
+ BYTE* ret = NULL;
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(content);
+ WINPR_ASSERT(length);
+
+ if (!ber_read_octet_string_tag(s, length))
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, *length))
+ return FALSE;
+
+ ret = malloc(*length);
+ if (!ret)
+ return FALSE;
+
+ Stream_Read(s, ret, *length);
+ *content = ret;
+ return TRUE;
+}
+
+size_t ber_write_octet_string_tag(wStream* s, size_t length)
+{
+ ber_write_universal_tag(s, BER_TAG_OCTET_STRING, FALSE);
+ ber_write_length(s, length);
+ return 1 + _ber_sizeof_length(length);
+}
+
+size_t ber_sizeof_octet_string(size_t length)
+{
+ return 1 + _ber_sizeof_length(length) + length;
+}
+
+size_t ber_sizeof_contextual_octet_string(size_t length)
+{
+ size_t ret = ber_sizeof_octet_string(length);
+ return ber_sizeof_contextual_tag(ret) + ret;
+}
+
+/** \brief Read a BER BOOLEAN
+ *
+ * @param s The stream to read from.
+ * @param value A pointer to the value read, must not be NULL
+ *
+ * \return \b TRUE for success, \b FALSE for any failure
+ */
+
+BOOL ber_read_BOOL(wStream* s, BOOL* value)
+{
+ size_t length = 0;
+ BYTE v = 0;
+
+ WINPR_ASSERT(value);
+ if (!ber_read_universal_tag(s, BER_TAG_BOOLEAN, FALSE) || !ber_read_length(s, &length))
+ return FALSE;
+
+ if (length != 1)
+ {
+ WLog_WARN(TAG, "short data, got %" PRIuz ", expected %" PRIuz, length, 1);
+ return FALSE;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, v);
+ *value = (v ? TRUE : FALSE);
+ return TRUE;
+}
+
+/**
+ * Write a BER BOOLEAN
+ *
+ * @param s A pointer to the stream to write to
+ * @param value The value to write
+ */
+
+void ber_write_BOOL(wStream* s, BOOL value)
+{
+ ber_write_universal_tag(s, BER_TAG_BOOLEAN, FALSE);
+ ber_write_length(s, 1);
+ Stream_Write_UINT8(s, (value == TRUE) ? 0xFF : 0);
+}
+
+BOOL ber_read_integer(wStream* s, UINT32* value)
+{
+ size_t length = 0;
+
+ WINPR_ASSERT(s);
+
+ if (!ber_read_universal_tag(s, BER_TAG_INTEGER, FALSE))
+ return FALSE;
+ if (!ber_read_length(s, &length))
+ return FALSE;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ if (value == NULL)
+ {
+ // even if we don't care the integer value, check the announced size
+ return Stream_SafeSeek(s, length);
+ }
+
+ if (length == 1)
+ {
+ Stream_Read_UINT8(s, *value);
+ }
+ else if (length == 2)
+ {
+ Stream_Read_UINT16_BE(s, *value);
+ }
+ else if (length == 3)
+ {
+ BYTE byte = 0;
+ Stream_Read_UINT8(s, byte);
+ Stream_Read_UINT16_BE(s, *value);
+ *value += (byte << 16);
+ }
+ else if (length == 4)
+ {
+ Stream_Read_UINT32_BE(s, *value);
+ }
+ else if (length == 8)
+ {
+ WLog_ERR(TAG, "should implement reading an 8 bytes integer");
+ return FALSE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "should implement reading an integer with length=%" PRIuz, length);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a BER INTEGER
+ *
+ * @param s A pointer to the stream to write to
+ * @param value The value to write
+ *
+ * @return The size in bytes that were written
+ */
+
+size_t ber_write_integer(wStream* s, UINT32 value)
+{
+ WINPR_ASSERT(s);
+
+ if (value < 0x80)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 1);
+
+ Stream_Write_UINT8(s, value);
+ return 3;
+ }
+ else if (value < 0x8000)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 2);
+
+ Stream_Write_UINT16_BE(s, value);
+ return 4;
+ }
+ else if (value < 0x800000)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 3);
+
+ Stream_Write_UINT8(s, (value >> 16));
+ Stream_Write_UINT16_BE(s, (value & 0xFFFF));
+ return 5;
+ }
+ else if (value < 0x80000000)
+ {
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 4);
+
+ Stream_Write_UINT32_BE(s, value);
+ return 6;
+ }
+ else
+ {
+ /* treat as signed integer i.e. NT/HRESULT error codes */
+ ber_write_universal_tag(s, BER_TAG_INTEGER, FALSE);
+ ber_write_length(s, 4);
+
+ Stream_Write_UINT32_BE(s, value);
+ return 6;
+ }
+}
+
+size_t ber_write_contextual_integer(wStream* s, BYTE tag, UINT32 value)
+{
+ size_t len = ber_sizeof_integer(value);
+
+ WINPR_ASSERT(s);
+
+ WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, len + 5));
+
+ len += ber_write_contextual_tag(s, tag, len, TRUE);
+ ber_write_integer(s, value);
+ return len;
+}
+
+size_t ber_sizeof_integer(UINT32 value)
+{
+ if (value < 0x80)
+ {
+ return 3;
+ }
+ else if (value < 0x8000)
+ {
+ return 4;
+ }
+ else if (value < 0x800000)
+ {
+ return 5;
+ }
+ else if (value < 0x80000000)
+ {
+ return 6;
+ }
+ else
+ {
+ /* treat as signed integer i.e. NT/HRESULT error codes */
+ return 6;
+ }
+}
+
+size_t ber_sizeof_contextual_integer(UINT32 value)
+{
+ size_t intSize = ber_sizeof_integer(value);
+ return ber_sizeof_contextual_tag(intSize) + intSize;
+}
+
+BOOL ber_read_integer_length(wStream* s, size_t* length)
+{
+ return ber_read_universal_tag(s, BER_TAG_INTEGER, FALSE) && ber_read_length(s, length);
+}
diff --git a/libfreerdp/crypto/cert_common.c b/libfreerdp/crypto/cert_common.c
new file mode 100644
index 0000000..60ef60f
--- /dev/null
+++ b/libfreerdp/crypto/cert_common.c
@@ -0,0 +1,217 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/crypto.h>
+
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#include "cert_common.h"
+#include "crypto.h"
+#include "opensslcompat.h"
+
+#define TAG FREERDP_TAG("core")
+
+static BOOL cert_info_allocate(rdpCertInfo* info, size_t size);
+
+BOOL read_bignum(BYTE** dst, UINT32* length, const BIGNUM* num, BOOL alloc)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(length);
+ WINPR_ASSERT(num);
+
+ if (alloc)
+ {
+ free(*dst);
+ *dst = NULL;
+ *length = 0;
+ }
+
+ const int len = BN_num_bytes(num);
+ if (len < 0)
+ return FALSE;
+
+ if (!alloc)
+ {
+ if (*length < (UINT32)len)
+ return FALSE;
+ }
+
+ if (len > 0)
+ {
+ if (alloc)
+ {
+ *dst = malloc((size_t)len);
+ if (!*dst)
+ return FALSE;
+ }
+ BN_bn2bin(num, *dst);
+ crypto_reverse(*dst, (size_t)len);
+ *length = (UINT32)len;
+ }
+
+ return TRUE;
+}
+
+BOOL cert_info_create(rdpCertInfo* dst, const BIGNUM* rsa, const BIGNUM* rsa_e)
+{
+ const rdpCertInfo empty = { 0 };
+
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(rsa);
+
+ *dst = empty;
+
+ if (!read_bignum(&dst->Modulus, &dst->ModulusLength, rsa, TRUE))
+ goto fail;
+
+ UINT32 len = sizeof(dst->exponent);
+ BYTE* ptr = &dst->exponent[0];
+ if (!read_bignum(&ptr, &len, rsa_e, FALSE))
+ goto fail;
+ return TRUE;
+
+fail:
+ cert_info_free(dst);
+ return FALSE;
+}
+
+BOOL cert_info_clone(rdpCertInfo* dst, const rdpCertInfo* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ *dst = *src;
+
+ dst->Modulus = NULL;
+ dst->ModulusLength = 0;
+ if (src->ModulusLength > 0)
+ {
+ dst->Modulus = malloc(src->ModulusLength);
+ if (!dst->Modulus)
+ return FALSE;
+ memcpy(dst->Modulus, src->Modulus, src->ModulusLength);
+ dst->ModulusLength = src->ModulusLength;
+ }
+ return TRUE;
+}
+
+void cert_info_free(rdpCertInfo* info)
+{
+ WINPR_ASSERT(info);
+ free(info->Modulus);
+ info->ModulusLength = 0;
+ info->Modulus = NULL;
+}
+
+BOOL cert_info_allocate(rdpCertInfo* info, size_t size)
+{
+ WINPR_ASSERT(info);
+ cert_info_free(info);
+
+ info->Modulus = (BYTE*)malloc(size);
+
+ if (!info->Modulus && (size > 0))
+ return FALSE;
+ info->ModulusLength = (UINT32)size;
+ return TRUE;
+}
+
+BOOL cert_info_read_modulus(rdpCertInfo* info, size_t size, wStream* s)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
+ return FALSE;
+ if (size > UINT32_MAX)
+ return FALSE;
+ if (!cert_info_allocate(info, size))
+ return FALSE;
+ Stream_Read(s, info->Modulus, info->ModulusLength);
+ return TRUE;
+}
+
+BOOL cert_info_read_exponent(rdpCertInfo* info, size_t size, wStream* s)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, size))
+ return FALSE;
+ if (size > 4)
+ return FALSE;
+ if (!info->Modulus || (info->ModulusLength == 0))
+ return FALSE;
+ Stream_Read(s, &info->exponent[4 - size], size);
+ crypto_reverse(info->Modulus, info->ModulusLength);
+ crypto_reverse(info->exponent, 4);
+ return TRUE;
+}
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+X509* x509_from_rsa(const RSA* rsa)
+{
+ EVP_PKEY* pubkey = NULL;
+ X509* x509 = NULL;
+ BIO* bio = BIO_new(
+#if defined(LIBRESSL_VERSION_NUMBER)
+ BIO_s_mem()
+#else
+ BIO_s_secmem()
+#endif
+ );
+ if (!bio)
+ return NULL;
+
+ const int rc = PEM_write_bio_RSA_PUBKEY(bio, (RSA*)rsa);
+ if (rc != 1)
+ goto fail;
+
+ pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
+ if (!pubkey)
+ goto fail;
+
+ x509 = X509_new();
+ if (!x509)
+ goto fail;
+
+ const int res = X509_set_pubkey(x509, pubkey);
+ if (res != 1)
+ {
+ X509_free(x509);
+ x509 = NULL;
+ goto fail;
+ }
+fail:
+ BIO_free_all(bio);
+ EVP_PKEY_free(pubkey);
+ return x509;
+}
+#endif
diff --git a/libfreerdp/crypto/cert_common.h b/libfreerdp/crypto/cert_common.h
new file mode 100644
index 0000000..7604fdf
--- /dev/null
+++ b/libfreerdp/crypto/cert_common.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate and private key heplers
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_LIB_CORE_CERT_COMMON_H
+#define FREERDP_LIB_CORE_CERT_COMMON_H
+
+#include <freerdp/crypto/ber.h>
+#include <freerdp/crypto/crypto.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/log.h>
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL BOOL cert_info_create(rdpCertInfo* dst, const BIGNUM* rsa, const BIGNUM* rsa_e);
+ FREERDP_LOCAL void cert_info_free(rdpCertInfo* info);
+
+ FREERDP_LOCAL BOOL cert_info_clone(rdpCertInfo* dst, const rdpCertInfo* src);
+ FREERDP_LOCAL BOOL cert_info_read_modulus(rdpCertInfo* info, size_t size, wStream* s);
+ FREERDP_LOCAL BOOL cert_info_read_exponent(rdpCertInfo* info, size_t size, wStream* s);
+
+ FREERDP_LOCAL BOOL read_bignum(BYTE** dst, UINT32* length, const BIGNUM* num, BOOL alloc);
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ FREERDP_LOCAL X509* x509_from_rsa(const RSA* rsa);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CORE_CERT_COMMON_H */
diff --git a/libfreerdp/crypto/certificate.c b/libfreerdp/crypto/certificate.c
new file mode 100644
index 0000000..ddfe776
--- /dev/null
+++ b/libfreerdp/crypto/certificate.c
@@ -0,0 +1,1732 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/crypto/certificate.h>
+
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+#include <openssl/core_names.h>
+#include <openssl/param_build.h>
+#include <openssl/evp.h>
+#endif
+
+#include "certificate.h"
+#include "cert_common.h"
+#include "crypto.h"
+
+#include "x509_utils.h"
+#include "privatekey.h"
+#include "opensslcompat.h"
+
+#define TAG FREERDP_TAG("core")
+
+#define CERTIFICATE_TAG FREERDP_TAG("core.certificate")
+#ifdef WITH_DEBUG_CERTIFICATE
+#define DEBUG_CERTIFICATE(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_CERTIFICATE(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define TSSK_KEY_LENGTH 64
+
+struct rdp_CertBlob
+{
+ UINT32 length;
+ BYTE* data;
+};
+typedef struct rdp_CertBlob rdpCertBlob;
+
+struct rdp_X509CertChain
+{
+ UINT32 count;
+ rdpCertBlob* array;
+};
+typedef struct rdp_X509CertChain rdpX509CertChain;
+
+struct rdp_certificate
+{
+ X509* x509;
+ STACK_OF(X509) * chain;
+
+ rdpCertInfo cert_info;
+ rdpX509CertChain x509_cert_chain;
+};
+
+/**
+ *
+ * X.509 Certificate Structure
+ *
+ * Certificate ::= SEQUENCE
+ * {
+ * tbsCertificate TBSCertificate,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT_STRING
+ * }
+ *
+ * TBSCertificate ::= SEQUENCE
+ * {
+ * version [0] EXPLICIT Version DEFAULT v1,
+ * serialNumber CertificateSerialNumber,
+ * signature AlgorithmIdentifier,
+ * issuer Name,
+ * validity Validity,
+ * subject Name,
+ * subjectPublicKeyInfo SubjectPublicKeyInfo,
+ * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ * subjectUniqueId [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ * extensions [3] EXPLICIT Extensions OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER { v1(0), v2(1), v3(2) }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * AlgorithmIdentifier ::= SEQUENCE
+ * {
+ * algorithm OBJECT_IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * Name ::= CHOICE { RDNSequence }
+ *
+ * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+ *
+ * AttributeTypeAndValue ::= SEQUENCE
+ * {
+ * type AttributeType,
+ * value AttributeValue
+ * }
+ *
+ * AttributeType ::= OBJECT_IDENTIFIER
+ *
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ *
+ * Validity ::= SEQUENCE
+ * {
+ * notBefore Time,
+ * notAfter Time
+ * }
+ *
+ * Time ::= CHOICE
+ * {
+ * utcTime UTCTime,
+ * generalTime GeneralizedTime
+ * }
+ *
+ * UniqueIdentifier ::= BIT_STRING
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE
+ * {
+ * algorithm AlgorithmIdentifier,
+ * subjectPublicKey BIT_STRING
+ * }
+ *
+ * RSAPublicKey ::= SEQUENCE
+ * {
+ * modulus INTEGER
+ * publicExponent INTEGER
+ * }
+ *
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Extension ::= SEQUENCE
+ * {
+ * extnID OBJECT_IDENTIFIER
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET_STRING
+ * }
+ *
+ */
+
+static const char rsa_magic[4] = "RSA1";
+
+static const char* certificate_read_errors[] = { "Certificate tag",
+ "TBSCertificate",
+ "Explicit Contextual Tag [0]",
+ "version",
+ "CertificateSerialNumber",
+ "AlgorithmIdentifier",
+ "Issuer Name",
+ "Validity",
+ "Subject Name",
+ "SubjectPublicKeyInfo Tag",
+ "subjectPublicKeyInfo::AlgorithmIdentifier",
+ "subjectPublicKeyInfo::subjectPublicKey",
+ "RSAPublicKey Tag",
+ "modulusLength",
+ "zero padding",
+ "modulusLength",
+ "modulus",
+ "publicExponent length",
+ "publicExponent" };
+
+static const BYTE initial_signature[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01
+};
+
+#if defined(CERT_VALIDATE_RSA)
+static const BYTE tssk_exponent[] = { 0x5b, 0x7b, 0x88, 0xc0 };
+#endif
+
+static void certificate_free_int(rdpCertificate* certificate);
+static BOOL cert_clone_int(rdpCertificate* dst, const rdpCertificate* src);
+
+/* [MS-RDPBCGR] 5.3.3.2 X.509 Certificate Chains:
+ *
+ * More detail[MS-RDPELE] section 2.2.1.4.2.
+ */
+static BOOL cert_blob_copy(rdpCertBlob* dst, const rdpCertBlob* src);
+static void cert_blob_free(rdpCertBlob* blob);
+static BOOL cert_blob_write(const rdpCertBlob* blob, wStream* s);
+static BOOL cert_blob_read(rdpCertBlob* blob, wStream* s);
+
+BOOL cert_blob_read(rdpCertBlob* blob, wStream* s)
+{
+ UINT32 certLength = 0;
+ WINPR_ASSERT(blob);
+ cert_blob_free(blob);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto fail;
+
+ Stream_Read_UINT32(s, certLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, certLength))
+ goto fail;
+
+ DEBUG_CERTIFICATE("X.509 Certificate length:%" PRIu32 "", certLength);
+ blob->data = (BYTE*)malloc(certLength);
+
+ if (!blob->data)
+ goto fail;
+
+ Stream_Read(s, blob->data, certLength);
+ blob->length = certLength;
+
+ return TRUE;
+
+fail:
+ cert_blob_free(blob);
+ return FALSE;
+}
+
+BOOL cert_blob_write(const rdpCertBlob* blob, wStream* s)
+{
+ WINPR_ASSERT(blob);
+
+ if (!Stream_EnsureRemainingCapacity(s, 4 + blob->length))
+ return FALSE;
+
+ Stream_Write_UINT32(s, blob->length);
+ Stream_Write(s, blob->data, blob->length);
+ return TRUE;
+}
+
+void cert_blob_free(rdpCertBlob* blob)
+{
+ if (!blob)
+ return;
+ free(blob->data);
+ blob->data = NULL;
+ blob->length = 0;
+}
+
+/**
+ * Read X.509 Certificate
+ */
+
+static BOOL is_rsa_key(const X509* x509)
+{
+ EVP_PKEY* evp = X509_get0_pubkey(x509);
+ if (!evp)
+ return FALSE;
+
+ return (EVP_PKEY_id(evp) == EVP_PKEY_RSA);
+}
+
+static BOOL certificate_read_x509_certificate(const rdpCertBlob* cert, rdpCertInfo* info)
+{
+ wStream sbuffer = { 0 };
+ wStream* s = NULL;
+ size_t length = 0;
+ BYTE padding = 0;
+ UINT32 version = 0;
+ size_t modulus_length = 0;
+ size_t exponent_length = 0;
+ int error = 0;
+
+ if (!cert || !info)
+ return FALSE;
+
+ cert_info_free(info);
+
+ s = Stream_StaticConstInit(&sbuffer, cert->data, cert->length);
+
+ if (!s)
+ return FALSE;
+
+ if (!ber_read_sequence_tag(s, &length)) /* Certificate (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ if (!ber_read_sequence_tag(s, &length)) /* TBSCertificate (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ if (!ber_read_contextual_tag(s, 0, &length, TRUE)) /* Explicit Contextual Tag [0] */
+ goto error;
+
+ error++;
+
+ if (!ber_read_integer(s, &version)) /* version (INTEGER) */
+ goto error;
+
+ error++;
+ version++;
+
+ /* serialNumber */
+ if (!ber_read_integer(s, NULL)) /* CertificateSerialNumber (INTEGER) */
+ goto error;
+
+ error++;
+
+ /* signature */
+ if (!ber_read_sequence_tag(s, &length) ||
+ !Stream_SafeSeek(s, length)) /* AlgorithmIdentifier (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* issuer */
+ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Name (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* validity */
+ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Validity (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subject */
+ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Name (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subjectPublicKeyInfo */
+ if (!ber_read_sequence_tag(s, &length)) /* SubjectPublicKeyInfo (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subjectPublicKeyInfo::AlgorithmIdentifier */
+ if (!ber_read_sequence_tag(s, &length) ||
+ !Stream_SafeSeek(s, length)) /* AlgorithmIdentifier (SEQUENCE) */
+ goto error;
+
+ error++;
+
+ /* subjectPublicKeyInfo::subjectPublicKey */
+ if (!ber_read_bit_string(s, &length, &padding)) /* BIT_STRING */
+ goto error;
+
+ error++;
+
+ /* RSAPublicKey (SEQUENCE) */
+ if (!ber_read_sequence_tag(s, &length)) /* SEQUENCE */
+ goto error;
+
+ error++;
+
+ if (!ber_read_integer_length(s, &modulus_length)) /* modulus (INTEGER) */
+ goto error;
+
+ error++;
+
+ /* skip zero padding, if any */
+ do
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ goto error;
+
+ Stream_Peek_UINT8(s, padding);
+
+ if (padding == 0)
+ {
+ if (!Stream_SafeSeek(s, 1))
+ goto error;
+
+ modulus_length--;
+ }
+ } while (padding == 0);
+
+ error++;
+
+ if (!cert_info_read_modulus(info, modulus_length, s))
+ goto error;
+
+ error++;
+
+ if (!ber_read_integer_length(s, &exponent_length)) /* publicExponent (INTEGER) */
+ goto error;
+
+ error++;
+
+ if (!cert_info_read_exponent(info, exponent_length, s))
+ goto error;
+ return TRUE;
+error:
+ WLog_ERR(TAG, "error reading when reading certificate: part=%s error=%d",
+ certificate_read_errors[error], error);
+ cert_info_free(info);
+ return FALSE;
+}
+
+/**
+ * Instantiate new X.509 Certificate Chain.
+ * @param count certificate chain count
+ * @return new X.509 certificate chain
+ */
+
+static rdpX509CertChain certificate_new_x509_certificate_chain(UINT32 count)
+{
+ rdpX509CertChain x509_cert_chain = { 0 };
+
+ x509_cert_chain.array = (rdpCertBlob*)calloc(count, sizeof(rdpCertBlob));
+
+ if (x509_cert_chain.array)
+ x509_cert_chain.count = count;
+
+ return x509_cert_chain;
+}
+
+/**
+ * Free X.509 Certificate Chain.
+ * @param x509_cert_chain X.509 certificate chain to be freed
+ */
+
+static void certificate_free_x509_certificate_chain(rdpX509CertChain* x509_cert_chain)
+{
+ if (!x509_cert_chain)
+ return;
+
+ if (x509_cert_chain->array)
+ {
+ for (UINT32 i = 0; i < x509_cert_chain->count; i++)
+ {
+ rdpCertBlob* element = &x509_cert_chain->array[i];
+ cert_blob_free(element);
+ }
+ }
+
+ free(x509_cert_chain->array);
+}
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+static OSSL_PARAM* get_params(const BIGNUM* e, const BIGNUM* mod)
+{
+ WINPR_ASSERT(e);
+ WINPR_ASSERT(mod);
+
+ OSSL_PARAM* parameters = NULL;
+ OSSL_PARAM_BLD* param = OSSL_PARAM_BLD_new();
+ if (!param)
+ return NULL;
+
+ const int bits = BN_num_bits(e);
+ if ((bits < 0) || (bits > 32))
+ goto fail;
+
+ UINT ie = 0;
+ const int ne = BN_bn2nativepad(e, (BYTE*)&ie, sizeof(ie));
+ if ((ne < 0) || (ne > 4))
+ goto fail;
+ if (OSSL_PARAM_BLD_push_BN(param, OSSL_PKEY_PARAM_RSA_N, mod) != 1)
+ goto fail;
+ if (OSSL_PARAM_BLD_push_uint(param, OSSL_PKEY_PARAM_RSA_E, ie) != 1)
+ goto fail;
+
+ parameters = OSSL_PARAM_BLD_to_param(param);
+fail:
+ OSSL_PARAM_BLD_free(param);
+
+ return parameters;
+}
+#endif
+
+static BOOL update_x509_from_info(rdpCertificate* cert)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(cert);
+
+ X509_free(cert->x509);
+ cert->x509 = NULL;
+
+ rdpCertInfo* info = &cert->cert_info;
+
+ BIGNUM* e = BN_new();
+ BIGNUM* mod = BN_new();
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = RSA_new();
+ if (!rsa)
+ goto fail;
+#endif
+
+ if (!mod || !e)
+ goto fail;
+ if (!BN_bin2bn(info->Modulus, info->ModulusLength, mod))
+ goto fail;
+ if (!BN_bin2bn(info->exponent, sizeof(info->exponent), e))
+ goto fail;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ const int rec = RSA_set0_key(rsa, mod, e, NULL);
+ if (rec != 1)
+ goto fail;
+
+ cert->x509 = x509_from_rsa(rsa);
+#else
+ EVP_PKEY* pkey = NULL;
+ EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (!ctx)
+ goto fail2;
+ const int xx = EVP_PKEY_fromdata_init(ctx);
+ if (xx != 1)
+ goto fail2;
+ OSSL_PARAM* parameters = get_params(e, mod);
+ if (!parameters)
+ goto fail2;
+
+ const int rc2 = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, parameters);
+ OSSL_PARAM_free(parameters);
+ if (rc2 <= 0)
+ goto fail2;
+
+ cert->x509 = X509_new();
+ if (!cert->x509)
+ goto fail2;
+ if (X509_set_pubkey(cert->x509, pkey) != 1)
+ {
+ X509_free(cert->x509);
+ cert->x509 = NULL;
+ }
+fail2:
+ EVP_PKEY_free(pkey);
+ EVP_PKEY_CTX_free(ctx);
+#endif
+ if (!cert->x509)
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ if (rsa)
+ RSA_free(rsa);
+ else
+#endif
+ {
+ BN_free(mod);
+ BN_free(e);
+ }
+ return rc;
+}
+
+static BOOL certificate_process_server_public_key(rdpCertificate* cert, wStream* s, UINT32 length)
+{
+ char magic[sizeof(rsa_magic)] = { 0 };
+ UINT32 keylen = 0;
+ UINT32 bitlen = 0;
+ UINT32 datalen = 0;
+
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return FALSE;
+
+ Stream_Read(s, magic, sizeof(magic));
+
+ if (memcmp(magic, rsa_magic, sizeof(magic)) != 0)
+ {
+ WLog_ERR(TAG, "magic error");
+ return FALSE;
+ }
+
+ rdpCertInfo* info = &cert->cert_info;
+ cert_info_free(info);
+
+ Stream_Read_UINT32(s, keylen);
+ Stream_Read_UINT32(s, bitlen);
+ Stream_Read_UINT32(s, datalen);
+ Stream_Read(s, info->exponent, 4);
+
+ if ((keylen <= 8) || (!Stream_CheckAndLogRequiredLength(TAG, s, keylen)))
+ return FALSE;
+
+ info->ModulusLength = keylen - 8;
+ BYTE* tmp = realloc(info->Modulus, info->ModulusLength);
+
+ if (!tmp)
+ return FALSE;
+ info->Modulus = tmp;
+
+ Stream_Read(s, info->Modulus, info->ModulusLength);
+ Stream_Seek(s, 8); /* 8 bytes of zero padding */
+ return update_x509_from_info(cert);
+}
+
+static BOOL certificate_process_server_public_signature(rdpCertificate* certificate,
+ const BYTE* sigdata, size_t sigdatalen,
+ wStream* s, UINT32 siglen)
+{
+ WINPR_ASSERT(certificate);
+#if defined(CERT_VALIDATE_RSA)
+ BYTE sig[TSSK_KEY_LENGTH];
+#endif
+ BYTE encsig[TSSK_KEY_LENGTH + 8];
+#if defined(CERT_VALIDATE_MD5) && defined(CERT_VALIDATE_RSA)
+ BYTE md5hash[WINPR_MD5_DIGEST_LENGTH];
+#endif
+#if !defined(CERT_VALIDATE_MD5) || !defined(CERT_VALIDATE_RSA)
+ (void)sigdata;
+ (void)sigdatalen;
+#endif
+ (void)certificate;
+ /* Do not bother with validation of server proprietary certificate. The use of MD5 here is not
+ * allowed under FIPS. Since the validation is not protecting against anything since the
+ * private/public keys are well known and documented in MS-RDPBCGR section 5.3.3.1, we are not
+ * gaining any security by using MD5 for signature comparison. Rather then use MD5
+ * here we just dont do the validation to avoid its use. Historically, freerdp has been ignoring
+ * a failed validation anyways. */
+#if defined(CERT_VALIDATE_MD5)
+
+ if (!winpr_Digest(WINPR_MD_MD5, sigdata, sigdatalen, md5hash, sizeof(md5hash)))
+ return FALSE;
+
+#endif
+ Stream_Read(s, encsig, siglen);
+
+ if (siglen < 8)
+ return FALSE;
+
+ /* Last 8 bytes shall be all zero. */
+#if defined(CERT_VALIDATE_PADDING)
+ {
+ size_t sum = 0;
+ for (size_t i = sizeof(encsig) - 8; i < sizeof(encsig); i++)
+ sum += encsig[i];
+
+ if (sum != 0)
+ {
+ WLog_ERR(TAG, "invalid signature");
+ return FALSE;
+ }
+ }
+#endif
+#if defined(CERT_VALIDATE_RSA)
+
+ if (crypto_rsa_public_decrypt(encsig, siglen - 8, TSSK_KEY_LENGTH, tssk_modulus, tssk_exponent,
+ sig) <= 0)
+ {
+ WLog_ERR(TAG, "invalid RSA decrypt");
+ return FALSE;
+ }
+
+ /* Verify signature. */
+ /* Do not bother with validation of server proprietary certificate as described above. */
+#if defined(CERT_VALIDATE_MD5)
+
+ if (memcmp(md5hash, sig, sizeof(md5hash)) != 0)
+ {
+ WLog_ERR(TAG, "invalid signature");
+ return FALSE;
+ }
+
+#endif
+ /*
+ * Verify rest of decrypted data:
+ * The 17th byte is 0x00.
+ * The 18th through 62nd bytes are each 0xFF.
+ * The 63rd byte is 0x01.
+ */
+ {
+ size_t sum = 0;
+ for (size_t i = 17; i < 62; i++)
+ sum += sig[i];
+
+ if (sig[16] != 0x00 || sum != 0xFF * (62 - 17) || sig[62] != 0x01)
+ {
+ WLog_ERR(TAG, "invalid signature");
+ return FALSE;
+ }
+ }
+#endif
+ return TRUE;
+}
+
+static BOOL certificate_read_server_proprietary_certificate(rdpCertificate* certificate, wStream* s)
+{
+ UINT32 dwSigAlgId = 0;
+ UINT32 dwKeyAlgId = 0;
+ UINT16 wPublicKeyBlobType = 0;
+ UINT16 wPublicKeyBlobLen = 0;
+ UINT16 wSignatureBlobType = 0;
+ UINT16 wSignatureBlobLen = 0;
+ size_t sigdatalen = 0;
+
+ WINPR_ASSERT(certificate);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return FALSE;
+
+ /* -4, because we need to include dwVersion */
+ const BYTE* sigdata = Stream_PointerAs(s, const BYTE) - 4;
+ Stream_Read_UINT32(s, dwSigAlgId);
+ Stream_Read_UINT32(s, dwKeyAlgId);
+
+ if (!((dwSigAlgId == SIGNATURE_ALG_RSA) && (dwKeyAlgId == KEY_EXCHANGE_ALG_RSA)))
+ {
+ WLog_ERR(TAG,
+ "unsupported signature or key algorithm, dwSigAlgId=%" PRIu32
+ " dwKeyAlgId=%" PRIu32 "",
+ dwSigAlgId, dwKeyAlgId);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, wPublicKeyBlobType);
+
+ if (wPublicKeyBlobType != BB_RSA_KEY_BLOB)
+ {
+ WLog_ERR(TAG, "unsupported public key blob type %" PRIu16 "", wPublicKeyBlobType);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, wPublicKeyBlobLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, wPublicKeyBlobLen))
+ return FALSE;
+
+ if (!certificate_process_server_public_key(certificate, s, wPublicKeyBlobLen))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ sigdatalen = Stream_PointerAs(s, const BYTE) - sigdata;
+ Stream_Read_UINT16(s, wSignatureBlobType);
+
+ if (wSignatureBlobType != BB_RSA_SIGNATURE_BLOB)
+ {
+ WLog_ERR(TAG, "unsupported blob signature %" PRIu16 "", wSignatureBlobType);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, wSignatureBlobLen);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, wSignatureBlobLen))
+ return FALSE;
+
+ if (wSignatureBlobLen != 72)
+ {
+ WLog_ERR(TAG, "invalid signature length (got %" PRIu16 ", expected 72)", wSignatureBlobLen);
+ return FALSE;
+ }
+
+ if (!certificate_process_server_public_signature(certificate, sigdata, sigdatalen, s,
+ wSignatureBlobLen))
+ {
+ WLog_ERR(TAG, "unable to parse server public signature");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* [MS-RDPBCGR] 2.2.1.4.3.1.1.1 RSA Public Key (RSA_PUBLIC_KEY) */
+static BOOL cert_write_rsa_public_key(wStream* s, const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(freerdp_certificate_is_rsa(cert));
+
+ const rdpCertInfo* info = &cert->cert_info;
+
+ const UINT32 keyLen = info->ModulusLength + 8;
+ const UINT32 bitLen = info->ModulusLength * 8;
+ const UINT32 dataLen = (bitLen / 8) - 1;
+ const size_t pubExpLen = sizeof(info->exponent);
+ const BYTE* pubExp = info->exponent;
+ const BYTE* modulus = info->Modulus;
+
+ const size_t wPublicKeyBlobLen = 16 + pubExpLen + keyLen;
+ WINPR_ASSERT(wPublicKeyBlobLen <= UINT16_MAX);
+ if (!Stream_EnsureRemainingCapacity(s, 2 + wPublicKeyBlobLen))
+ return FALSE;
+ Stream_Write_UINT16(s, (UINT16)wPublicKeyBlobLen);
+ Stream_Write(s, rsa_magic, sizeof(rsa_magic));
+ Stream_Write_UINT32(s, keyLen);
+ Stream_Write_UINT32(s, bitLen);
+ Stream_Write_UINT32(s, dataLen);
+ Stream_Write(s, pubExp, pubExpLen);
+ Stream_Write(s, modulus, info->ModulusLength);
+ Stream_Zero(s, 8);
+ return TRUE;
+}
+
+static BOOL cert_write_rsa_signature(wStream* s, const void* sigData, size_t sigDataLen)
+{
+ BYTE encryptedSignature[TSSK_KEY_LENGTH] = { 0 };
+ BYTE signature[sizeof(initial_signature)] = { 0 };
+
+ memcpy(signature, initial_signature, sizeof(initial_signature));
+ if (!winpr_Digest(WINPR_MD_MD5, sigData, sigDataLen, signature, sizeof(signature)))
+ return FALSE;
+
+ crypto_rsa_private_encrypt(signature, sizeof(signature), priv_key_tssk, encryptedSignature,
+ sizeof(encryptedSignature));
+
+ if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT16) + sizeof(encryptedSignature) + 8))
+ return FALSE;
+ Stream_Write_UINT16(s, BB_RSA_SIGNATURE_BLOB);
+ Stream_Write_UINT16(s, sizeof(encryptedSignature) + 8); /* wSignatureBlobLen */
+ Stream_Write(s, encryptedSignature, sizeof(encryptedSignature));
+ Stream_Zero(s, 8);
+ return TRUE;
+}
+
+/* [MS-RDPBCGR] 2.2.1.4.3.1.1 Server Proprietary Certificate (PROPRIETARYSERVERCERTIFICATE) */
+static BOOL cert_write_server_certificate_v1(wStream* s, const rdpCertificate* certificate)
+{
+ const size_t start = Stream_GetPosition(s);
+ const BYTE* sigData = Stream_PointerAs(s, const BYTE) - sizeof(UINT32);
+
+ WINPR_ASSERT(start >= 4);
+ if (!Stream_EnsureRemainingCapacity(s, 10))
+ return FALSE;
+ Stream_Write_UINT32(s, SIGNATURE_ALG_RSA);
+ Stream_Write_UINT32(s, KEY_EXCHANGE_ALG_RSA);
+ Stream_Write_UINT16(s, BB_RSA_KEY_BLOB);
+ if (!cert_write_rsa_public_key(s, certificate))
+ return FALSE;
+
+ const size_t end = Stream_GetPosition(s);
+ return cert_write_rsa_signature(s, sigData, end - start + sizeof(UINT32));
+}
+
+static BOOL cert_write_server_certificate_v2(wStream* s, const rdpCertificate* certificate)
+{
+ WINPR_ASSERT(certificate);
+
+ const rdpX509CertChain* chain = &certificate->x509_cert_chain;
+ const size_t padding = 8ull + 4ull * chain->count;
+
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32)))
+ return FALSE;
+
+ Stream_Write_UINT32(s, chain->count);
+ for (UINT32 x = 0; x < chain->count; x++)
+ {
+ const rdpCertBlob* cert = &chain->array[x];
+ if (!cert_blob_write(cert, s))
+ return FALSE;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, padding))
+ return FALSE;
+ Stream_Zero(s, padding);
+ return TRUE;
+}
+
+SSIZE_T freerdp_certificate_write_server_cert(const rdpCertificate* certificate, UINT32 dwVersion,
+ wStream* s)
+{
+ if (!certificate)
+ return -1;
+
+ const size_t start = Stream_GetPosition(s);
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return -1;
+ Stream_Write_UINT32(s, dwVersion);
+
+ switch (dwVersion & CERT_CHAIN_VERSION_MASK)
+ {
+ case CERT_CHAIN_VERSION_1:
+ if (!cert_write_server_certificate_v1(s, certificate))
+ return -1;
+ break;
+ case CERT_CHAIN_VERSION_2:
+ if (!cert_write_server_certificate_v2(s, certificate))
+ return -1;
+ break;
+ default:
+ WLog_ERR(TAG, "invalid certificate chain version:%" PRIu32 "",
+ dwVersion & CERT_CHAIN_VERSION_MASK);
+ return -1;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ return end - start;
+}
+
+/**
+ * Read an X.509 Certificate Chain.
+ * @param cert certificate module
+ * @param s stream
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+static BOOL certificate_read_server_x509_certificate_chain(rdpCertificate* cert, wStream* s)
+{
+ UINT32 numCertBlobs = 0;
+ DEBUG_CERTIFICATE("Server X.509 Certificate Chain");
+
+ WINPR_ASSERT(cert);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, numCertBlobs); /* numCertBlobs */
+ certificate_free_x509_certificate_chain(&cert->x509_cert_chain);
+ cert->x509_cert_chain = certificate_new_x509_certificate_chain(numCertBlobs);
+
+ for (UINT32 i = 0; i < cert->x509_cert_chain.count; i++)
+ {
+ rdpCertBlob* blob = &cert->x509_cert_chain.array[i];
+ if (!cert_blob_read(blob, s))
+ return FALSE;
+
+ if (numCertBlobs - i == 1)
+ {
+ DEBUG_CERTIFICATE("Terminal Server Certificate");
+
+ BOOL res = certificate_read_x509_certificate(blob, &cert->cert_info);
+
+ if (res)
+ {
+ if (!update_x509_from_info(cert))
+ res = FALSE;
+ }
+
+ if (!res)
+ {
+ return FALSE;
+ }
+
+ DEBUG_CERTIFICATE("modulus length:%" PRIu32 "", cert->cert_info.ModulusLength);
+ }
+ }
+
+ return update_x509_from_info(cert);
+}
+
+static BOOL certificate_write_server_x509_certificate_chain(const rdpCertificate* certificate,
+ wStream* s)
+{
+ UINT32 numCertBlobs = 0;
+
+ WINPR_ASSERT(certificate);
+ WINPR_ASSERT(s);
+
+ numCertBlobs = certificate->x509_cert_chain.count;
+
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+ Stream_Write_UINT32(s, numCertBlobs); /* numCertBlobs */
+
+ for (UINT32 i = 0; i < numCertBlobs; i++)
+ {
+ const rdpCertBlob* cert = &certificate->x509_cert_chain.array[i];
+ if (!cert_blob_write(cert, s))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Read a Server Certificate.
+ * @param certificate certificate module
+ * @param server_cert server certificate
+ * @param length certificate length
+ */
+
+BOOL freerdp_certificate_read_server_cert(rdpCertificate* certificate, const BYTE* server_cert,
+ size_t length)
+{
+ BOOL ret = FALSE;
+ wStream* s = NULL;
+ wStream sbuffer;
+ UINT32 dwVersion = 0;
+
+ WINPR_ASSERT(certificate);
+ if (length < 4) /* NULL certificate is not an error see #1795 */
+ {
+ WLog_DBG(TAG, "Received empty certificate, ignoring...");
+ return TRUE;
+ }
+
+ WINPR_ASSERT(server_cert);
+ s = Stream_StaticConstInit(&sbuffer, server_cert, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return FALSE;
+ }
+
+ Stream_Read_UINT32(s, dwVersion); /* dwVersion (4 bytes) */
+
+ switch (dwVersion & CERT_CHAIN_VERSION_MASK)
+ {
+ case CERT_CHAIN_VERSION_1:
+ ret = certificate_read_server_proprietary_certificate(certificate, s);
+ break;
+
+ case CERT_CHAIN_VERSION_2:
+ ret = certificate_read_server_x509_certificate_chain(certificate, s);
+ break;
+
+ default:
+ WLog_ERR(TAG, "invalid certificate chain version:%" PRIu32 "",
+ dwVersion & CERT_CHAIN_VERSION_MASK);
+ ret = FALSE;
+ break;
+ }
+
+ return ret;
+}
+
+static BOOL cert_blob_copy(rdpCertBlob* dst, const rdpCertBlob* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ cert_blob_free(dst);
+ if (src->length > 0)
+ {
+ dst->data = malloc(src->length);
+ if (!dst->data)
+ return FALSE;
+ dst->length = src->length;
+ memcpy(dst->data, src->data, src->length);
+ }
+
+ return TRUE;
+}
+
+static BOOL cert_x509_chain_copy(rdpX509CertChain* cert, const rdpX509CertChain* src)
+{
+ WINPR_ASSERT(cert);
+
+ certificate_free_x509_certificate_chain(cert);
+ if (!src)
+ return TRUE;
+
+ if (src->count > 0)
+ {
+ cert->array = calloc(src->count, sizeof(rdpCertBlob));
+ if (!cert->array)
+ {
+ return FALSE;
+ }
+ cert->count = src->count;
+
+ for (UINT32 x = 0; x < cert->count; x++)
+ {
+ const rdpCertBlob* srcblob = &src->array[x];
+ rdpCertBlob* dstblob = &cert->array[x];
+
+ if (!cert_blob_copy(dstblob, srcblob))
+ {
+ certificate_free_x509_certificate_chain(cert);
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL cert_clone_int(rdpCertificate* dst, const rdpCertificate* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ if (src->x509)
+ {
+ dst->x509 = X509_dup(src->x509);
+ if (!dst->x509)
+ return FALSE;
+ }
+
+ if (!cert_info_clone(&dst->cert_info, &src->cert_info))
+ return FALSE;
+ return cert_x509_chain_copy(&dst->x509_cert_chain, &src->x509_cert_chain);
+}
+
+rdpCertificate* freerdp_certificate_clone(const rdpCertificate* certificate)
+{
+ if (!certificate)
+ return NULL;
+
+ rdpCertificate* _certificate = freerdp_certificate_new();
+
+ if (!_certificate)
+ return NULL;
+
+ if (!cert_clone_int(_certificate, certificate))
+ goto out_fail;
+
+ return _certificate;
+out_fail:
+
+ freerdp_certificate_free(_certificate);
+ return NULL;
+}
+
+/**
+ * Instantiate new certificate module.
+ * @return new certificate module
+ */
+
+rdpCertificate* freerdp_certificate_new(void)
+{
+ return (rdpCertificate*)calloc(1, sizeof(rdpCertificate));
+}
+
+void certificate_free_int(rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+
+ if (cert->x509)
+ X509_free(cert->x509);
+ if (cert->chain)
+ sk_X509_free(cert->chain);
+
+ certificate_free_x509_certificate_chain(&cert->x509_cert_chain);
+ cert_info_free(&cert->cert_info);
+}
+
+/**
+ * Free certificate module.
+ * @param cert certificate module to be freed
+ */
+
+void freerdp_certificate_free(rdpCertificate* cert)
+{
+ if (!cert)
+ return;
+
+ certificate_free_int(cert);
+ free(cert);
+}
+
+static BOOL freerdp_rsa_from_x509(rdpCertificate* cert)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(cert);
+
+ if (!freerdp_certificate_is_rsa(cert))
+ return TRUE;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = NULL;
+ const BIGNUM* rsa_n = NULL;
+ const BIGNUM* rsa_e = NULL;
+#else
+ BIGNUM* rsa_n = NULL;
+ BIGNUM* rsa_e = NULL;
+#endif
+ EVP_PKEY* pubkey = X509_get0_pubkey(cert->x509);
+ if (!pubkey)
+ goto fail;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ rsa = EVP_PKEY_get1_RSA(pubkey);
+
+ /* If this is not a RSA key return success */
+ rc = TRUE;
+ if (!rsa)
+ goto fail;
+
+ /* Now we return failure again if something is wrong. */
+ rc = FALSE;
+
+ RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL);
+#else
+ if (!EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_E, &rsa_e))
+ goto fail;
+ if (!EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_N, &rsa_n))
+ goto fail;
+#endif
+ if (!rsa_n || !rsa_e)
+ goto fail;
+ if (!cert_info_create(&cert->cert_info, rsa_n, rsa_e))
+ goto fail;
+ rc = TRUE;
+fail:
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA_free(rsa);
+#else
+ BN_free(rsa_n);
+ BN_free(rsa_e);
+#endif
+ return rc;
+}
+
+rdpCertificate* freerdp_certificate_new_from_der(const BYTE* data, size_t length)
+{
+ rdpCertificate* cert = freerdp_certificate_new();
+
+ if (!cert || !data || (length == 0))
+ goto fail;
+ const BYTE* ptr = data;
+ cert->x509 = d2i_X509(NULL, &ptr, length);
+ if (!cert->x509)
+ goto fail;
+ if (!freerdp_rsa_from_x509(cert))
+ goto fail;
+ return cert;
+fail:
+ freerdp_certificate_free(cert);
+ return NULL;
+}
+
+rdpCertificate* freerdp_certificate_new_from_x509(const X509* xcert, const STACK_OF(X509) * chain)
+{
+ WINPR_ASSERT(xcert);
+
+ rdpCertificate* cert = freerdp_certificate_new();
+ if (!cert)
+ return NULL;
+
+ cert->x509 = X509_dup(xcert);
+ if (!cert->x509)
+ goto fail;
+
+ if (!freerdp_rsa_from_x509(cert))
+ goto fail;
+
+ if (chain)
+ {
+ cert->chain = sk_X509_dup(chain);
+ }
+ return cert;
+fail:
+ freerdp_certificate_free(cert);
+ return NULL;
+}
+
+static rdpCertificate* freerdp_certificate_new_from(const char* file, BOOL isFile)
+{
+ X509* x509 = x509_utils_from_pem(file, strlen(file), isFile);
+ if (!x509)
+ return NULL;
+ rdpCertificate* cert = freerdp_certificate_new_from_x509(x509, NULL);
+ X509_free(x509);
+ return cert;
+}
+
+rdpCertificate* freerdp_certificate_new_from_file(const char* file)
+{
+ return freerdp_certificate_new_from(file, TRUE);
+}
+
+rdpCertificate* freerdp_certificate_new_from_pem(const char* pem)
+{
+ return freerdp_certificate_new_from(pem, FALSE);
+}
+
+const rdpCertInfo* freerdp_certificate_get_info(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ if (!freerdp_certificate_is_rsa(cert))
+ return NULL;
+ return &cert->cert_info;
+}
+
+char* freerdp_certificate_get_fingerprint(const rdpCertificate* cert)
+{
+ return freerdp_certificate_get_fingerprint_by_hash(cert, "sha256");
+}
+
+char* freerdp_certificate_get_fingerprint_by_hash(const rdpCertificate* cert, const char* hash)
+{
+ return freerdp_certificate_get_fingerprint_by_hash_ex(cert, hash, TRUE);
+}
+
+char* freerdp_certificate_get_fingerprint_by_hash_ex(const rdpCertificate* cert, const char* hash,
+ BOOL separator)
+{
+ size_t fp_len = 0;
+ size_t pos = 0;
+ size_t size = 0;
+ BYTE* fp = NULL;
+ char* fp_buffer = NULL;
+ if (!cert || !cert->x509)
+ {
+ WLog_ERR(TAG, "Invalid certificate [%p, %p]", cert, cert ? cert->x509 : NULL);
+ return NULL;
+ }
+ if (!hash)
+ {
+ WLog_ERR(TAG, "Invalid certificate hash %p", hash);
+ return NULL;
+ }
+ fp = x509_utils_get_hash(cert->x509, hash, &fp_len);
+ if (!fp)
+ return NULL;
+
+ size = fp_len * 3 + 1;
+ fp_buffer = calloc(size, sizeof(char));
+ if (!fp_buffer)
+ goto fail;
+
+ pos = 0;
+
+ size_t i = 0;
+ for (; i < (fp_len - 1); i++)
+ {
+ int rc = 0;
+ char* p = &fp_buffer[pos];
+ if (separator)
+ rc = sprintf_s(p, size - pos, "%02" PRIx8 ":", fp[i]);
+ else
+ rc = sprintf_s(p, size - pos, "%02" PRIx8, fp[i]);
+ if (rc <= 0)
+ goto fail;
+ pos += (size_t)rc;
+ }
+
+ sprintf_s(&fp_buffer[pos], size - pos, "%02" PRIx8 "", fp[i]);
+
+ free(fp);
+
+ return fp_buffer;
+fail:
+ free(fp);
+ free(fp_buffer);
+ return NULL;
+}
+
+static BOOL bio_read_pem(BIO* bio, char** ppem, size_t* plength)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(bio);
+ WINPR_ASSERT(ppem);
+
+ size_t offset = 0;
+ size_t length = 2048;
+ char* pem = NULL;
+ while (offset < length)
+ {
+ char* tmp = realloc(pem, length + 1);
+ if (!tmp)
+ goto fail;
+ pem = tmp;
+
+ ERR_clear_error();
+
+ const int status = BIO_read(bio, &pem[offset], length - offset);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "failed to read certificate");
+ goto fail;
+ }
+
+ if (status == 0)
+ break;
+
+ offset += (size_t)status;
+ if (length - offset > 0)
+ break;
+ length *= 2;
+ }
+ pem[offset] = '\0';
+ *ppem = pem;
+ if (plength)
+ *plength = offset;
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+char* freerdp_certificate_get_pem(const rdpCertificate* cert, size_t* pLength)
+{
+ char* pem = NULL;
+ WINPR_ASSERT(cert);
+
+ if (!cert->x509)
+ return NULL;
+
+ BIO* bio = NULL;
+ int status = 0;
+
+ /**
+ * 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 NULL;
+ }
+
+ status = PEM_write_bio_X509(bio, cert->x509);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+
+#if 0
+ if (chain)
+ {
+ int count = sk_X509_num(chain);
+ for (int x = 0; x < count; x++)
+ {
+ X509* c = sk_X509_value(chain, x);
+ status = PEM_write_bio_X509(bio, c);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+ }
+ }
+#endif
+ if (!bio_read_pem(bio, &pem, pLength))
+ goto fail;
+fail:
+ BIO_free_all(bio);
+ return pem;
+}
+
+char* freerdp_certificate_get_subject(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_subject(cert->x509);
+}
+
+char* freerdp_certificate_get_issuer(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_issuer(cert->x509);
+}
+
+char* freerdp_certificate_get_upn(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_upn(cert->x509);
+}
+
+char* freerdp_certificate_get_email(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_email(cert->x509);
+}
+
+BOOL freerdp_certificate_check_eku(const rdpCertificate* cert, int nid)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_check_eku(cert->x509, nid);
+}
+
+BOOL freerdp_certificate_get_public_key(const rdpCertificate* cert, BYTE** PublicKey,
+ DWORD* PublicKeyLength)
+{
+ BYTE* ptr = NULL;
+ BYTE* optr = NULL;
+ int length = 0;
+ BOOL status = FALSE;
+ EVP_PKEY* pkey = NULL;
+
+ WINPR_ASSERT(cert);
+
+ pkey = X509_get0_pubkey(cert->x509);
+
+ if (!pkey)
+ {
+ WLog_ERR(TAG, "X509_get_pubkey() failed");
+ goto exit;
+ }
+
+ length = i2d_PublicKey(pkey, NULL);
+
+ if (length < 1)
+ {
+ WLog_ERR(TAG, "i2d_PublicKey() failed");
+ goto exit;
+ }
+
+ *PublicKey = optr = ptr = (BYTE*)calloc(length, sizeof(BYTE));
+
+ if (!ptr)
+ goto exit;
+
+ const int length2 = i2d_PublicKey(pkey, &ptr);
+ if (length != length2)
+ goto exit;
+ *PublicKeyLength = (DWORD)length2;
+ status = TRUE;
+exit:
+
+ if (!status)
+ free(optr);
+
+ return status;
+}
+
+BOOL freerdp_certificate_verify(const rdpCertificate* cert, const char* certificate_store_path)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_verify(cert->x509, cert->chain, certificate_store_path);
+}
+
+char** freerdp_certificate_get_dns_names(const rdpCertificate* cert, size_t* pcount,
+ size_t** pplengths)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_dns_names(cert->x509, pcount, pplengths);
+}
+
+char* freerdp_certificate_get_common_name(const rdpCertificate* cert, size_t* plength)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_common_name(cert->x509, plength);
+}
+
+WINPR_MD_TYPE freerdp_certificate_get_signature_alg(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return x509_utils_get_signature_alg(cert->x509);
+}
+
+void freerdp_certificate_free_dns_names(size_t count, size_t* lengths, char** names)
+{
+ x509_utils_dns_names_free(count, lengths, names);
+}
+
+char* freerdp_certificate_get_hash(const rdpCertificate* cert, const char* hash, size_t* plength)
+{
+ WINPR_ASSERT(cert);
+ return (char*)x509_utils_get_hash(cert->x509, hash, plength);
+}
+
+X509* freerdp_certificate_get_x509(rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return cert->x509;
+}
+
+BOOL freerdp_certificate_publickey_encrypt(const rdpCertificate* cert, const BYTE* input,
+ size_t cbInput, BYTE** poutput, size_t* pcbOutput)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(poutput);
+ WINPR_ASSERT(pcbOutput);
+
+ BOOL ret = FALSE;
+ BYTE* output = NULL;
+ EVP_PKEY* pkey = X509_get0_pubkey(cert->x509);
+ if (!pkey)
+ return FALSE;
+
+ EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ return FALSE;
+
+ size_t outputSize = EVP_PKEY_size(pkey);
+ output = malloc(outputSize);
+ *pcbOutput = outputSize;
+
+ if (EVP_PKEY_encrypt_init(ctx) != 1 ||
+ EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) != 1 ||
+ EVP_PKEY_encrypt(ctx, output, pcbOutput, input, cbInput) != 1)
+ {
+ WLog_ERR(TAG, "error when setting up public key");
+ goto out;
+ }
+
+ *poutput = output;
+ output = NULL;
+ ret = TRUE;
+out:
+ EVP_PKEY_CTX_free(ctx);
+ free(output);
+ return ret;
+}
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+static RSA* freerdp_certificate_get_RSA(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+
+ if (!freerdp_certificate_is_rsa(cert))
+ return NULL;
+
+ EVP_PKEY* pubkey = X509_get0_pubkey(cert->x509);
+ if (!pubkey)
+ return NULL;
+
+ return EVP_PKEY_get1_RSA(pubkey);
+}
+#endif
+
+BYTE* freerdp_certificate_get_der(const rdpCertificate* cert, size_t* pLength)
+{
+ WINPR_ASSERT(cert);
+
+ if (pLength)
+ *pLength = 0;
+
+ const int rc = i2d_X509(cert->x509, NULL);
+ if (rc <= 0)
+ return NULL;
+
+ BYTE* ptr = calloc(rc + 1, sizeof(BYTE));
+ if (!ptr)
+ return NULL;
+ BYTE* i2d_ptr = ptr;
+
+ const int rc2 = i2d_X509(cert->x509, &i2d_ptr);
+ if (rc2 <= 0)
+ {
+ free(ptr);
+ return NULL;
+ }
+
+ if (pLength)
+ *pLength = (size_t)rc2;
+ return ptr;
+}
+
+BOOL freerdp_certificate_is_rsa(const rdpCertificate* cert)
+{
+ WINPR_ASSERT(cert);
+ return is_rsa_key(cert->x509);
+}
+
+BOOL freerdp_certificate_is_rdp_security_compatible(const rdpCertificate* cert)
+{
+ const rdpCertInfo* info = freerdp_certificate_get_info(cert);
+ if (!freerdp_certificate_is_rsa(cert) || !info || (info->ModulusLength != 2048 / 8))
+ {
+ WLog_INFO(TAG, "certificate is not RSA 2048, RDP security not supported.");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+char* freerdp_certificate_get_param(const rdpCertificate* cert, enum FREERDP_CERT_PARAM what,
+ size_t* psize)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(psize);
+
+ *psize = 0;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ const BIGNUM* bn = NULL;
+ RSA* rsa = freerdp_certificate_get_RSA(cert);
+ switch (what)
+ {
+ case FREERDP_CERT_RSA_E:
+ RSA_get0_key(rsa, NULL, &bn, NULL);
+ break;
+ case FREERDP_CERT_RSA_N:
+ RSA_get0_key(rsa, &bn, NULL, NULL);
+ break;
+ default:
+ RSA_free(rsa);
+ return NULL;
+ }
+ RSA_free(rsa);
+#else
+ EVP_PKEY* pkey = X509_get0_pubkey(cert->x509);
+ if (!pkey)
+ return NULL;
+
+ BIGNUM* bn = NULL;
+ switch (what)
+ {
+ case FREERDP_CERT_RSA_E:
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn))
+ return NULL;
+ break;
+ case FREERDP_CERT_RSA_N:
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn))
+ return NULL;
+ break;
+ default:
+ return NULL;
+ }
+#endif
+
+ const size_t bnsize = BN_num_bytes(bn);
+ char* rc = calloc(bnsize + 1, sizeof(char));
+ if (!rc)
+ goto fail;
+ BN_bn2bin(bn, (BYTE*)rc);
+ *psize = bnsize;
+
+fail:
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR < 3)
+ BN_free(bn);
+#endif
+ return rc;
+}
diff --git a/libfreerdp/crypto/certificate.h b/libfreerdp/crypto/certificate.h
new file mode 100644
index 0000000..ead71f8
--- /dev/null
+++ b/libfreerdp/crypto/certificate.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_LIB_CORE_CERTIFICATE_H
+#define FREERDP_LIB_CORE_CERTIFICATE_H
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/certificate.h>
+
+#include <openssl/x509.h>
+
+/* Certificate Version */
+#define CERT_CHAIN_VERSION_1 0x00000001
+#define CERT_CHAIN_VERSION_2 0x00000002
+#define CERT_CHAIN_VERSION_MASK 0x7FFFFFFF
+#define CERT_PERMANENTLY_ISSUED 0x00000000
+#define CERT_TEMPORARILY_ISSUED 0x80000000
+
+#define SIGNATURE_ALG_RSA 0x00000001
+#define KEY_EXCHANGE_ALG_RSA 0x00000001
+
+#define BB_RSA_KEY_BLOB 6
+#define BB_RSA_SIGNATURE_BLOB 8
+
+WINPR_ATTR_MALLOC(freerdp_certificate_free, 1)
+FREERDP_LOCAL rdpCertificate* freerdp_certificate_new_from_x509(const X509* xcert,
+ const STACK_OF(X509) * chain);
+
+FREERDP_LOCAL BOOL freerdp_certificate_read_server_cert(rdpCertificate* certificate,
+ const BYTE* server_cert, size_t length);
+FREERDP_LOCAL SSIZE_T freerdp_certificate_write_server_cert(const rdpCertificate* certificate,
+ UINT32 dwVersion, wStream* s);
+
+FREERDP_LOCAL rdpCertificate* freerdp_certificate_clone(const rdpCertificate* certificate);
+
+FREERDP_LOCAL const rdpCertInfo* freerdp_certificate_get_info(const rdpCertificate* certificate);
+
+/** \brief returns a pointer to a X509 structure.
+ * Call X509_free when done.
+ */
+FREERDP_LOCAL X509* freerdp_certificate_get_x509(rdpCertificate* certificate);
+
+FREERDP_LOCAL BOOL freerdp_certificate_publickey_encrypt(const rdpCertificate* cert,
+ const BYTE* input, size_t cbInput,
+ BYTE** poutput, size_t* pcbOutput);
+
+#endif /* FREERDP_LIB_CORE_CERTIFICATE_H */
diff --git a/libfreerdp/crypto/certificate_data.c b/libfreerdp/crypto/certificate_data.c
new file mode 100644
index 0000000..a48beb4
--- /dev/null
+++ b/libfreerdp/crypto/certificate_data.c
@@ -0,0 +1,255 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/path.h>
+
+#include <freerdp/settings.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/certificate_data.h>
+
+#include "certificate.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("crypto")
+
+struct rdp_certificate_data
+{
+ char* hostname;
+ UINT16 port;
+ rdpCertificate* cert;
+
+ char cached_hash[MAX_PATH + 10];
+ char* cached_subject;
+ char* cached_issuer;
+ char* cached_fingerprint;
+ char* cached_pem;
+};
+
+static const char* freerdp_certificate_data_hash_(const char* hostname, UINT16 port, char* name,
+ size_t length)
+{
+ _snprintf(name, length, "%s_%" PRIu16 ".pem", hostname, port);
+ return name;
+}
+
+static BOOL freerdp_certificate_data_load_cache(rdpCertificateData* data)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(data);
+
+ freerdp_certificate_data_hash_(data->hostname, data->port, data->cached_hash,
+ sizeof(data->cached_hash));
+ if (strnlen(data->cached_hash, sizeof(data->cached_hash)) == 0)
+ goto fail;
+
+ data->cached_subject = freerdp_certificate_get_subject(data->cert);
+ if (!data->cached_subject)
+ data->cached_subject = calloc(1, 1);
+
+ size_t pemlen = 0;
+ data->cached_pem = freerdp_certificate_get_pem(data->cert, &pemlen);
+ if (!data->cached_pem)
+ goto fail;
+
+ data->cached_fingerprint = freerdp_certificate_get_fingerprint(data->cert);
+ if (!data->cached_fingerprint)
+ goto fail;
+
+ data->cached_issuer = freerdp_certificate_get_issuer(data->cert);
+ if (!data->cached_issuer)
+ data->cached_issuer = calloc(1, 1);
+
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+static rdpCertificateData* freerdp_certificate_data_new_nocopy(const char* hostname, UINT16 port,
+ rdpCertificate* xcert)
+{
+ rdpCertificateData* certdata = NULL;
+
+ if (!hostname || !xcert)
+ goto fail;
+
+ certdata = (rdpCertificateData*)calloc(1, sizeof(rdpCertificateData));
+
+ if (!certdata)
+ goto fail;
+
+ certdata->port = port;
+ certdata->hostname = _strdup(hostname);
+ if (!certdata->hostname)
+ goto fail;
+ for (size_t i = 0; i < strlen(hostname); i++)
+ certdata->hostname[i] = tolower(certdata->hostname[i]);
+
+ certdata->cert = xcert;
+ if (!freerdp_certificate_data_load_cache(certdata))
+ {
+ certdata->cert = NULL;
+ goto fail;
+ }
+
+ return certdata;
+fail:
+ freerdp_certificate_data_free(certdata);
+ return NULL;
+}
+
+rdpCertificateData* freerdp_certificate_data_new(const char* hostname, UINT16 port,
+ const rdpCertificate* xcert)
+{
+ rdpCertificate* copy = freerdp_certificate_clone(xcert);
+ rdpCertificateData* data = freerdp_certificate_data_new_nocopy(hostname, port, copy);
+ if (!data)
+ freerdp_certificate_free(copy);
+ return data;
+}
+
+rdpCertificateData* freerdp_certificate_data_new_from_pem(const char* hostname, UINT16 port,
+ const char* pem, size_t length)
+{
+ if (!pem || (length == 0))
+ return NULL;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
+ rdpCertificateData* data = freerdp_certificate_data_new_nocopy(hostname, port, cert);
+ if (!data)
+ freerdp_certificate_free(cert);
+ return data;
+}
+
+rdpCertificateData* freerdp_certificate_data_new_from_file(const char* hostname, UINT16 port,
+ const char* file)
+{
+ if (!file)
+ return NULL;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_file(file);
+ rdpCertificateData* data = freerdp_certificate_data_new_nocopy(hostname, port, cert);
+ if (!data)
+ freerdp_certificate_free(cert);
+ return data;
+}
+
+void freerdp_certificate_data_free(rdpCertificateData* data)
+{
+ if (data == NULL)
+ return;
+
+ free(data->hostname);
+ freerdp_certificate_free(data->cert);
+ free(data->cached_subject);
+ free(data->cached_issuer);
+ free(data->cached_fingerprint);
+ free(data->cached_pem);
+
+ free(data);
+}
+
+const char* freerdp_certificate_data_get_host(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ return cert->hostname;
+}
+
+UINT16 freerdp_certificate_data_get_port(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ return cert->port;
+}
+
+const char* freerdp_certificate_data_get_pem(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_pem);
+
+ return cert->cached_pem;
+}
+
+const char* freerdp_certificate_data_get_subject(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_subject);
+
+ return cert->cached_subject;
+}
+
+const char* freerdp_certificate_data_get_issuer(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_issuer);
+
+ return cert->cached_issuer;
+}
+const char* freerdp_certificate_data_get_fingerprint(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_fingerprint);
+
+ return cert->cached_fingerprint;
+}
+
+BOOL freerdp_certificate_data_equal(const rdpCertificateData* a, const rdpCertificateData* b)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(a);
+ WINPR_ASSERT(b);
+
+ if (strcmp(a->hostname, b->hostname) != 0)
+ return FALSE;
+ if (a->port != b->port)
+ return FALSE;
+
+ const char* pem1 = freerdp_certificate_data_get_fingerprint(a);
+ const char* pem2 = freerdp_certificate_data_get_fingerprint(b);
+ if (pem1 && pem2)
+ rc = strcmp(pem1, pem2) == 0;
+ else
+ rc = pem1 == pem2;
+
+ return rc;
+}
+
+const char* freerdp_certificate_data_get_hash(const rdpCertificateData* cert)
+{
+ WINPR_ASSERT(cert);
+ WINPR_ASSERT(cert->cached_hash);
+
+ return cert->cached_hash;
+}
+
+char* freerdp_certificate_data_hash(const char* hostname, UINT16 port)
+{
+ char name[MAX_PATH + 10] = { 0 };
+ freerdp_certificate_data_hash_(hostname, port, name, sizeof(name));
+ return _strdup(name);
+}
diff --git a/libfreerdp/crypto/certificate_store.c b/libfreerdp/crypto/certificate_store.c
new file mode 100644
index 0000000..bd182b4
--- /dev/null
+++ b/libfreerdp/crypto/certificate_store.c
@@ -0,0 +1,207 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Certificate Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <winpr/crypto.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+
+#include <freerdp/settings.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/certificate_store.h>
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("crypto")
+
+struct rdp_certificate_store
+{
+ char* certs_path;
+ char* server_path;
+};
+
+static const char certificate_store_dir[] = "certs";
+static const char certificate_server_dir[] = "server";
+
+static char* freerdp_certificate_store_file_path(const rdpCertificateStore* store, const char* hash)
+{
+ const char* hosts = freerdp_certificate_store_get_hosts_path(store);
+
+ if (!hosts || !hash)
+ return NULL;
+
+ return GetCombinedPath(hosts, hash);
+}
+
+freerdp_certificate_store_result
+freerdp_certificate_store_contains_data(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ freerdp_certificate_store_result rc = CERT_STORE_NOT_FOUND;
+ const char* host = freerdp_certificate_data_get_host(data);
+ const UINT16 port = freerdp_certificate_data_get_port(data);
+
+ rdpCertificateData* loaded = freerdp_certificate_store_load_data(store, host, port);
+ if (!loaded)
+ goto fail;
+
+ rc = freerdp_certificate_data_equal(data, loaded) ? CERT_STORE_MATCH : CERT_STORE_MISMATCH;
+
+fail:
+ freerdp_certificate_data_free(loaded);
+ return rc;
+}
+
+BOOL freerdp_certificate_store_remove_data(rdpCertificateStore* store,
+ const rdpCertificateData* data)
+{
+ BOOL rc = TRUE;
+
+ WINPR_ASSERT(store);
+
+ const char* hash = freerdp_certificate_data_get_hash(data);
+ if (!hash)
+ return FALSE;
+ char* path = freerdp_certificate_store_file_path(store, hash);
+
+ if (!path)
+ return FALSE;
+
+ if (winpr_PathFileExists(path))
+ rc = winpr_DeleteFile(path);
+ free(path);
+ return rc;
+}
+
+BOOL freerdp_certificate_store_save_data(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ BOOL rc = FALSE;
+ const char* base = freerdp_certificate_store_get_hosts_path(store);
+ const char* hash = freerdp_certificate_data_get_hash(data);
+ char* path = freerdp_certificate_store_file_path(store, hash);
+ FILE* fp = NULL;
+
+ if (!winpr_PathFileExists(base))
+ {
+ if (!winpr_PathMakePath(base, NULL))
+ goto fail;
+ }
+
+ fp = winpr_fopen(path, "w");
+ if (!fp)
+ goto fail;
+
+ fprintf(fp, "%s", freerdp_certificate_data_get_pem(data));
+
+ rc = TRUE;
+fail:
+ if (fp)
+ fclose(fp);
+ free(path);
+ return rc;
+}
+
+rdpCertificateData* freerdp_certificate_store_load_data(rdpCertificateStore* store,
+ const char* host, UINT16 port)
+{
+ char* path = NULL;
+ rdpCertificateData* data = NULL;
+
+ WINPR_ASSERT(store);
+
+ path = freerdp_certificate_store_get_cert_path(store, host, port);
+ if (!path)
+ goto fail;
+
+ data = freerdp_certificate_data_new_from_file(host, port, path);
+
+fail:
+ free(path);
+ return data;
+}
+
+rdpCertificateStore* freerdp_certificate_store_new(const rdpSettings* settings)
+{
+ rdpCertificateStore* store = (rdpCertificateStore*)calloc(1, sizeof(rdpCertificateStore));
+
+ if (!store)
+ return NULL;
+
+ const char* base = freerdp_settings_get_string(settings, FreeRDP_ConfigPath);
+ if (!base)
+ goto fail;
+
+ store->certs_path = GetCombinedPath(base, certificate_store_dir);
+ store->server_path = GetCombinedPath(base, certificate_server_dir);
+ if (!store->certs_path || !store->server_path)
+ goto fail;
+
+ return store;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_certificate_store_free(store);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void freerdp_certificate_store_free(rdpCertificateStore* store)
+{
+ if (!store)
+ return;
+
+ free(store->certs_path);
+ free(store->server_path);
+ free(store);
+}
+
+const char* freerdp_certificate_store_get_certs_path(const rdpCertificateStore* store)
+{
+ WINPR_ASSERT(store);
+ return store->certs_path;
+}
+
+const char* freerdp_certificate_store_get_hosts_path(const rdpCertificateStore* store)
+{
+ WINPR_ASSERT(store);
+ return store->server_path;
+}
+
+char* freerdp_certificate_store_get_cert_path(const rdpCertificateStore* store, const char* host,
+ UINT16 port)
+{
+ WINPR_ASSERT(store);
+
+ char* hash = freerdp_certificate_data_hash(host, port);
+ if (!hash)
+ return NULL;
+ char* path = freerdp_certificate_store_file_path(store, hash);
+ free(hash);
+ return path;
+}
diff --git a/libfreerdp/crypto/crypto.c b/libfreerdp/crypto/crypto.c
new file mode 100644
index 0000000..37ddaa1
--- /dev/null
+++ b/libfreerdp/crypto/crypto.c
@@ -0,0 +1,260 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+
+#include <openssl/objects.h>
+#include <openssl/bn.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/crypto/crypto.h>
+
+#include "crypto.h"
+#include "privatekey.h"
+
+#define TAG FREERDP_TAG("crypto")
+
+static SSIZE_T crypto_rsa_common(const BYTE* input, size_t length, UINT32 key_length,
+ const BYTE* modulus, const BYTE* exponent, size_t exponent_size,
+ BYTE* output, size_t out_length)
+{
+ BN_CTX* ctx = NULL;
+ int output_length = -1;
+ BYTE* input_reverse = NULL;
+ BYTE* modulus_reverse = NULL;
+ BYTE* exponent_reverse = NULL;
+ BIGNUM* mod = NULL;
+ BIGNUM* exp = NULL;
+ BIGNUM* x = NULL;
+ BIGNUM* y = NULL;
+ size_t bufferSize = 0;
+
+ if (!input || !modulus || !exponent || !output)
+ return -1;
+
+ if ((size_t)exponent_size > INT_MAX / 2)
+ return -1;
+
+ if (key_length >= INT_MAX / 2 - exponent_size)
+ return -1;
+
+ bufferSize = 2ULL * key_length + exponent_size;
+ if ((size_t)length > bufferSize)
+ bufferSize = (size_t)length;
+
+ input_reverse = (BYTE*)calloc(bufferSize, 1);
+
+ if (!input_reverse)
+ return -1;
+
+ modulus_reverse = input_reverse + key_length;
+ exponent_reverse = modulus_reverse + key_length;
+ memcpy(modulus_reverse, modulus, key_length);
+ crypto_reverse(modulus_reverse, key_length);
+ memcpy(exponent_reverse, exponent, exponent_size);
+ crypto_reverse(exponent_reverse, exponent_size);
+ memcpy(input_reverse, input, length);
+ crypto_reverse(input_reverse, length);
+
+ if (!(ctx = BN_CTX_new()))
+ goto fail;
+
+ if (!(mod = BN_new()))
+ goto fail;
+
+ if (!(exp = BN_new()))
+ goto fail;
+
+ if (!(x = BN_new()))
+ goto fail;
+
+ if (!(y = BN_new()))
+ goto fail;
+
+ if (!BN_bin2bn(modulus_reverse, key_length, mod))
+ goto fail;
+
+ if (!BN_bin2bn(exponent_reverse, exponent_size, exp))
+ goto fail;
+ if (!BN_bin2bn(input_reverse, length, x))
+ goto fail;
+ if (BN_mod_exp(y, x, exp, mod, ctx) != 1)
+ goto fail;
+ output_length = BN_bn2bin(y, output);
+ if (output_length < 0)
+ goto fail;
+ if ((size_t)output_length > out_length)
+ goto fail;
+ crypto_reverse(output, output_length);
+
+ if ((size_t)output_length < key_length)
+ {
+ size_t diff = key_length - output_length;
+ if ((size_t)output_length + diff > out_length)
+ diff = out_length - (size_t)output_length;
+ memset(output + output_length, 0, diff);
+ }
+
+fail:
+ BN_free(y);
+ BN_clear_free(x);
+ BN_free(exp);
+ BN_free(mod);
+ BN_CTX_free(ctx);
+ free(input_reverse);
+ return output_length;
+}
+
+static SSIZE_T crypto_rsa_public(const BYTE* input, size_t length, const rdpCertInfo* cert,
+ BYTE* output, size_t output_length)
+{
+ WINPR_ASSERT(cert);
+ return crypto_rsa_common(input, length, cert->ModulusLength, cert->Modulus, cert->exponent,
+ sizeof(cert->exponent), output, output_length);
+}
+
+static SSIZE_T crypto_rsa_private(const BYTE* input, size_t length, const rdpPrivateKey* key,
+ BYTE* output, size_t output_length)
+{
+ WINPR_ASSERT(key);
+ const rdpCertInfo* info = freerdp_key_get_info(key);
+ WINPR_ASSERT(info);
+
+ size_t PrivateExponentLength = 0;
+ const BYTE* PrivateExponent = freerdp_key_get_exponent(key, &PrivateExponentLength);
+ return crypto_rsa_common(input, length, info->ModulusLength, info->Modulus, PrivateExponent,
+ PrivateExponentLength, output, output_length);
+}
+
+SSIZE_T crypto_rsa_public_encrypt(const BYTE* input, size_t length, const rdpCertInfo* cert,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_public(input, length, cert, output, output_length);
+}
+
+SSIZE_T crypto_rsa_public_decrypt(const BYTE* input, size_t length, const rdpCertInfo* cert,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_public(input, length, cert, output, output_length);
+}
+
+SSIZE_T crypto_rsa_private_encrypt(const BYTE* input, size_t length, const rdpPrivateKey* key,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_private(input, length, key, output, output_length);
+}
+
+SSIZE_T crypto_rsa_private_decrypt(const BYTE* input, size_t length, const rdpPrivateKey* key,
+ BYTE* output, size_t output_length)
+{
+ return crypto_rsa_private(input, length, key, output, output_length);
+}
+
+void crypto_reverse(BYTE* data, size_t length)
+{
+ if (length < 1)
+ return;
+
+ for (size_t i = 0, j = length - 1; i < j; i++, j--)
+ {
+ const BYTE temp = data[i];
+ data[i] = data[j];
+ data[j] = temp;
+ }
+}
+
+char* crypto_read_pem(const char* filename, size_t* plength)
+{
+ char* pem = NULL;
+ FILE* fp = NULL;
+
+ WINPR_ASSERT(filename);
+
+ if (plength)
+ *plength = 0;
+
+ fp = winpr_fopen(filename, "r");
+ if (!fp)
+ goto fail;
+ const int rs = _fseeki64(fp, 0, SEEK_END);
+ if (rs < 0)
+ goto fail;
+ const SSIZE_T size = _ftelli64(fp);
+ if (size < 0)
+ goto fail;
+ const int rc = _fseeki64(fp, 0, SEEK_SET);
+ if (rc < 0)
+ goto fail;
+
+ pem = calloc(size + 1, sizeof(char));
+ if (!pem)
+ goto fail;
+
+ const size_t fr = fread(pem, (size_t)size, 1, fp);
+ if (fr != 1)
+ goto fail;
+
+ if (plength)
+ *plength = (size_t)strnlen(pem, size);
+ fclose(fp);
+ return pem;
+
+fail:
+{
+ char buffer[8192] = { 0 };
+ WLog_WARN(TAG, "Failed to read PEM from file '%s' [%s]", filename,
+ winpr_strerror(errno, buffer, sizeof(buffer)));
+}
+ if (fp)
+ fclose(fp);
+ free(pem);
+ return NULL;
+}
+
+BOOL crypto_write_pem(const char* filename, const char* pem, size_t length)
+{
+ WINPR_ASSERT(filename);
+ WINPR_ASSERT(pem || (length == 0));
+
+ WINPR_ASSERT(filename);
+ WINPR_ASSERT(pem);
+
+ const size_t size = strnlen(pem, length) + 1;
+ size_t rc = 0;
+ FILE* fp = winpr_fopen(filename, "w");
+ if (!fp)
+ goto fail;
+ rc = fwrite(pem, 1, size, fp);
+ fclose(fp);
+fail:
+ if (rc == 0)
+ {
+ char buffer[8192] = { 0 };
+ WLog_WARN(TAG, "Failed to write PEM [%" PRIuz "] to file '%s' [%s]", length, filename,
+ winpr_strerror(errno, buffer, sizeof(buffer)));
+ }
+ return rc == size;
+}
diff --git a/libfreerdp/crypto/crypto.h b/libfreerdp/crypto/crypto.h
new file mode 100644
index 0000000..91ac2fe
--- /dev/null
+++ b/libfreerdp/crypto/crypto.h
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_LIB_CRYPTO_H
+#define FREERDP_LIB_CRYPTO_H
+
+/* OpenSSL includes windows.h */
+#include <winpr/windows.h>
+#include <winpr/custom-crypto.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/crypto/crypto.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL SSIZE_T crypto_rsa_public_encrypt(const BYTE* input, size_t length,
+ const rdpCertInfo* cert, BYTE* output,
+ size_t output_length);
+ FREERDP_LOCAL SSIZE_T crypto_rsa_public_decrypt(const BYTE* input, size_t length,
+ const rdpCertInfo* cert, BYTE* output,
+ size_t output_length);
+ FREERDP_LOCAL SSIZE_T crypto_rsa_private_encrypt(const BYTE* input, size_t length,
+ const rdpPrivateKey* key, BYTE* output,
+ size_t output_length);
+ FREERDP_LOCAL SSIZE_T crypto_rsa_private_decrypt(const BYTE* input, size_t length,
+ const rdpPrivateKey* key, BYTE* output,
+ size_t output_length);
+
+ FREERDP_LOCAL void crypto_reverse(BYTE* data, size_t length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CRYPTO_H */
diff --git a/libfreerdp/crypto/der.c b/libfreerdp/crypto/der.c
new file mode 100644
index 0000000..5fcbf4a
--- /dev/null
+++ b/libfreerdp/crypto/der.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Basic Encoding Rules (DER)
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/crypto/der.h>
+
+int _der_skip_length(int length)
+{
+ if (length > 0x7F && length <= 0xFF)
+ return 2;
+ else if (length > 0xFF)
+ return 3;
+ else
+ return 1;
+}
+
+int der_write_length(wStream* s, int length)
+{
+ if (length > 0x7F && length <= 0xFF)
+ {
+ Stream_Write_UINT8(s, 0x81);
+ Stream_Write_UINT8(s, length);
+ return 2;
+ }
+ else if (length > 0xFF)
+ {
+ Stream_Write_UINT8(s, 0x82);
+ Stream_Write_UINT16_BE(s, length);
+ return 3;
+ }
+ else
+ {
+ Stream_Write_UINT8(s, length);
+ return 1;
+ }
+}
+
+int der_get_content_length(int length)
+{
+ if (length > 0x81 && length <= 0x102)
+ return length - 3;
+ else if (length > 0x102)
+ return length - 4;
+ else
+ return length - 2;
+}
+
+int der_skip_contextual_tag(int length)
+{
+ return _der_skip_length(length) + 1;
+}
+
+int der_write_contextual_tag(wStream* s, BYTE tag, int length, BOOL pc)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_CTXT | ER_PC(pc)) | (ER_TAG_MASK & tag));
+ return der_write_length(s, length) + 1;
+}
+
+static void der_write_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_PC(pc)) | (ER_TAG_MASK & tag));
+}
+
+int der_skip_octet_string(int length)
+{
+ return 1 + _der_skip_length(length) + length;
+}
+
+void der_write_octet_string(wStream* s, BYTE* oct_str, int length)
+{
+ der_write_universal_tag(s, ER_TAG_OCTET_STRING, FALSE);
+ der_write_length(s, length);
+ Stream_Write(s, oct_str, length);
+}
+
+int der_skip_sequence_tag(int length)
+{
+ return 1 + _der_skip_length(length);
+}
+
+int der_write_sequence_tag(wStream* s, int length)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_CONSTRUCT) | (ER_TAG_MASK & ER_TAG_SEQUENCE));
+ return der_write_length(s, length) + 1;
+}
diff --git a/libfreerdp/crypto/er.c b/libfreerdp/crypto/er.c
new file mode 100644
index 0000000..8616e17
--- /dev/null
+++ b/libfreerdp/crypto/er.c
@@ -0,0 +1,445 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Encoding Rules (BER/DER common functions)
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Modified by Jiten Pathy
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/crypto/er.h>
+#include <freerdp/crypto/ber.h>
+#include <freerdp/crypto/der.h>
+
+void er_read_length(wStream* s, int* length)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (!length)
+ return;
+
+ *length = 0;
+ if (!s)
+ return;
+
+ if (byte & 0x80)
+ {
+ byte &= ~(0x80);
+
+ if (byte == 1)
+ Stream_Read_UINT8(s, *length);
+ if (byte == 2)
+ Stream_Read_UINT16_BE(s, *length);
+ }
+ else
+ {
+ *length = byte;
+ }
+}
+
+/**
+ * Write er length.
+ * @param s stream
+ * @param length length
+ */
+
+int er_write_length(wStream* s, int length, BOOL flag)
+{
+ if (flag)
+ return der_write_length(s, length);
+ else
+ return ber_write_length(s, length);
+}
+
+int _er_skip_length(int length)
+{
+ if (length > 0x7F)
+ return 3;
+ else
+ return 1;
+}
+
+int er_get_content_length(int length)
+{
+ if (length - 1 > 0x7F)
+ return length - 4;
+ else
+ return length - 2;
+}
+
+/**
+ * Read er Universal tag.
+ * @param s A pointer to the stream to write to
+ * @param tag er universally-defined tag
+ * @return \b TRUE for success
+ */
+
+BOOL er_read_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != (ER_CLASS_UNIV | ER_PC(pc) | (ER_TAG_MASK & tag)))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Write er Universal tag.
+ * @param s stream
+ * @param tag er universally-defined tag
+ * @param pc primitive (FALSE) or constructed (TRUE)
+ */
+
+void er_write_universal_tag(wStream* s, BYTE tag, BOOL pc)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_PC(pc)) | (ER_TAG_MASK & tag));
+}
+
+/**
+ * Read er Application tag.
+ * @param s stream
+ * @param tag er application-defined tag
+ * @param length length
+ */
+
+BOOL er_read_application_tag(wStream* s, BYTE tag, int* length)
+{
+ BYTE byte = 0;
+
+ if (tag > 30)
+ {
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_APPL | ER_CONSTRUCT) | ER_TAG_MASK))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != tag)
+ return FALSE;
+
+ er_read_length(s, length);
+ }
+ else
+ {
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_APPL | ER_CONSTRUCT) | (ER_TAG_MASK & tag)))
+ return FALSE;
+
+ er_read_length(s, length);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write er Application tag.
+ * @param s stream
+ * @param tag er application-defined tag
+ * @param length length
+ */
+
+void er_write_application_tag(wStream* s, BYTE tag, int length, BOOL flag)
+{
+ if (tag > 30)
+ {
+ Stream_Write_UINT8(s, (ER_CLASS_APPL | ER_CONSTRUCT) | ER_TAG_MASK);
+ Stream_Write_UINT8(s, tag);
+ er_write_length(s, length, flag);
+ }
+ else
+ {
+ Stream_Write_UINT8(s, (ER_CLASS_APPL | ER_CONSTRUCT) | (ER_TAG_MASK & tag));
+ er_write_length(s, length, flag);
+ }
+}
+
+BOOL er_read_contextual_tag(wStream* s, BYTE tag, int* length, BOOL pc)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_CTXT | ER_PC(pc)) | (ER_TAG_MASK & tag)))
+ {
+ Stream_Rewind(s, 1);
+ return FALSE;
+ }
+
+ er_read_length(s, length);
+
+ return TRUE;
+}
+
+int er_write_contextual_tag(wStream* s, BYTE tag, int length, BOOL pc, BOOL flag)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_CTXT | ER_PC(pc)) | (ER_TAG_MASK & tag));
+ return er_write_length(s, length, flag) + 1;
+}
+
+int er_skip_contextual_tag(int length)
+{
+ return _er_skip_length(length) + 1;
+}
+
+BOOL er_read_sequence_tag(wStream* s, int* length)
+{
+ BYTE byte = 0;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte != ((ER_CLASS_UNIV | ER_CONSTRUCT) | (ER_TAG_SEQUENCE_OF)))
+ return FALSE;
+
+ er_read_length(s, length);
+
+ return TRUE;
+}
+
+/**
+ * Write er SEQUENCE tag.
+ * @param s stream
+ * @param length length
+ */
+
+int er_write_sequence_tag(wStream* s, int length, BOOL flag)
+{
+ Stream_Write_UINT8(s, (ER_CLASS_UNIV | ER_CONSTRUCT) | (ER_TAG_MASK & ER_TAG_SEQUENCE));
+ return er_write_length(s, length, flag) + 1;
+}
+
+int er_skip_sequence(int length)
+{
+ return 1 + _er_skip_length(length) + length;
+}
+
+int er_skip_sequence_tag(int length)
+{
+ return 1 + _er_skip_length(length);
+}
+
+BOOL er_read_enumerated(wStream* s, BYTE* enumerated, BYTE count)
+{
+ int length = 0;
+
+ er_read_universal_tag(s, ER_TAG_ENUMERATED, FALSE);
+ er_read_length(s, &length);
+
+ if (length == 1)
+ Stream_Read_UINT8(s, *enumerated);
+ else
+ return FALSE;
+
+ /* check that enumerated value falls within expected range */
+ if (*enumerated + 1 > count)
+ return FALSE;
+
+ return TRUE;
+}
+
+void er_write_enumerated(wStream* s, BYTE enumerated, BYTE count, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_ENUMERATED, FALSE);
+ er_write_length(s, 1, flag);
+ Stream_Write_UINT8(s, enumerated);
+}
+
+BOOL er_read_bit_string(wStream* s, int* length, BYTE* padding)
+{
+ er_read_universal_tag(s, ER_TAG_BIT_STRING, FALSE);
+ er_read_length(s, length);
+ Stream_Read_UINT8(s, *padding);
+
+ return TRUE;
+}
+
+BOOL er_write_bit_string_tag(wStream* s, UINT32 length, BYTE padding, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_BIT_STRING, FALSE);
+ er_write_length(s, length, flag);
+ Stream_Write_UINT8(s, padding);
+ return TRUE;
+}
+
+BOOL er_read_octet_string(wStream* s, int* length)
+{
+ if (!er_read_universal_tag(s, ER_TAG_OCTET_STRING, FALSE))
+ return FALSE;
+ er_read_length(s, length);
+
+ return TRUE;
+}
+
+/**
+ * Write a er OCTET_STRING
+ * @param s stream
+ * @param oct_str octet string
+ * @param length string length
+ */
+
+void er_write_octet_string(wStream* s, BYTE* oct_str, int length, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_OCTET_STRING, FALSE);
+ er_write_length(s, length, flag);
+ Stream_Write(s, oct_str, length);
+}
+
+int er_write_octet_string_tag(wStream* s, int length, BOOL flag)
+{
+ er_write_universal_tag(s, ER_TAG_OCTET_STRING, FALSE);
+ er_write_length(s, length, flag);
+ return 1 + _er_skip_length(length);
+}
+
+int er_skip_octet_string(int length)
+{
+ return 1 + _er_skip_length(length) + length;
+}
+
+/**
+ * Read a er BOOLEAN
+ * @param s A pointer to the stream to read from
+ * @param value A pointer to read the data to
+ */
+
+BOOL er_read_BOOL(wStream* s, BOOL* value)
+{
+ int length = 0;
+ BYTE v = 0;
+
+ if (!er_read_universal_tag(s, ER_TAG_BOOLEAN, FALSE))
+ return FALSE;
+ er_read_length(s, &length);
+ if (length != 1)
+ return FALSE;
+ Stream_Read_UINT8(s, v);
+ *value = (v ? TRUE : FALSE);
+ return TRUE;
+}
+
+/**
+ * Write a er BOOLEAN
+ * @param s A pointer to the stream to write to
+ * @param value The value to write
+ */
+
+void er_write_BOOL(wStream* s, BOOL value)
+{
+ er_write_universal_tag(s, ER_TAG_BOOLEAN, FALSE);
+ er_write_length(s, 1, FALSE);
+ Stream_Write_UINT8(s, (value == TRUE) ? 0xFF : 0);
+}
+
+BOOL er_read_integer(wStream* s, UINT32* value)
+{
+ int length = 0;
+
+ er_read_universal_tag(s, ER_TAG_INTEGER, FALSE);
+ er_read_length(s, &length);
+
+ if (value == NULL)
+ {
+ Stream_Seek(s, length);
+ return TRUE;
+ }
+
+ if (length == 1)
+ {
+ Stream_Read_UINT8(s, *value);
+ }
+ else if (length == 2)
+ {
+ Stream_Read_UINT16_BE(s, *value);
+ }
+ else if (length == 3)
+ {
+ BYTE byte = 0;
+ Stream_Read_UINT8(s, byte);
+ Stream_Read_UINT16_BE(s, *value);
+ *value += (byte << 16);
+ }
+ else if (length == 4)
+ {
+ Stream_Read_UINT32_BE(s, *value);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write a er INTEGER
+ * @param s A pointer to the stream to write to
+ * @param value the value to write
+ */
+
+int er_write_integer(wStream* s, INT32 value)
+{
+ er_write_universal_tag(s, ER_TAG_INTEGER, FALSE);
+
+ if (value <= 127 && value >= -128)
+ {
+ er_write_length(s, 1, FALSE);
+ Stream_Write_UINT8(s, value);
+ return 2;
+ }
+ else if (value <= 32767 && value >= -32768)
+ {
+ er_write_length(s, 2, FALSE);
+ Stream_Write_UINT16_BE(s, value);
+ return 3;
+ }
+ else
+ {
+ er_write_length(s, 4, FALSE);
+ Stream_Write_UINT32_BE(s, value);
+ return 5;
+ }
+}
+
+int er_skip_integer(INT32 value)
+{
+ if (value <= 127 && value >= -128)
+ {
+ return _er_skip_length(1) + 2;
+ }
+ else if (value <= 32767 && value >= -32768)
+ {
+ return _er_skip_length(2) + 3;
+ }
+ else
+ {
+ return _er_skip_length(4) + 5;
+ }
+}
+
+BOOL er_read_integer_length(wStream* s, int* length)
+{
+ er_read_universal_tag(s, ER_TAG_INTEGER, FALSE);
+ er_read_length(s, length);
+ return TRUE;
+}
diff --git a/libfreerdp/crypto/opensslcompat.c b/libfreerdp/crypto/opensslcompat.c
new file mode 100644
index 0000000..314d6b5
--- /dev/null
+++ b/libfreerdp/crypto/opensslcompat.c
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OpenSSL Compatibility
+ *
+ * Copyright (C) 2016 Norbert Federa <norbert.federa@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "opensslcompat.h"
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+
+BIO_METHOD* BIO_meth_new(int type, const char* name)
+{
+ BIO_METHOD* m;
+ if (!(m = calloc(1, sizeof(BIO_METHOD))))
+ return NULL;
+ m->type = type;
+ m->name = name;
+ return m;
+}
+
+void RSA_get0_key(const RSA* r, const BIGNUM** n, const BIGNUM** e, const BIGNUM** d)
+{
+ if (n != NULL)
+ *n = r->n;
+ if (e != NULL)
+ *e = r->e;
+ if (d != NULL)
+ *d = r->d;
+}
+
+#endif /* OPENSSL < 1.1.0 || LIBRESSL */
diff --git a/libfreerdp/crypto/opensslcompat.h b/libfreerdp/crypto/opensslcompat.h
new file mode 100644
index 0000000..6982612
--- /dev/null
+++ b/libfreerdp/crypto/opensslcompat.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OpenSSL Compatibility
+ *
+ * Copyright (C) 2016 Norbert Federa <norbert.federa@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_LIB_CRYPTO_OPENSSLCOMPAT_H
+#define FREERDP_LIB_CRYPTO_OPENSSLCOMPAT_H
+
+#include <freerdp/config.h>
+
+#include <freerdp/api.h>
+
+#ifdef WITH_OPENSSL
+
+#include <openssl/opensslv.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+
+#include <openssl/bio.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#define BIO_get_data(b) (b)->ptr
+#define BIO_set_data(b, v) (b)->ptr = v
+#define BIO_get_init(b) (b)->init
+#define BIO_set_init(b, v) (b)->init = v
+#define BIO_get_next(b, v) (b)->next_bio
+#define BIO_set_next(b, v) (b)->next_bio = v
+#define BIO_get_shutdown(b) (b)->shutdown
+#define BIO_set_shutdown(b, v) (b)->shutdown = v
+#define BIO_get_retry_reason(b) (b)->retry_reason
+#define BIO_set_retry_reason(b, v) (b)->retry_reason = v
+
+#define BIO_meth_set_write(b, f) (b)->bwrite = (f)
+#define BIO_meth_set_read(b, f) (b)->bread = (f)
+#define BIO_meth_set_puts(b, f) (b)->bputs = (f)
+#define BIO_meth_set_gets(b, f) (b)->bgets = (f)
+#define BIO_meth_set_ctrl(b, f) (b)->ctrl = (f)
+#define BIO_meth_set_create(b, f) (b)->create = (f)
+#define BIO_meth_set_destroy(b, f) (b)->destroy = (f)
+#define BIO_meth_set_callback_ctrl(b, f) (b)->callback_ctrl = (f)
+
+BIO_METHOD* BIO_meth_new(int type, const char* name);
+void RSA_get0_key(const RSA* r, const BIGNUM** n, const BIGNUM** e, const BIGNUM** d);
+
+#endif /* OPENSSL < 1.1.0 || LIBRESSL */
+#endif /* WITH_OPENSSL */
+
+#endif /* FREERDP_LIB_CRYPTO_OPENSSLCOMPAT_H */
diff --git a/libfreerdp/crypto/per.c b/libfreerdp/crypto/per.c
new file mode 100644
index 0000000..8ccfa4d
--- /dev/null
+++ b/libfreerdp/crypto/per.c
@@ -0,0 +1,602 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ASN.1 Packed Encoding Rules (BER)
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/assert.h>
+#include <winpr/print.h>
+
+#include <freerdp/config.h>
+#include <freerdp/crypto/per.h>
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("crypto.per")
+
+/**
+ * Read PER length.
+ *
+ * @param s stream to read from
+ * @param length A pointer to return the length read, must not be NULL
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_length(wStream* s, UINT16* length)
+{
+ BYTE byte = 0;
+
+ WINPR_ASSERT(length);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, byte);
+
+ if (byte & 0x80)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ byte &= ~(0x80);
+ *length = (byte << 8);
+ Stream_Read_UINT8(s, byte);
+ *length += byte;
+ }
+ else
+ {
+ *length = byte;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write PER length.
+ * @param s stream
+ * @param length length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_length(wStream* s, UINT16 length)
+{
+ if (length > 0x7F)
+ {
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+ Stream_Write_UINT16_BE(s, (length | 0x8000));
+ }
+ else
+ {
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, (UINT8)length);
+ }
+ return TRUE;
+}
+
+/**
+ * Read PER choice.
+ * @param s stream
+ * @param choice choice
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_choice(wStream* s, BYTE* choice)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *choice);
+ return TRUE;
+}
+
+/**
+ * Write PER CHOICE.
+ * @param s stream
+ * @param choice index of chosen field
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_choice(wStream* s, BYTE choice)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, choice);
+ return TRUE;
+}
+
+/**
+ * Read PER selection.
+ * @param s stream
+ * @param selection selection
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_selection(wStream* s, BYTE* selection)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ WINPR_ASSERT(selection);
+ Stream_Read_UINT8(s, *selection);
+ return TRUE;
+}
+
+/**
+ * Write PER selection for OPTIONAL fields.
+ * @param s stream
+ * @param selection bit map of selected fields
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_selection(wStream* s, BYTE selection)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, selection);
+ return TRUE;
+}
+
+/**
+ * Read PER number of sets.
+ * @param s stream
+ * @param number number of sets
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_number_of_sets(wStream* s, BYTE* number)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ WINPR_ASSERT(number);
+ Stream_Read_UINT8(s, *number);
+ return TRUE;
+}
+
+/**
+ * Write PER number of sets for SET OF.
+ *
+ * @param s stream
+ * @param number number of sets
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_number_of_sets(wStream* s, BYTE number)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, number);
+ return TRUE;
+}
+
+/**
+ * Read PER padding with zeros.
+ *
+ * @param s A stream to read from
+ * @param length the data to write
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_padding(wStream* s, UINT16 length)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ Stream_Seek(s, length);
+ return TRUE;
+}
+
+/**
+ * Write PER padding with zeros.
+ * @param s A stream to write to
+ * @param length the data to write
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_padding(wStream* s, UINT16 length)
+{
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ return FALSE;
+ Stream_Zero(s, length);
+ return TRUE;
+}
+
+/**
+ * Read PER INTEGER.
+ * @param s stream
+ * @param integer integer
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_integer(wStream* s, UINT32* integer)
+{
+ UINT16 length = 0;
+
+ WINPR_ASSERT(integer);
+
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ if (length == 0)
+ *integer = 0;
+ else if (length == 1)
+ Stream_Read_UINT8(s, *integer);
+ else if (length == 2)
+ Stream_Read_UINT16_BE(s, *integer);
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * Write PER INTEGER.
+ * @param s stream
+ * @param integer integer
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_integer(wStream* s, UINT32 integer)
+{
+ if (integer <= UINT8_MAX)
+ {
+ if (!per_write_length(s, 1))
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, integer);
+ }
+ else if (integer <= UINT16_MAX)
+ {
+ if (!per_write_length(s, 2))
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+ Stream_Write_UINT16_BE(s, integer);
+ }
+ else if (integer <= UINT32_MAX)
+ {
+ if (!per_write_length(s, 4))
+ return FALSE;
+ if (!Stream_EnsureRemainingCapacity(s, 4))
+ return FALSE;
+ Stream_Write_UINT32_BE(s, integer);
+ }
+ return TRUE;
+}
+
+/**
+ * Read PER INTEGER (UINT16).
+ *
+ * @param s The stream to read from
+ * @param integer The integer result variable pointer, must not be NULL
+ * @param min minimum value
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_read_integer16(wStream* s, UINT16* integer, UINT16 min)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16_BE(s, *integer);
+
+ if (*integer > UINT16_MAX - min)
+ {
+ WLog_WARN(TAG, "PER uint16 invalid value %" PRIu16 " > %" PRIu16, *integer,
+ UINT16_MAX - min);
+ return FALSE;
+ }
+
+ *integer += min;
+
+ return TRUE;
+}
+
+/**
+ * Write PER INTEGER (UINT16).
+ * @param s stream
+ * @param integer integer
+ * @param min minimum value
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_integer16(wStream* s, UINT16 integer, UINT16 min)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 2))
+ return FALSE;
+ Stream_Write_UINT16_BE(s, integer - min);
+ return TRUE;
+}
+
+/**
+ * Read PER ENUMERATED.
+ *
+ * @param s The stream to read from
+ * @param enumerated enumerated result variable, must not be NULL
+ * @param count enumeration count
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_read_enumerated(wStream* s, BYTE* enumerated, BYTE count)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ WINPR_ASSERT(enumerated);
+ Stream_Read_UINT8(s, *enumerated);
+
+ /* check that enumerated value falls within expected range */
+ if (*enumerated + 1 > count)
+ {
+ WLog_WARN(TAG, "PER invalid data, expected %" PRIu8 " < %" PRIu8, *enumerated, count);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Write PER ENUMERATED.
+ *
+ * @param s The stream to write to
+ * @param enumerated enumerated
+ * @param count enumeration count
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_write_enumerated(wStream* s, BYTE enumerated, BYTE count)
+{
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return FALSE;
+ Stream_Write_UINT8(s, enumerated);
+ return TRUE;
+}
+
+static BOOL per_check_oid_and_log_mismatch(const BYTE* got, const BYTE* expect, size_t length)
+{
+ if (memcmp(got, expect, length) == 0)
+ {
+ return TRUE;
+ }
+ else
+ {
+ char* got_str = winpr_BinToHexString(got, length, TRUE);
+ char* expect_str = winpr_BinToHexString(expect, length, TRUE);
+
+ WLog_WARN(TAG, "PER OID mismatch, got %s, expected %s", got_str, expect_str);
+ free(got_str);
+ free(expect_str);
+ return FALSE;
+ }
+}
+
+/**
+ * Read PER OBJECT_IDENTIFIER (OID).
+ *
+ * @param s The stream to read from
+ * @param oid object identifier (OID)
+ * @warning It works correctly only for limited set of OIDs.
+ *
+ * @return \b TRUE for success, \b FALSE otherwise
+ */
+
+BOOL per_read_object_identifier(wStream* s, const BYTE oid[6])
+{
+ BYTE t12 = 0;
+ UINT16 length = 0;
+ BYTE a_oid[6] = { 0 };
+
+ if (!per_read_length(s, &length))
+ return FALSE;
+
+ if (length != 5)
+ {
+ WLog_WARN(TAG, "PER length, got %" PRIu16 ", expected 5", length);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ Stream_Read_UINT8(s, t12); /* first two tuples */
+ a_oid[0] = t12 / 40;
+ a_oid[1] = t12 % 40;
+
+ Stream_Read_UINT8(s, a_oid[2]); /* tuple 3 */
+ Stream_Read_UINT8(s, a_oid[3]); /* tuple 4 */
+ Stream_Read_UINT8(s, a_oid[4]); /* tuple 5 */
+ Stream_Read_UINT8(s, a_oid[5]); /* tuple 6 */
+
+ return per_check_oid_and_log_mismatch(a_oid, oid, sizeof(a_oid));
+}
+
+/**
+ * Write PER OBJECT_IDENTIFIER (OID)
+ * @param s stream
+ * @param oid object identifier (oid)
+ * @warning It works correctly only for limited set of OIDs.
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_object_identifier(wStream* s, const BYTE oid[6])
+{
+ BYTE t12 = oid[0] * 40 + oid[1];
+ if (!Stream_EnsureRemainingCapacity(s, 6))
+ return FALSE;
+ Stream_Write_UINT8(s, 5); /* length */
+ Stream_Write_UINT8(s, t12); /* first two tuples */
+ Stream_Write_UINT8(s, oid[2]); /* tuple 3 */
+ Stream_Write_UINT8(s, oid[3]); /* tuple 4 */
+ Stream_Write_UINT8(s, oid[4]); /* tuple 5 */
+ Stream_Write_UINT8(s, oid[5]); /* tuple 6 */
+ return TRUE;
+}
+
+/**
+ * Write PER string.
+ * @param s stream
+ * @param str string
+ * @param length string length
+ */
+
+static void per_write_string(wStream* s, BYTE* str, int length)
+{
+ for (int i = 0; i < length; i++)
+ Stream_Write_UINT8(s, str[i]);
+}
+
+/**
+ * Read PER OCTET_STRING.
+ *
+ * @param s The stream to read from
+ * @param oct_str octet string
+ * @param length string length
+ * @param min minimum length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_octet_string(wStream* s, const BYTE* oct_str, UINT16 length, UINT16 min)
+{
+ UINT16 mlength = 0;
+
+ if (!per_read_length(s, &mlength))
+ return FALSE;
+
+ if (mlength + min != length)
+ {
+ WLog_ERR(TAG, "length mismatch: %" PRIu16 "!= %" PRIu16, mlength + min, length);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ const BYTE* a_oct_str = Stream_ConstPointer(s);
+ Stream_Seek(s, length);
+
+ return per_check_oid_and_log_mismatch(a_oct_str, oct_str, length);
+}
+
+/**
+ * Write PER OCTET_STRING
+ * @param s stream
+ * @param oct_str octet string
+ * @param length string length
+ * @param min minimum string length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_octet_string(wStream* s, const BYTE* oct_str, UINT16 length, UINT16 min)
+{
+ UINT16 mlength = 0;
+
+ mlength = (length >= min) ? length - min : min;
+
+ if (!per_write_length(s, mlength))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ return FALSE;
+ for (UINT16 i = 0; i < length; i++)
+ Stream_Write_UINT8(s, oct_str[i]);
+ return TRUE;
+}
+
+/**
+ * Read PER NumericString.
+ * @param s stream
+ * @param min minimum string length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_read_numeric_string(wStream* s, UINT16 min)
+{
+ size_t length = 0;
+ UINT16 mlength = 0;
+
+ if (!per_read_length(s, &mlength))
+ return FALSE;
+
+ length = (mlength + min + 1) / 2;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
+ return FALSE;
+
+ Stream_Seek(s, length);
+ return TRUE;
+}
+
+/**
+ * Write PER NumericString.
+ * @param s stream
+ * @param num_str numeric string
+ * @param length string length
+ * @param min minimum string length
+ *
+ * @return \b TRUE for success, \b FALSE otherwise.
+ */
+
+BOOL per_write_numeric_string(wStream* s, const BYTE* num_str, UINT16 length, UINT16 min)
+{
+ UINT16 mlength = 0;
+ BYTE num = 0;
+ BYTE c1 = 0;
+ BYTE c2 = 0;
+
+ mlength = (length >= min) ? length - min : min;
+
+ if (!per_write_length(s, mlength))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, length))
+ return FALSE;
+ for (UINT16 i = 0; i < length; i += 2)
+ {
+ c1 = num_str[i];
+ c2 = ((i + 1) < length) ? num_str[i + 1] : 0x30;
+
+ c1 = (c1 - 0x30) % 10;
+ c2 = (c2 - 0x30) % 10;
+ num = (c1 << 4) | c2;
+
+ Stream_Write_UINT8(s, num); /* string */
+ }
+ return TRUE;
+}
diff --git a/libfreerdp/crypto/privatekey.c b/libfreerdp/crypto/privatekey.c
new file mode 100644
index 0000000..159157c
--- /dev/null
+++ b/libfreerdp/crypto/privatekey.c
@@ -0,0 +1,539 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Private key Handling
+ *
+ * Copyright 2011 Jiten Pathy
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include <winpr/crypto.h>
+
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+
+#include "privatekey.h"
+#include "cert_common.h"
+
+#include <freerdp/crypto/privatekey.h>
+
+#include <openssl/evp.h>
+
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+#include <openssl/core_names.h>
+#endif
+
+#include "x509_utils.h"
+#include "crypto.h"
+#include "opensslcompat.h"
+
+#define TAG FREERDP_TAG("crypto")
+
+struct rdp_private_key
+{
+ EVP_PKEY* evp;
+
+ rdpCertInfo cert;
+ BYTE* PrivateExponent;
+ DWORD PrivateExponentLength;
+};
+
+/*
+ * Terminal Services Signing Keys.
+ * Yes, Terminal Services Private Key is publicly available.
+ */
+
+static BYTE tssk_modulus[] = { 0x3d, 0x3a, 0x5e, 0xbd, 0x72, 0x43, 0x3e, 0xc9, 0x4d, 0xbb, 0xc1,
+ 0x1e, 0x4a, 0xba, 0x5f, 0xcb, 0x3e, 0x88, 0x20, 0x87, 0xef, 0xf5,
+ 0xc1, 0xe2, 0xd7, 0xb7, 0x6b, 0x9a, 0xf2, 0x52, 0x45, 0x95, 0xce,
+ 0x63, 0x65, 0x6b, 0x58, 0x3a, 0xfe, 0xef, 0x7c, 0xe7, 0xbf, 0xfe,
+ 0x3d, 0xf6, 0x5c, 0x7d, 0x6c, 0x5e, 0x06, 0x09, 0x1a, 0xf5, 0x61,
+ 0xbb, 0x20, 0x93, 0x09, 0x5f, 0x05, 0x6d, 0xea, 0x87 };
+
+static BYTE tssk_privateExponent[] = {
+ 0x87, 0xa7, 0x19, 0x32, 0xda, 0x11, 0x87, 0x55, 0x58, 0x00, 0x16, 0x16, 0x25, 0x65, 0x68, 0xf8,
+ 0x24, 0x3e, 0xe6, 0xfa, 0xe9, 0x67, 0x49, 0x94, 0xcf, 0x92, 0xcc, 0x33, 0x99, 0xe8, 0x08, 0x60,
+ 0x17, 0x9a, 0x12, 0x9f, 0x24, 0xdd, 0xb1, 0x24, 0x99, 0xc7, 0x3a, 0xb8, 0x0a, 0x7b, 0x0d, 0xdd,
+ 0x35, 0x07, 0x79, 0x17, 0x0b, 0x51, 0x9b, 0xb3, 0xc7, 0x10, 0x01, 0x13, 0xe7, 0x3f, 0xf3, 0x5f
+};
+
+static const rdpPrivateKey tssk = { .PrivateExponent = tssk_privateExponent,
+ .PrivateExponentLength = sizeof(tssk_privateExponent),
+ .cert = { .Modulus = tssk_modulus,
+ .ModulusLength = sizeof(tssk_modulus) } };
+const rdpPrivateKey* priv_key_tssk = &tssk;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+static RSA* evp_pkey_to_rsa(const rdpPrivateKey* key)
+{
+ if (!freerdp_key_is_rsa(key))
+ {
+ WLog_WARN(TAG, "Key is no RSA key");
+ return NULL;
+ }
+
+ RSA* rsa = NULL;
+ BIO* bio = BIO_new(
+#if defined(LIBRESSL_VERSION_NUMBER)
+ BIO_s_mem()
+#else
+ BIO_s_secmem()
+#endif
+ );
+ if (!bio)
+ return NULL;
+ const int rc = PEM_write_bio_PrivateKey(bio, key->evp, NULL, NULL, 0, NULL, NULL);
+ if (rc != 1)
+ goto fail;
+ rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
+fail:
+ BIO_free_all(bio);
+ return rsa;
+}
+#endif
+
+static EVP_PKEY* evp_pkey_utils_from_pem(const char* data, size_t len, BOOL fromFile)
+{
+ EVP_PKEY* evp = NULL;
+ BIO* bio = NULL;
+ if (fromFile)
+ bio = BIO_new_file(data, "rb");
+ else
+ bio = BIO_new_mem_buf(data, len);
+
+ if (!bio)
+ {
+ WLog_ERR(TAG, "BIO_new failed for private key");
+ return NULL;
+ }
+
+ evp = PEM_read_bio_PrivateKey(bio, NULL, NULL, 0);
+ BIO_free_all(bio);
+ if (!evp)
+ WLog_ERR(TAG, "PEM_read_bio_PrivateKey returned NULL [input length %" PRIuz "]", len);
+
+ return evp;
+}
+
+static BOOL key_read_private(rdpPrivateKey* key)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(key->evp);
+
+ /* The key is not an RSA key, that means we just return success. */
+ if (!freerdp_key_is_rsa(key))
+ return TRUE;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = evp_pkey_to_rsa(key);
+ if (!rsa)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "unable to load RSA key: %s.",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto fail;
+ }
+
+ switch (RSA_check_key(rsa))
+ {
+ case 0:
+ WLog_ERR(TAG, "invalid RSA key");
+ goto fail;
+
+ case 1:
+ /* Valid key. */
+ break;
+
+ default:
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "unexpected error when checking RSA key: %s.",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto fail;
+ }
+ }
+
+ const BIGNUM* rsa_e = NULL;
+ const BIGNUM* rsa_n = NULL;
+ const BIGNUM* rsa_d = NULL;
+
+ RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d);
+#else
+ BIGNUM* rsa_e = NULL;
+ BIGNUM* rsa_n = NULL;
+ BIGNUM* rsa_d = NULL;
+
+ if (!EVP_PKEY_get_bn_param(key->evp, OSSL_PKEY_PARAM_RSA_N, &rsa_n))
+ goto fail;
+ if (!EVP_PKEY_get_bn_param(key->evp, OSSL_PKEY_PARAM_RSA_E, &rsa_e))
+ goto fail;
+ if (!EVP_PKEY_get_bn_param(key->evp, OSSL_PKEY_PARAM_RSA_D, &rsa_d))
+ goto fail;
+#endif
+ if (BN_num_bytes(rsa_e) > 4)
+ {
+ WLog_ERR(TAG, "RSA public exponent too large");
+ goto fail;
+ }
+
+ if (!read_bignum(&key->PrivateExponent, &key->PrivateExponentLength, rsa_d, TRUE))
+ goto fail;
+
+ if (!cert_info_create(&key->cert, rsa_n, rsa_e))
+ goto fail;
+ rc = TRUE;
+fail:
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA_free(rsa);
+#else
+ BN_free(rsa_d);
+ BN_free(rsa_e);
+ BN_free(rsa_n);
+#endif
+ return rc;
+}
+
+rdpPrivateKey* freerdp_key_new_from_pem(const char* pem)
+{
+ rdpPrivateKey* key = freerdp_key_new();
+ if (!key || !pem)
+ goto fail;
+ key->evp = evp_pkey_utils_from_pem(pem, strlen(pem), FALSE);
+ if (!key->evp)
+ goto fail;
+ if (!key_read_private(key))
+ goto fail;
+ return key;
+fail:
+ freerdp_key_free(key);
+ return NULL;
+}
+
+rdpPrivateKey* freerdp_key_new_from_file(const char* keyfile)
+{
+
+ rdpPrivateKey* key = freerdp_key_new();
+ if (!key || !keyfile)
+ goto fail;
+
+ key->evp = evp_pkey_utils_from_pem(keyfile, strlen(keyfile), TRUE);
+ if (!key->evp)
+ goto fail;
+ if (!key_read_private(key))
+ goto fail;
+ return key;
+fail:
+ freerdp_key_free(key);
+ return NULL;
+}
+
+rdpPrivateKey* freerdp_key_new(void)
+{
+ return calloc(1, sizeof(rdpPrivateKey));
+}
+
+rdpPrivateKey* freerdp_key_clone(const rdpPrivateKey* key)
+{
+ if (!key)
+ return NULL;
+
+ rdpPrivateKey* _key = (rdpPrivateKey*)calloc(1, sizeof(rdpPrivateKey));
+
+ if (!_key)
+ return NULL;
+
+ if (key->evp)
+ {
+ _key->evp = key->evp;
+ if (!_key->evp)
+ goto out_fail;
+ EVP_PKEY_up_ref(_key->evp);
+ }
+
+ if (key->PrivateExponent)
+ {
+ _key->PrivateExponent = (BYTE*)malloc(key->PrivateExponentLength);
+
+ if (!_key->PrivateExponent)
+ goto out_fail;
+
+ CopyMemory(_key->PrivateExponent, key->PrivateExponent, key->PrivateExponentLength);
+ _key->PrivateExponentLength = key->PrivateExponentLength;
+ }
+
+ if (!cert_info_clone(&_key->cert, &key->cert))
+ goto out_fail;
+
+ return _key;
+out_fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_key_free(_key);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void freerdp_key_free(rdpPrivateKey* key)
+{
+ if (!key)
+ return;
+
+ EVP_PKEY_free(key->evp);
+ if (key->PrivateExponent)
+ memset(key->PrivateExponent, 0, key->PrivateExponentLength);
+ free(key->PrivateExponent);
+ cert_info_free(&key->cert);
+ free(key);
+}
+
+const rdpCertInfo* freerdp_key_get_info(const rdpPrivateKey* key)
+{
+ WINPR_ASSERT(key);
+ if (!freerdp_key_is_rsa(key))
+ return NULL;
+ return &key->cert;
+}
+
+const BYTE* freerdp_key_get_exponent(const rdpPrivateKey* key, size_t* plength)
+{
+ WINPR_ASSERT(key);
+ if (!freerdp_key_is_rsa(key))
+ {
+ if (plength)
+ *plength = 0;
+ return NULL;
+ }
+
+ if (plength)
+ *plength = key->PrivateExponentLength;
+ return key->PrivateExponent;
+}
+
+EVP_PKEY* freerdp_key_get_evp_pkey(const rdpPrivateKey* key)
+{
+ WINPR_ASSERT(key);
+
+ EVP_PKEY* evp = key->evp;
+ WINPR_ASSERT(evp);
+ EVP_PKEY_up_ref(evp);
+ return evp;
+}
+
+BOOL freerdp_key_is_rsa(const rdpPrivateKey* key)
+{
+ WINPR_ASSERT(key);
+ if (key == priv_key_tssk)
+ return TRUE;
+
+ WINPR_ASSERT(key->evp);
+ return (EVP_PKEY_id(key->evp) == EVP_PKEY_RSA);
+}
+
+size_t freerdp_key_get_bits(const rdpPrivateKey* key)
+{
+ int rc = -1;
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = evp_pkey_to_rsa(key);
+ if (rsa)
+ {
+ rc = RSA_bits(rsa);
+ RSA_free(rsa);
+ }
+#else
+ rc = EVP_PKEY_get_bits(key->evp);
+#endif
+
+ return rc;
+}
+
+BOOL freerdp_key_generate(rdpPrivateKey* key, size_t key_length)
+{
+ BOOL rc = FALSE;
+
+#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
+ RSA* rsa = NULL;
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+ rsa = RSA_generate_key(key_length, RSA_F4, NULL, NULL);
+#else
+ {
+ BIGNUM* bn = BN_secure_new();
+
+ if (!bn)
+ return FALSE;
+
+ rsa = RSA_new();
+
+ if (!rsa)
+ {
+ BN_clear_free(bn);
+ return FALSE;
+ }
+
+ BN_set_word(bn, RSA_F4);
+ const int res = RSA_generate_key_ex(rsa, key_length, bn, NULL);
+ BN_clear_free(bn);
+
+ if (res != 1)
+ return FALSE;
+ }
+#endif
+
+ EVP_PKEY_free(key->evp);
+ key->evp = EVP_PKEY_new();
+
+ if (!EVP_PKEY_assign_RSA(key->evp, rsa))
+ {
+ EVP_PKEY_free(key->evp);
+ key->evp = NULL;
+ RSA_free(rsa);
+ return FALSE;
+ }
+
+ rc = TRUE;
+#else
+ EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+ if (!pctx)
+ return FALSE;
+
+ if (EVP_PKEY_keygen_init(pctx) != 1)
+ goto fail;
+
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, key_length) != 1)
+ goto fail;
+
+ EVP_PKEY_free(key->evp);
+ key->evp = NULL;
+
+ if (EVP_PKEY_generate(pctx, &key->evp) != 1)
+ goto fail;
+
+ rc = TRUE;
+fail:
+ EVP_PKEY_CTX_free(pctx);
+#endif
+ return rc;
+}
+
+char* freerdp_key_get_param(const rdpPrivateKey* key, enum FREERDP_KEY_PARAM param, size_t* plength)
+{
+ BYTE* buf = NULL;
+
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(plength);
+
+ *plength = 0;
+
+ BIGNUM* bn = NULL;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+
+ const char* pk = NULL;
+ switch (param)
+ {
+ case FREERDP_KEY_PARAM_RSA_D:
+ pk = OSSL_PKEY_PARAM_RSA_D;
+ break;
+ case FREERDP_KEY_PARAM_RSA_E:
+ pk = OSSL_PKEY_PARAM_RSA_E;
+ break;
+ case FREERDP_KEY_PARAM_RSA_N:
+ pk = OSSL_PKEY_PARAM_RSA_N;
+ break;
+ default:
+ return NULL;
+ }
+
+ if (!EVP_PKEY_get_bn_param(key->evp, pk, &bn))
+ return NULL;
+#else
+ {
+ const RSA* rsa = EVP_PKEY_get0_RSA(key->evp);
+ if (!rsa)
+ return NULL;
+
+ const BIGNUM* cbn = NULL;
+ switch (param)
+ {
+ case FREERDP_KEY_PARAM_RSA_D:
+ cbn = RSA_get0_d(rsa);
+ break;
+ case FREERDP_KEY_PARAM_RSA_E:
+ cbn = RSA_get0_e(rsa);
+ break;
+ case FREERDP_KEY_PARAM_RSA_N:
+ cbn = RSA_get0_n(rsa);
+ break;
+ default:
+ return NULL;
+ }
+ if (!cbn)
+ return NULL;
+ bn = BN_dup(cbn);
+ if (!bn)
+ return NULL;
+ }
+#endif
+
+ const int length = BN_num_bytes(bn);
+ if (length < 0)
+ goto fail;
+
+ const size_t alloc_size = (size_t)length + 1ull;
+ buf = calloc(alloc_size, sizeof(BYTE));
+ if (!buf)
+ goto fail;
+
+ const int bnlen = BN_bn2bin(bn, buf);
+ if (bnlen != length)
+ {
+ free(buf);
+ buf = NULL;
+ }
+ else
+ *plength = length;
+
+fail:
+ BN_free(bn);
+ return (char*)buf;
+}
+
+WINPR_DIGEST_CTX* freerdp_key_digest_sign(rdpPrivateKey* key, WINPR_MD_TYPE digest)
+{
+ WINPR_DIGEST_CTX* md_ctx = winpr_Digest_New();
+ if (!md_ctx)
+ return NULL;
+
+ if (!winpr_DigestSign_Init(md_ctx, digest, key->evp))
+ {
+ winpr_Digest_Free(md_ctx);
+ return NULL;
+ }
+ return md_ctx;
+}
diff --git a/libfreerdp/crypto/privatekey.h b/libfreerdp/crypto/privatekey.h
new file mode 100644
index 0000000..418abf5
--- /dev/null
+++ b/libfreerdp/crypto/privatekey.h
@@ -0,0 +1,67 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Private key Handling
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_LIB_CORE_PRIVATEKEY_H
+#define FREERDP_LIB_CORE_PRIVATEKEY_H
+
+#include <freerdp/api.h>
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/crypto/privatekey.h>
+
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ enum FREERDP_KEY_PARAM
+ {
+ FREERDP_KEY_PARAM_RSA_D,
+ FREERDP_KEY_PARAM_RSA_E,
+ FREERDP_KEY_PARAM_RSA_N
+ };
+
+ FREERDP_LOCAL rdpPrivateKey* freerdp_key_clone(const rdpPrivateKey* key);
+
+ FREERDP_LOCAL const rdpCertInfo* freerdp_key_get_info(const rdpPrivateKey* key);
+ FREERDP_LOCAL const BYTE* freerdp_key_get_exponent(const rdpPrivateKey* key, size_t* plength);
+
+ FREERDP_LOCAL BOOL freerdp_key_generate(rdpPrivateKey* key, size_t bits);
+
+ /** \brief returns a pointer to a EVP_PKEY structure.
+ * Call EVP_PKEY_free when done.
+ */
+ FREERDP_LOCAL EVP_PKEY* freerdp_key_get_evp_pkey(const rdpPrivateKey* key);
+
+ FREERDP_LOCAL char* freerdp_key_get_param(const rdpPrivateKey* key,
+ enum FREERDP_KEY_PARAM param, size_t* plength);
+
+ FREERDP_LOCAL WINPR_DIGEST_CTX* freerdp_key_digest_sign(rdpPrivateKey* key,
+ WINPR_MD_TYPE digest);
+
+ FREERDP_LOCAL extern const rdpPrivateKey* priv_key_tssk;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CORE_PRIVATEKEY_H */
diff --git a/libfreerdp/crypto/test/CMakeLists.txt b/libfreerdp/crypto/test/CMakeLists.txt
new file mode 100644
index 0000000..994c43f
--- /dev/null
+++ b/libfreerdp/crypto/test/CMakeLists.txt
@@ -0,0 +1,33 @@
+
+set(MODULE_NAME "TestFreeRDPCrypto")
+set(MODULE_PREFIX "TEST_FREERDP_CRYPTO")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestKnownHosts.c
+ TestBase64.c
+ Test_x509_utils.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(TEST_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_definitions(-DTEST_SOURCE_DIR="${TEST_PATH}")
+target_link_libraries(${MODULE_NAME} freerdp winpr ${OPENSSL_LIBRARIES})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")
+
diff --git a/libfreerdp/crypto/test/TestBase64.c b/libfreerdp/crypto/test/TestBase64.c
new file mode 100644
index 0000000..d133637
--- /dev/null
+++ b/libfreerdp/crypto/test/TestBase64.c
@@ -0,0 +1,178 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/crypto/crypto.h>
+
+struct Encode64test
+{
+ const char* input;
+ size_t len;
+ const char* output;
+};
+
+static const struct Encode64test encodeTests_base64[] = {
+ { "\x00", 1, "AA==" },
+ { "\x00\x00", 2, "AAA=" },
+ { "\x00\x00\x00", 3, "AAAA" },
+ { "0123456", 7, "MDEyMzQ1Ng==" },
+ { "90123456", 8, "OTAxMjM0NTY=" },
+ { "890123456", 9, "ODkwMTIzNDU2" },
+ { "7890123456", 10, "Nzg5MDEyMzQ1Ng==" },
+
+ { NULL, -1, NULL }, /* /!\ last one /!\ */
+};
+
+static const struct Encode64test encodeTests_base64url[] = {
+ { "\x00", 1, "AA" },
+ { "\x00\x00", 2, "AAA" },
+ { "\x00\x00\x00", 3, "AAAA" },
+ { "01?34>6", 7, "MDE_MzQ-Ng" },
+ { "90123456", 8, "OTAxMjM0NTY" },
+ { "890123456", 9, "ODkwMTIzNDU2" },
+ { "78?01>3456", 10, "Nzg_MDE-MzQ1Ng" },
+
+ { NULL, -1, NULL }, /* /!\ last one /!\ */
+};
+
+int TestBase64(int argc, char* argv[])
+{
+ int testNb = 0;
+ size_t outLen = 0;
+ BYTE* decoded = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ testNb++;
+ fprintf(stderr, "%d:encode base64...", testNb);
+
+ for (int i = 0; encodeTests_base64[i].input; i++)
+ {
+ char* encoded = crypto_base64_encode((const BYTE*)encodeTests_base64[i].input,
+ encodeTests_base64[i].len);
+
+ if (strcmp(encodeTests_base64[i].output, encoded))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(encoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:encode base64url...", testNb);
+
+ for (int i = 0; encodeTests_base64url[i].input; i++)
+ {
+ char* encoded = crypto_base64url_encode((const BYTE*)encodeTests_base64url[i].input,
+ encodeTests_base64url[i].len);
+
+ if (strcmp(encodeTests_base64url[i].output, encoded))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(encoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:decode base64...", testNb);
+
+ for (int i = 0; encodeTests_base64[i].input; i++)
+ {
+ crypto_base64_decode(encodeTests_base64[i].output, strlen(encodeTests_base64[i].output),
+ &decoded, &outLen);
+
+ if (!decoded || (outLen != encodeTests_base64[i].len) ||
+ memcmp(encodeTests_base64[i].input, decoded, outLen))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(decoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:decode base64url...", testNb);
+
+ for (int i = 0; encodeTests_base64url[i].input; i++)
+ {
+ crypto_base64url_decode(encodeTests_base64url[i].output,
+ strlen(encodeTests_base64url[i].output), &decoded, &outLen);
+
+ if (!decoded || (outLen != encodeTests_base64url[i].len) ||
+ memcmp(encodeTests_base64url[i].input, decoded, outLen))
+ {
+ fprintf(stderr, "ko, error for string %d\n", i);
+ return -1;
+ }
+
+ free(decoded);
+ }
+
+ fprintf(stderr, "ok\n");
+ testNb++;
+ fprintf(stderr, "%d:decode base64 errors...", testNb);
+ crypto_base64_decode("000", 3, &decoded, &outLen);
+
+ if (decoded)
+ {
+ fprintf(stderr, "ko, badly padded string\n");
+ return -1;
+ }
+
+ crypto_base64_decode("0=00", 4, &decoded, &outLen);
+
+ if (decoded)
+ {
+ fprintf(stderr, "ko, = in a wrong place\n");
+ return -1;
+ }
+
+ crypto_base64_decode("00=0", 4, &decoded, &outLen);
+
+ if (decoded)
+ {
+ fprintf(stderr, "ko, = in a wrong place\n");
+ return -1;
+ }
+ fprintf(stderr, "ok\n");
+ testNb++;
+
+ /* test the encode_ex version that will add \r\n */
+ fprintf(stderr, "%d:encode base64 with crLf...", testNb);
+ const char* longStr = "01234567890123456789012345678901234567890123456789";
+ const char* longStrExpected =
+ "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3\r\nODk=\r\n";
+
+ char* encoded = crypto_base64_encode_ex((const BYTE*)longStr, strlen(longStr), TRUE);
+ if (!encoded || strcmp(encoded, longStrExpected) != 0)
+ {
+ fprintf(stderr, "problem with encode with CRLF\n");
+ return -1;
+ }
+ free(encoded);
+ fprintf(stderr, "ok\n");
+
+ return 0;
+}
diff --git a/libfreerdp/crypto/test/TestKnownHosts.c b/libfreerdp/crypto/test/TestKnownHosts.c
new file mode 100644
index 0000000..e3a4264
--- /dev/null
+++ b/libfreerdp/crypto/test/TestKnownHosts.c
@@ -0,0 +1,394 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/crypto/certificate_store.h>
+
+/* Some certificates copied from /usr/share/ca-certificates */
+static const char pem1[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH\n"
+ "MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM\n"
+ "QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy\n"
+ "MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl\n"
+ "cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB\n"
+ "AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM\n"
+ "f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX\n"
+ "mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7\n"
+ "zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P\n"
+ "fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc\n"
+ "vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4\n"
+ "Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp\n"
+ "zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO\n"
+ "Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW\n"
+ "k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+\n"
+ "DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF\n"
+ "lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n"
+ "HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW\n"
+ "Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1\n"
+ "d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z\n"
+ "XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR\n"
+ "gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3\n"
+ "d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv\n"
+ "J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg\n"
+ "DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM\n"
+ "+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy\n"
+ "F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9\n"
+ "SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws\n"
+ "E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl\n"
+ "-----END CERTIFICATE-----";
+
+static const char pem2[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH\n"
+ "MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM\n"
+ "QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy\n"
+ "MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl\n"
+ "cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB\n"
+ "AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv\n"
+ "CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg\n"
+ "GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu\n"
+ "XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd\n"
+ "re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu\n"
+ "PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1\n"
+ "mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K\n"
+ "8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj\n"
+ "x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR\n"
+ "nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0\n"
+ "kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok\n"
+ "twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n"
+ "HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp\n"
+ "8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT\n"
+ "vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT\n"
+ "z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA\n"
+ "pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb\n"
+ "pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB\n"
+ "R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R\n"
+ "RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk\n"
+ "0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC\n"
+ "5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF\n"
+ "izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn\n"
+ "yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC\n"
+ "-----END CERTIFICATE-----";
+
+static const char pem3[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw\n"
+ "CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n"
+ "MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n"
+ "MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n"
+ "Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA\n"
+ "IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout\n"
+ "736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A\n"
+ "DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\n"
+ "DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk\n"
+ "fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA\n"
+ "njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd\n"
+ "-----END CERTIFICATE-----";
+
+static const char pem4[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw\n"
+ "CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n"
+ "MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n"
+ "MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n"
+ "Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA\n"
+ "IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu\n"
+ "hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l\n"
+ "xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\n"
+ "DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0\n"
+ "CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx\n"
+ "sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==\n"
+ "-----END CERTIFICATE-----";
+
+static int prepare(const char* currentFileV2)
+{
+ int rc = -1;
+ const char* hosts[] = { "#somecomment\r\n"
+ "someurl 3389 ff:11:22:dd c3ViamVjdA== aXNzdWVy\r\n"
+ " \t#anothercomment\r\n"
+ "otherurl\t3389\taa:bb:cc:dd\tsubject2\tissuer2\r" };
+ FILE* fc = NULL;
+ fc = winpr_fopen(currentFileV2, "w+");
+
+ if (!fc)
+ goto finish;
+
+ for (size_t i = 0; i < ARRAYSIZE(hosts); i++)
+ {
+ if (fwrite(hosts[i], strlen(hosts[i]), 1, fc) != 1)
+ goto finish;
+ }
+
+ rc = 0;
+finish:
+
+ if (fc)
+ fclose(fc);
+
+ return rc;
+}
+
+static BOOL setup_config(rdpSettings** settings)
+{
+ BOOL rc = FALSE;
+ char* path = NULL;
+ char sname[8192];
+ SYSTEMTIME systemTime;
+
+ if (!settings)
+ goto fail;
+ *settings = freerdp_settings_new(0);
+ if (!*settings)
+ goto fail;
+
+ GetSystemTime(&systemTime);
+ sprintf_s(sname, sizeof(sname),
+ "TestKnownHostsCurrent-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16
+ "%02" PRIu16 "%04" PRIu16,
+ systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour,
+ systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
+
+ path = GetKnownSubPath(KNOWN_PATH_TEMP, sname);
+ if (!path)
+ goto fail;
+ if (!winpr_PathFileExists(path))
+ {
+ if (!CreateDirectoryA(path, NULL))
+ {
+ fprintf(stderr, "Could not create %s!\n", path);
+ goto fail;
+ }
+ }
+
+ rc = freerdp_settings_set_string(*settings, FreeRDP_ConfigPath, path);
+fail:
+ free(path);
+ return rc;
+}
+
+static BOOL equal(const char* a, const char* b)
+{
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+ return strcmp(a, b) == 0;
+}
+
+static BOOL compare(const rdpCertificateData* data, const rdpCertificateData* stored)
+{
+ if (!data || !stored)
+ return FALSE;
+ if (!equal(freerdp_certificate_data_get_subject(data),
+ freerdp_certificate_data_get_subject(stored)))
+ return FALSE;
+ if (!equal(freerdp_certificate_data_get_issuer(data),
+ freerdp_certificate_data_get_issuer(stored)))
+ return FALSE;
+ if (!equal(freerdp_certificate_data_get_fingerprint(data),
+ freerdp_certificate_data_get_fingerprint(stored)))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL pem_equal(const char* a, const char* b)
+{
+ return strcmp(a, b) == 0;
+}
+
+static BOOL compare_ex(const rdpCertificateData* data, const rdpCertificateData* stored)
+{
+ if (!compare(data, stored))
+ return FALSE;
+ if (!pem_equal(freerdp_certificate_data_get_pem(data),
+ freerdp_certificate_data_get_pem(stored)))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL test_get_data(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ BOOL res = 0;
+ rdpCertificateData* stored = freerdp_certificate_store_load_data(
+ store, freerdp_certificate_data_get_host(data), freerdp_certificate_data_get_port(data));
+ if (!stored)
+ return FALSE;
+
+ res = compare(data, stored);
+ freerdp_certificate_data_free(stored);
+ return res;
+}
+
+static BOOL test_get_data_ex(rdpCertificateStore* store, const rdpCertificateData* data)
+{
+ BOOL res = 0;
+ rdpCertificateData* stored = freerdp_certificate_store_load_data(
+ store, freerdp_certificate_data_get_host(data), freerdp_certificate_data_get_port(data));
+ if (!stored)
+ return FALSE;
+
+ res = compare_ex(data, stored);
+ freerdp_certificate_data_free(stored);
+ return res;
+}
+
+static BOOL test_certs_dir(void)
+{
+ BOOL rc = FALSE;
+ rdpSettings* settings = NULL;
+ rdpCertificateStore* store = NULL;
+ rdpCertificateData* data1 = NULL;
+ rdpCertificateData* data2 = NULL;
+ rdpCertificateData* data3 = NULL;
+ rdpCertificateData* data4 = NULL;
+
+ printf("%s\n", __func__);
+ if (!setup_config(&settings))
+ goto fail;
+
+ printf("freerdp_certificate_store_new()\n");
+ store = freerdp_certificate_store_new(settings);
+ if (!store)
+ goto fail;
+
+ {
+ printf("freerdp_certificate_data_new()\n");
+ data1 = freerdp_certificate_data_new_from_pem("somehost", 1234, pem1, strlen(pem1));
+ data2 = freerdp_certificate_data_new_from_pem("otherhost", 4321, pem2, strlen(pem2));
+ data3 = freerdp_certificate_data_new_from_pem("otherhost4", 444, pem3, strlen(pem3));
+ data4 = freerdp_certificate_data_new_from_pem("otherhost", 4321, pem4, strlen(pem4));
+ if (!data1 || !data2 || !data3 || !data4)
+ goto fail;
+
+ /* Find non existing in empty store */
+ printf("freerdp_certificate_store_load_data on empty store\n");
+ if (test_get_data(store, data1))
+ goto fail;
+ if (test_get_data_ex(store, data1))
+ goto fail;
+ if (test_get_data(store, data2))
+ goto fail;
+ if (test_get_data_ex(store, data2))
+ goto fail;
+ if (test_get_data(store, data3))
+ goto fail;
+ if (test_get_data_ex(store, data3))
+ goto fail;
+
+ /* Add certificates */
+ printf("freerdp_certificate_store_save_data\n");
+ if (!freerdp_certificate_store_save_data(store, data1))
+ goto fail;
+ if (!freerdp_certificate_store_save_data(store, data2))
+ goto fail;
+
+ /* Find non existing in non empty store */
+ printf("freerdp_certificate_store_load_data on filled store, non existing value\n");
+ if (test_get_data(store, data3))
+ goto fail;
+ if (test_get_data_ex(store, data3))
+ goto fail;
+
+ /* Add remaining certs */
+ printf("freerdp_certificate_store_save_data\n");
+ if (!freerdp_certificate_store_save_data(store, data3))
+ goto fail;
+
+ /* Check existing can all be found */
+ printf("freerdp_certificate_store_load_data on filled store, existing value\n");
+ if (!test_get_data(store, data1))
+ goto fail;
+ if (!test_get_data_ex(store, data1))
+ goto fail;
+ if (!test_get_data(store, data2))
+ goto fail;
+ if (!test_get_data_ex(store, data2))
+ goto fail;
+ if (!test_get_data(store, data3))
+ goto fail;
+ if (!test_get_data_ex(store, data3))
+ goto fail;
+
+ /* Modify existing entry */
+ printf("freerdp_certificate_store_save_data modify data\n");
+ if (!freerdp_certificate_store_save_data(store, data4))
+ goto fail;
+
+ /* Check new data is in store */
+ printf("freerdp_certificate_store_load_data check modified data can be loaded\n");
+ if (!test_get_data(store, data4))
+ goto fail;
+ if (!test_get_data_ex(store, data4))
+ goto fail;
+
+ /* Check old data is no longer valid */
+ printf("freerdp_certificate_store_load_data check original data no longer there\n");
+ if (test_get_data(store, data2))
+ goto fail;
+ if (test_get_data_ex(store, data2))
+ goto fail;
+
+ /* Delete a cert */
+ printf("freerdp_certificate_store_remove_data\n");
+ if (!freerdp_certificate_store_remove_data(store, data3))
+ goto fail;
+ /* Delete non existing, should succeed */
+ printf("freerdp_certificate_store_remove_data missing value\n");
+ if (!freerdp_certificate_store_remove_data(store, data3))
+ goto fail;
+
+ printf("freerdp_certificate_store_load_data on filled store, existing value\n");
+ if (!test_get_data(store, data1))
+ goto fail;
+ if (!test_get_data_ex(store, data1))
+ goto fail;
+ if (!test_get_data(store, data4))
+ goto fail;
+ if (!test_get_data_ex(store, data4))
+ goto fail;
+
+ printf("freerdp_certificate_store_load_data on filled store, removed value\n");
+ if (test_get_data(store, data3))
+ goto fail;
+ if (test_get_data_ex(store, data3))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ printf("freerdp_certificate_data_free %d\n", rc);
+ freerdp_certificate_data_free(data1);
+ freerdp_certificate_data_free(data2);
+ freerdp_certificate_data_free(data3);
+ freerdp_certificate_data_free(data4);
+ freerdp_certificate_store_free(store);
+ freerdp_settings_free(settings);
+ return rc;
+}
+
+int TestKnownHosts(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_certs_dir())
+ return -1;
+ return 0;
+}
diff --git a/libfreerdp/crypto/test/Test_x509_cert_info.pem b/libfreerdp/crypto/test/Test_x509_cert_info.pem
new file mode 100644
index 0000000..5ac1187
--- /dev/null
+++ b/libfreerdp/crypto/test/Test_x509_cert_info.pem
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIHNzCCBR+gAwIBAgICEAAwDQYJKoZIhvcNAQEFBQAwcDEqMCgGA1UEAwwhQURN
+SU5JU1RSQVRJT04gQ0VOVFJBTEUgREVTIFRFU1RTMQswCQYDVQQGEwJGUjEcMBoG
+A1UECgwTTUlOSVNURVJFIERFUyBURVNUUzEXMBUGA1UECwwOMDAwMiAxMTAwMTQw
+MTYwHhcNMTgwNTE4MDkyNTU1WhcNMTkwNTEzMDkyNTU1WjCBvzEkMCIGA1UEAwwb
+VEVTVEpFQU4gVEVTVE1BUlRJTiA5OTk5OTk5MQswCQYDVQQGEwJGUjEcMBoGA1UE
+CgwTTUlOSVNURVJFIERFUyBURVNUUzEXMBUGA1UECwwOMDAwMiAxMTAwMTQwMTYx
+EjAQBgNVBAsMCVBFUlNPTk5FUzEXMBUGCgmSJomT8ixkAQEMBzk5OTk5OTkxETAP
+BgNVBCoMCFRFU1RKRUFOMRMwEQYDVQQEDApURVNUTUFSVElOMIICIjANBgkqhkiG
+9w0BAQEFAAOCAg8AMIICCgKCAgEA3yc22RDYc+Vc6F26/LONvaYkdTVDiCgbh9Ik
+6pLF5izNpfdQ/YZU25h/UPECdchYX31UEErVOYudOBOHtU4fNjTO0oK5Va/DoFln
+LnfwNpAlBZfogG+yy8fK4yLxG+raoSKDR/5P3hmTqKJqw1WpkwcVE2EDqkP1clMZ
+L5cvJj6gLJa2q0JCdoKe7NntZkgpIk5ZHUZm2JYC30xL7XHfvvb/i0OZLpPOIekT
+DCzxr9HTjbqe+BRZix2UiGpXzjIlDm6EEQNebZqf5kKgcbkxIDWcVraE0kO3TqJI
+P4FBUeuxLqGwQ0AMKrZ+j8U7KAoM9WUoIFcmm8nYGo4hT6ugNIQ9nwQSgyH3yGH1
+PU2k12Ovv2Ft8C/IFuusXxTOJprcFxtjE7qYZ44tmvlozlDOBOJYjLiURAh3r5LL
+TadgArZ3XVMyWlwlTEy9qX59izY9Zz27kd5H11DOz5ezopHAWwP6sgCvWeNDyx8Q
+I3jY8TYzJHahN2bknP2fqwwdGqFCrHItJx2DhDe2ruTk6vvbnwGgYqGzv+RtdNbW
+CL4IMEQQKG9AM40WCz9pu32/vOaQ+hrYyCQMCtli0DSauB+K2IFPsAcz5OAaITJv
+LenMt8mUP9NWHWfr5WYm0tuUCCU4dUT38MqkkdQv7oly1LHkvUdMU+Nk/Ki0Q83U
+9gMvaPcCAwEAAaOCAYkwggGFMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg
+MDIGA1UdJQQrMCkGCCsGAQUFBwMCBggrBgEFBQcDBAYKKwYBBAGCNxQCAgYHKwYB
+BQIDBDAdBgNVHQ4EFgQUXs4RKN+vUVsZjEW/J6qo6EZTLZUwHwYDVR0jBBgwFoAU
+fUXj4k7OA3d8KylcprhMptiOL10wgbIGA1UdEQSBqjCBp6A9BgYrBgEFAgKgMzAx
+oBYbFGtwbi50ZXN0LmV4YW1wbGUuY29toRcwFRsTdGVzdGplYW4udGVzdG1hcnRp
+bqBABgorBgEEAYI3FAIDoDIMMHRlc3RqZWFuLnRlc3RtYXJ0aW4uOTk5OTk5OUB1
+cG4udGVzdC5leGFtcGxlLmNvbYEkdGVzdGplYW4udGVzdG1hcnRpbkB0ZXN0LmV4
+YW1wbGUuY29tMDEGA1UdEgQqMCiCEnJvb3RjYS5leGFtcGxlLmNvbYISaW50ZWNh
+LmV4YW1wbGUuY29tMAkGA1UdIAQCMAAwDQYJKoZIhvcNAQEFBQADggIBAKRDovf+
+CCnV2mtXnzH5JlduOjPWJmGB5a8HLPvakfAm4wQ0YyAViE1tar0V9lhG6nCogWWa
+28D+eM5vLPjVE8ebq5UjIv76x6gWoJkQ3HtfVJvn9UfXwax6IqT7hb1fAHBqu0rj
+uSnSxf1wIzPMp9Lb5x3jBu9ryNMiLUzeY1slBvosOXKlmprPhGWfPYYNCZo2bGJI
+1w5alGDgTBcWKl7icJjAIuCpyRTnKCsaN3kyDU7C5aUhsm9AriPiNErzRI+l5+eu
+Ywg3MZ7Yfjd3rXb6JleT0ZnCh/nFtVLIccWaI4phCrYTGz6odNIqrZ6X23Pt6Rx3
+ZbQjtj4ipMdvbvJbS90aFMrTyfqhVLOxHy+setDcmPOixUgXlx8ZjFI9vgFUeJbo
+OKrkLw4ITUduO+9MplBX7Kt/iCS/CbTfPlHMv03Xb6rbjqHxTJZCCu5QMNHiBeHV
+l8FK5R6gv+9FuCl8uPHwGh/jelQp51cVORlQWeKpqWdwTi0Q3VeVeQAG5RR34xgT
+cQa8h9AqkxYajhxKUmbUlaoYGd8TwUQLrS2jZxp/9geyApVQLAQ27CyAK5HyHSCA
+uqCKsM0gFQyCL4IbXQyFMWgjXZYaorHFjVuMhYEkgWui/9sv+7sMAV5JzROeAw3l
+4+D7yhywwuRzH2SzoavzGpWGMUveVsdLMRk9
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/Test_x509_utils.c b/libfreerdp/crypto/test/Test_x509_utils.c
new file mode 100644
index 0000000..7d90e38
--- /dev/null
+++ b/libfreerdp/crypto/test/Test_x509_utils.c
@@ -0,0 +1,241 @@
+#include <winpr/file.h>
+#include <winpr/string.h>
+#include "../x509_utils.h"
+
+typedef char* (*get_field_pr)(const X509*);
+typedef struct
+{
+ enum
+ {
+ DISABLED,
+ ENABLED,
+ } status;
+ const char* field_description;
+ get_field_pr get_field;
+ const char* expected_result;
+} certificate_test_t;
+
+static char* x509_utils_subject_common_name_wo_length(const X509* xcert)
+{
+ size_t length = 0;
+ return x509_utils_get_common_name(xcert, &length);
+}
+
+static char* certificate_path(const char* filename)
+{
+ /*
+ Assume the .pem file is in the same directory as this source file.
+ Assume that __FILE__ will be a valid path to this file, even from the current working directory
+ where the tests are run. (ie. no chdir occurs between compilation and test running, or __FILE__
+ is an absolute path).
+ */
+ static const char dirsep = '/';
+#ifdef TEST_SOURCE_DIR
+ const char* file = TEST_SOURCE_DIR;
+ const size_t flen = strlen(file) + sizeof(dirsep) + strlen(filename) + sizeof(char);
+ char* result = calloc(1, flen);
+ if (!result)
+ return NULL;
+ _snprintf(result, flen, "%s%c%s", file, dirsep, filename);
+ return result;
+#else
+ const char* file = __FILE__;
+ const char* last_dirsep = strrchr(file, dirsep);
+
+ if (last_dirsep)
+ {
+ const size_t filenameLen = strlen(filename);
+ const size_t dirsepLen = last_dirsep - file + 1;
+ char* result = malloc(dirsepLen + filenameLen + 1);
+ if (!result)
+ return NULL;
+ strncpy(result, file, dirsepLen);
+ strncpy(result + dirsepLen, filename, filenameLen + 1);
+ return result;
+ }
+ else
+ {
+ /* No dirsep => relative path in same directory */
+ return _strdup(filename);
+ }
+#endif
+}
+
+static const certificate_test_t certificate_tests[] = {
+
+ { ENABLED, "Certificate Common Name", x509_utils_subject_common_name_wo_length,
+ "TESTJEAN TESTMARTIN 9999999" },
+
+ { ENABLED, "Certificate subject", x509_utils_get_subject,
+ "CN = TESTJEAN TESTMARTIN 9999999, C = FR, O = MINISTERE DES TESTS, OU = 0002 110014016, OU "
+ "= PERSONNES, UID = 9999999, GN = TESTJEAN, SN = TESTMARTIN" },
+
+ { DISABLED, "Kerberos principal name", 0, "testjean.testmartin@kpn.test.example.com" },
+
+ { ENABLED, "Certificate e-mail", x509_utils_get_email, "testjean.testmartin@test.example.com"
+
+ },
+
+ { ENABLED, "Microsoft's Universal Principal Name", x509_utils_get_upn,
+ "testjean.testmartin.9999999@upn.test.example.com" },
+
+ { ENABLED, "Certificate issuer", x509_utils_get_issuer,
+ "CN = ADMINISTRATION CENTRALE DES TESTS, C = FR, O = MINISTERE DES TESTS, OU = 0002 "
+ "110014016" },
+};
+
+static int TestCertificateFile(const char* certificate_path,
+ const certificate_test_t* ccertificate_tests, size_t count)
+{
+ int success = 0;
+
+ X509* certificate = x509_utils_from_pem(certificate_path, strlen(certificate_path), TRUE);
+
+ if (!certificate)
+ {
+ printf("%s: failure: cannot read certificate file '%s'\n", __func__, certificate_path);
+ success = -1;
+ goto fail;
+ }
+
+ for (size_t i = 0; i < count; i++)
+ {
+ const certificate_test_t* test = &ccertificate_tests[i];
+ char* result = NULL;
+
+ if (test->status == DISABLED)
+ {
+ continue;
+ }
+
+ result = (test->get_field ? test->get_field(certificate) : 0);
+
+ if (result)
+ {
+ printf("%s: crypto got %-40s -> \"%s\"\n", __func__, test->field_description, result);
+
+ if (0 != strcmp(result, test->expected_result))
+ {
+ printf("%s: failure: for %s, actual: \"%s\", expected \"%s\"\n", __func__,
+ test->field_description, result, test->expected_result);
+ success = -1;
+ }
+
+ free(result);
+ }
+ else
+ {
+ printf("%s: failure: cannot get %s\n", __func__, test->field_description);
+ }
+ }
+
+fail:
+ X509_free(certificate);
+ return success;
+}
+
+/* clang-format off */
+/*
+These certificates were generated with the following commands:
+
+openssl ecparam -name P-256 -out /tmp/p256.pem
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha1_cert.pem -sha1
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha256_cert.pem -sha256
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha384_cert.pem -sha384
+openssl req -x509 -newkey ec:/tmp/p256.pem -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out ecdsa_sha512_cert.pem -sha512
+
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha1_cert.pem -sha1
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha256_cert.pem -sha256
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha384_cert.pem -sha384
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pkcs1_sha512_cert.pem -sha512
+
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha1_cert.pem -sha1 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha256_cert.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha384_cert.pem -sha384 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha512_cert.pem -sha512 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest
+openssl req -x509 -newkey rsa:2048 -keyout /dev/null -days 3650 -nodes -subj "/CN=Test" -out rsa_pss_sha256_mgf1_sha384_cert.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest -sigopt rsa_mgf1_md:sha384
+*/
+/* clang-format on */
+
+typedef struct
+{
+ const char* filename;
+ WINPR_MD_TYPE expected;
+} signature_alg_test_t;
+
+static const signature_alg_test_t signature_alg_tests[] = {
+ { "rsa_pkcs1_sha1_cert.pem", WINPR_MD_SHA1 },
+ { "rsa_pkcs1_sha256_cert.pem", WINPR_MD_SHA256 },
+ { "rsa_pkcs1_sha384_cert.pem", WINPR_MD_SHA384 },
+ { "rsa_pkcs1_sha512_cert.pem", WINPR_MD_SHA512 },
+
+ { "ecdsa_sha1_cert.pem", WINPR_MD_SHA1 },
+ { "ecdsa_sha256_cert.pem", WINPR_MD_SHA256 },
+ { "ecdsa_sha384_cert.pem", WINPR_MD_SHA384 },
+ { "ecdsa_sha512_cert.pem", WINPR_MD_SHA512 },
+
+ { "rsa_pss_sha1_cert.pem", WINPR_MD_SHA1 },
+ { "rsa_pss_sha256_cert.pem", WINPR_MD_SHA256 },
+ { "rsa_pss_sha384_cert.pem", WINPR_MD_SHA384 },
+ { "rsa_pss_sha512_cert.pem", WINPR_MD_SHA512 },
+ /*
+ PSS may use different digests for the message hash and MGF-1 hash. In this case, RFC 5929
+ leaves the tls-server-end-point hash unspecified, so it should return WINPR_MD_NONE.
+ */
+ { "rsa_pss_sha256_mgf1_sha384_cert.pem", WINPR_MD_NONE },
+};
+
+static int TestSignatureAlgorithm(const signature_alg_test_t* test)
+{
+ int success = 0;
+ WINPR_MD_TYPE signature_alg = WINPR_MD_NONE;
+ char* path = certificate_path(test->filename);
+ X509* certificate = x509_utils_from_pem(path, strlen(path), TRUE);
+
+ if (!certificate)
+ {
+ printf("%s: failure: cannot read certificate file '%s'\n", __func__, path);
+ success = -1;
+ goto fail;
+ }
+
+ signature_alg = x509_utils_get_signature_alg(certificate);
+ if (signature_alg != test->expected)
+ {
+ const char* signature_alg_string =
+ signature_alg == WINPR_MD_NONE ? "none" : winpr_md_type_to_string(signature_alg);
+ const char* expected_string =
+ test->expected == WINPR_MD_NONE ? "none" : winpr_md_type_to_string(test->expected);
+ printf("%s: failure: for \"%s\", actual: %s, expected %s\n", __func__, test->filename,
+ signature_alg_string, expected_string);
+ success = -1;
+ goto fail;
+ }
+
+fail:
+ X509_free(certificate);
+ free(path);
+ return success;
+}
+
+int Test_x509_utils(int argc, char* argv[])
+{
+ char* cert_path = certificate_path("Test_x509_cert_info.pem");
+ int ret = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ ret = TestCertificateFile(cert_path, certificate_tests, ARRAYSIZE(certificate_tests));
+ free(cert_path);
+ if (ret != 0)
+ return ret;
+
+ for (size_t i = 0; i < ARRAYSIZE(signature_alg_tests); i++)
+ {
+ ret = TestSignatureAlgorithm(&signature_alg_tests[i]);
+ if (ret != 0)
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/libfreerdp/crypto/test/ecdsa_sha1_cert.pem b/libfreerdp/crypto/test/ecdsa_sha1_cert.pem
new file mode 100644
index 0000000..86b98bd
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha1_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBcjCCARigAwIBAgIUP9Q+so71qPtrK898RUJibDMRSYQwCQYHKoZIzj0EATAP
+MQ0wCwYDVQQDDARUZXN0MB4XDTI0MDIwNjAzMDkzNFoXDTM0MDIwMzAzMDkzNFow
+DzENMAsGA1UEAwwEVGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHj9x4Vr
+7pGzpilUY799+mWmOsJwtxFZ3lPNRy+wsfxibRE6e2T0Gk2Ifysl8Vya6Ynwrd2d
+7ztAk+b6HF+1lgqjUzBRMB0GA1UdDgQWBBSX66LoFThh5RCXaeAS+sjGPmLxKTAf
+BgNVHSMEGDAWgBSX66LoFThh5RCXaeAS+sjGPmLxKTAPBgNVHRMBAf8EBTADAQH/
+MAkGByqGSM49BAEDSQAwRgIhAJf3H7PWAZ/5G2SbBKF5jzBVlmWLiVmfanLOvttf
+9DFUAiEA3CnntihpfkAGjUCav7CojYfz8hqe0d6F9ZStfzV4t3g=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/ecdsa_sha256_cert.pem b/libfreerdp/crypto/test/ecdsa_sha256_cert.pem
new file mode 100644
index 0000000..29aaf69
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha256_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBdDCCARmgAwIBAgIUUDFppYHwhd7smJSH6W8QSLttoNEwCgYIKoZIzj0EAwIw
+DzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA5MzRaFw0zNDAyMDMwMzA5MzRa
+MA8xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATfQ2ox
+CF1xh6Dwcsi3BqyUIlKxgY3J2qOSmOzepOMLWhPpiDsneKskpKx4b5JM92mmIyiq
+UMMR7mXlclDHyQtro1MwUTAdBgNVHQ4EFgQUgoV/fxICc75gTRslwgvs/I1YbOUw
+HwYDVR0jBBgwFoAUgoV/fxICc75gTRslwgvs/I1YbOUwDwYDVR0TAQH/BAUwAwEB
+/zAKBggqhkjOPQQDAgNJADBGAiEAyVInWgy3JVEUPDSpjNseJKPie/hINfO6KbrK
+IqGQ0+ACIQDk/oXOIwFZr26TTghYKOn12aOuPCxOqeBu5ObeFMf91Q==
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/ecdsa_sha384_cert.pem b/libfreerdp/crypto/test/ecdsa_sha384_cert.pem
new file mode 100644
index 0000000..85b9d88
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha384_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBczCCARmgAwIBAgIUDT9Rw/q4CH5WmNCTbGbNI964MQwwCgYIKoZIzj0EAwMw
+DzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA5MzRaFw0zNDAyMDMwMzA5MzRa
+MA8xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAR0oA7y
+QeXAp65otDob8Uqmtthdub5T7fbzMr/qnUTxNYoUpXKnde28Cvan4QPCuepHmVPw
+sVx94UX8RIlrXAhdo1MwUTAdBgNVHQ4EFgQUFfghIBL0wxknjd9I8+Wub61VJk4w
+HwYDVR0jBBgwFoAUFfghIBL0wxknjd9I8+Wub61VJk4wDwYDVR0TAQH/BAUwAwEB
+/zAKBggqhkjOPQQDAwNIADBFAiB66sAH30kMoOsMHu5vb1hUl3DPRLb30WbtVSBC
+ZHEDyQIhAK1xgDA005XqcC77o8gzQFFsxIkQrCHTqre2LEGndxLA
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/ecdsa_sha512_cert.pem b/libfreerdp/crypto/test/ecdsa_sha512_cert.pem
new file mode 100644
index 0000000..5115ea8
--- /dev/null
+++ b/libfreerdp/crypto/test/ecdsa_sha512_cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBdDCCARmgAwIBAgIUfb77WvmuJ7r/9aLrhvfHymxssoQwCgYIKoZIzj0EAwQw
+DzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA5MzRaFw0zNDAyMDMwMzA5MzRa
+MA8xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARewOb8
+HMJXad76YWUSaPLMUH8IKpzO0iZkQ2d1SSCylEMdrPJKhi54r7/y6m5LXMejyQzi
+eB2eiNju1yfs1tkoo1MwUTAdBgNVHQ4EFgQUGSveQiJxuzwWX1jIRXdHCzdvj7Ew
+HwYDVR0jBBgwFoAUGSveQiJxuzwWX1jIRXdHCzdvj7EwDwYDVR0TAQH/BAUwAwEB
+/zAKBggqhkjOPQQDBANJADBGAiEAspDRGKH6Nlp+XUxyHKc3IGN5WVIg5ezGHJDR
+9+Q8RAkCIQDVWMxflgAII4D+t2Z8nT4bavUImHD26kbaGtR2/DYPVw==
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/known_hosts/known_hosts b/libfreerdp/crypto/test/known_hosts/known_hosts
new file mode 100644
index 0000000..c165fc5
--- /dev/null
+++ b/libfreerdp/crypto/test/known_hosts/known_hosts
@@ -0,0 +1,2 @@
+someurl ff:11:22:dd
+otherurl aa:bb:cc:dd
diff --git a/libfreerdp/crypto/test/known_hosts/known_hosts.v2 b/libfreerdp/crypto/test/known_hosts/known_hosts.v2
new file mode 100644
index 0000000..7d02106
--- /dev/null
+++ b/libfreerdp/crypto/test/known_hosts/known_hosts.v2
@@ -0,0 +1,2 @@
+someurl 3389 ff:11:22:dd
+otherurl 3389 aa:bb:cc:dd
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem
new file mode 100644
index 0000000..2b56b5f
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha1_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUNveSXnRIoI24dM6PxYjhcXQhsgkwDQYJKoZIhvcNAQEF
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCy23VMgwiNO32ovOxt+7CJCR5Ep1qn1tV5//ONhLEoz+VhEbMTYQNHK1WY
+E9isGrcRUVLsBehIFP02ImgOGv1Yep/P1pY+A/fLpy4NhHoLYxmvdhAKQG3TB5P1
+s7GuXTaK4/Kp8CzVYP7xZu7zI2TWWolkCDYZvkewR5QOuyiAstvZp5IoIx0J9mo2
+rI5DqnSmK+zzaYTMaGyWFLXOQJZi+k+RUB3XUFZSid69thW0rfi7tC0fyUm7fP7H
+72/27aBmW1S/8hUYSfu88kCCmEEu+KGXbmyNPEVMJcM9cZ43TVMGUPodOXuDydm/
+IKnsZaRnGPi8IBzn0y6k/8ZdmJojAgMBAAGjUzBRMB0GA1UdDgQWBBRsqgoFZ83u
+IMZzUjsVI5N73Izq1DAfBgNVHSMEGDAWgBRsqgoFZ83uIMZzUjsVI5N73Izq1DAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCIFhCbHy6U6EYHogXE
+xnoHdtzitHTUU+mymWeQxWxSvZjWX8xdJdbWQyktSCChKrRnQE+P/e5+HOZFn9q5
+jwgj3HwZZwFqt/0nSX7pjEvTOmwEXTo/QBlyHaLSdrxbd85gahkXP1br6vI0yWcT
+kkKZCFiqbsGGqcoErRyZjYfJBgaZ5AMYXEKkKCgRJ59Sln+mW+fpta7dmgmnPIdX
+Jl/ovEHr0X+PgwLby8BxCb5pOyb6CQvUNWfngtzgm76vricszpmDl4HeBAb0IkZ1
+0hlnHEBNgP46/2EhPvD1QehYOmr5HRi2xSPC9gOW8pkbRRFuCDZwoSfav023xim6
+lOS3
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem
new file mode 100644
index 0000000..2233c18
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha256_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUK9L1Ajn7PiMRVvR6YUoATBxgD2wwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC73wUpONU4o0abFykUC08A2deojU/+SGomnN8V51VDVeC2BGUljLabjABm
+iluh/yUJ0zDHp6x1spr6wdDVzRMvjjxC/G2HVzYRn/3zXhZ0q9avPnCe1hgawHZY
+FG4vgysJ1jtPSR97E4MvSg6v6mATCU2ttvceENFwo5FQ6nfBv+rHpepRyOKUtfsa
+gxRCWU0uHwyahNsYzWOrbkEcpoQAowAoHZh9EbjyNNbCX0/C3yew/GX8mNBz1UOV
+X4taJsOW52LKQ2xgSwl31m6VYmNqqfzTA6pr96PpYqsuAT3WYBBnIk+XT+Yhq3Cs
+n96PvHAovUiBwWptvzsICvtAUwCjAgMBAAGjUzBRMB0GA1UdDgQWBBQGUaK7joEk
+yFR6FtsA4UjF+FPrRTAfBgNVHSMEGDAWgBQGUaK7joEkyFR6FtsA4UjF+FPrRTAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAkYpNiExDwuqfFCHpz
+jWFSkL9drTxMuasGfmq634FSe050TGKl8yUZhz215PoEDHcutGAw6JV59DJMI/wo
+Xq4MpJiCLlUu8BOixU41rlyc9XkV6twcnQlkVhYWV07390SP4uKGXRR03yGwjN7C
+RDv/4z4t9iMQhiIYkN0EXKvymjRMhb9GocfTQFSXq6vy0fr4MuwnuOkr6EcU41Hr
+pQ4nNLuj2P7cAaaFo5RaD/eXzsE4CfgoltmTp+Ir3deXG/RKrJ9V/YTx5d6pqssp
+5f7nDKmhiYn6wtF4TlOnujppT1Mr4UZF7lJAfwUX80bYQBbpfczHBKSYQ/Th5Mtx
+ediM
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem
new file mode 100644
index 0000000..7ab6147
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha384_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUHpfVpdCFW4e5gmPMyNN/CZou028wDQYJKoZIhvcNAQEM
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCMlHin4N8tMbfDqIahZlyOhBX392Pcyo4bUM7ipUEdLdz1SSCf7/gh4SgH
+ihCbN/NWiYoobnR7iS6vBgUMQgzdivwOia0jydcifpf2UR2D1KjBnolALqaRYpDB
+zdEOnJ4Nm21sOlfCM/QoiMdPWZlbzisXyGBYHR8E8G5Snfy13cRvfV+M9/epbvwV
+1o4m/wPu2H+Q+XVRHJY1H7jAtjUtLSUwii0jc134c9nHOdqXB9fgcn7etkQKXWPv
+w7Dg/OWXURpABGjflS6w4UhECzlPInmd9fBtLngf7n9Sa4b0rcdWEWcTbWORmEbj
+xoHyWNiRbdzIc0zUb42DFuFgeUcFAgMBAAGjUzBRMB0GA1UdDgQWBBRm5loY68pA
+VVlVVcIL3zTv1sCYWjAfBgNVHSMEGDAWgBRm5loY68pAVVlVVcIL3zTv1sCYWjAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4IBAQAJFNU9dUNpIbbsgyty
+RRd5SVD/4ziOOPoKO6jobBenPxNQNbaOQmENImW0WbgOHWW1kX15afCThMocibKl
+OIVoOf2tcrseAYdfsOzEMwDc1hI5y68vNRV32rJamix1223wPhs1xuoX8bn0t0VB
+RK4kgb9U1mxSKERjcgtp2Pph5DeflCQDeNayBGAA5PCL4ydO61DEKbVo1Cyqtw9n
+yhae7AUR4zzRnZEh1eePd06cXSIYwmTkiJLSF7ILsZnGcqy4bO8yJPrqXT01Iq0S
+RzQ2hgyldKD0kJ3EBmki3mIrnswCzNqyXux1CXIR1FjIA5SnwycqiiPUUPLckinq
+DtQX
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem b/libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem
new file mode 100644
index 0000000..bb798a3
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pkcs1_sha512_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUTAXZy2Zojh8wh4YSdc2vMBiytGIwDQYJKoZIhvcNAQEN
+BQAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDH2av9vKvlzlSB63ylb8OyacC6zkhoiH4ECkVt7DRvBBnHYX39eeXg1KNk
+yHBaLA+9ASXucckqAQ8hWlIW7y9KLYYsK8+ajDUnWkzPj+eOonSc2oTbSBtEP8V4
+3TihNGRgyLSZYj6HxUrFWW6pc0Kz7yAG83YNzR8uWCEZV8wYAdBqZDO1RAOd2tLr
+DFLI+DJwMtgDquT1Uo/xDsx0p9+tSGXm4eXjlZQguhA4tuP4eJP7qvwR+khJNN9X
+u2igfO3zMY26wX4m/kPdljxh5vRc2DVIePfKbTKLyfpsydQUSoKVzlSQG7QJAUmC
+jcqgIGQqYy8f6Nq4fRzgiSQOULmPAgMBAAGjUzBRMB0GA1UdDgQWBBQMYhVmmR0c
+9DJmJZOqHawdWDVFJDAfBgNVHSMEGDAWgBQMYhVmmR0c9DJmJZOqHawdWDVFJDAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQBr2I9uvQ8Ccn5yw1Ig
+7k2Qmg/HOCFGh42Ufz1PAc9dc1CzBzMlFucJ42shES98Spyh4R+ZCJmlZ9PneViA
+s9NW+mHLAFJ2Fuct4XY8RnyDYqTne0in6eQykF33gl79sdYSPuBfAxMfJqUoQo+b
+tF0N0DA00i5z69wcF5LQJLRbeTh7T1qikkay3ODMJYpCDVb7GOhWCt4hOkxOszuL
+SoJ2gFMGoEKbt19IzcMctsSPgpQCNYZirU9x3l/Ptw5zgUQMEngwuutuhDMxb5Ht
+2QHTGt3jwzlOi+lvHFH1AMW3e8/XRZa7jFUa+KCr8vD3yfDimx9J0ESmlUrLxOZG
+ayG7
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha1_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha1_cert.pem
new file mode 100644
index 0000000..dace407
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha1_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUDZuL7xQULUgo1YKeDIJDFdsVCuMwDQYJKoZIhvcNAQEK
+MAAwDzENMAsGA1UEAwwEVGVzdDAeFw0yNDAyMDYwMzA2MzVaFw0zNDAyMDMwMzA2
+MzVaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCxLekSxv8aVwB5p9UImN+jipKNJUb8fOrtOVHjeO5jK9G920vneZsubpq/
+NngzpjT2A+HOkee9oNG92O8U7DGyGXiZugmeFE6kunPQ+GP5za3XfZqwvAu53+Pg
+oyrBGl5ssowRimRDXOH3/x7WSKD2lvQNgLWOS5NTIZethXx29Xj+/nKJ33im8ra/
+YcEB6CANHmo2bDCz8Q54iHjIOro+MiP10oQM2ERzuZREn/+xCFuYYGhXKb3fqy+r
+Ze/tIVqEr7yMVpp76RxFM3I4PadHt8T9Q5Oat9FXWaqG/1fBtBKXt6jElkqnd+vv
+KtRMEqQ4XNeMJi6Q4oyLsmxGB8q7AgMBAAGjUzBRMB0GA1UdDgQWBBQuCV/GISEn
+D18xN3d1KEOo13c/2zAfBgNVHSMEGDAWgBQuCV/GISEnD18xN3d1KEOo13c/2zAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCjAAA4IBAQA+nnvGYJwVP5byl+XB
+1KoFLv4SOS9kDypjv+4Mjo3/houn7DAf1+6ewfmAN7edkElTngpz6rGzJgARULbO
+djKi6AJDDqF9NzWbJZC/UEVqqVY1Znw1v8Hwz31YFaoARLWwuKQrKjLZUHphsMok
+iWdwusebfQ1SK9b8QTW7s8CncC+PbOHH1UnHhM1XWGz49sWZo/KnHCGL0pGROSro
+gT2zEDDk/bpslalcavEGcw2wttaBiYovdpgUHzOIcTxLz/yQGAfaCk/cO8x5mS9y
+w6CZVaH6K1TrmXGWBoHtLQ+JMC7msms3PmBZ4XCc8zAqjhVI4o71Jlbrd/Bag0lm
+X+89
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha256_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha256_cert.pem
new file mode 100644
index 0000000..e9e30e0
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha256_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUChf3gsm9kG9E05UbcYnEzQ/fSfkwQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF
+AKIDAgEgMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM1WhcNMzQwMjAz
+MDMwNjM1WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAtk22hPAmTxSTSs8MUJInf7N6byDo1LlEgw5juF8y1bVxhoF0U4Ug
+u0zqCVtar/JBPmcVofM4IU4+GoIRfI9qyyyD5EQup2e8exUn/Bku+7XTmlCSKfUI
+T4u/zpe/qJCyRwT6pLP4POtoqsmrRf+u7zVvS7VHafPioxLLrOIbB750AYmi7y/n
+h8MreJHrQ901IQV4Ktf2QgaFrjJFgXD307iTGoiRt+UxkmGsOOzFt+N91lM30ad+
+sCgmc1NVVyo6p4RByl7ilxlQGCATOQ9wTVwehgmtFGpU0denhqViNUU8yuPZ3pTD
+w1o9uWTAKdUuMySSUiKmmlE+qnlFfR9uPwIDAQABo1MwUTAdBgNVHQ4EFgQUQPus
+AuQ9cNE9E2+vZXKtKAtfDIUwHwYDVR0jBBgwFoAUQPusAuQ9cNE9E2+vZXKtKAtf
+DIUwDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASADggEBAGpIH7ic
+GwD2vzTGgFhHrh4NkGu9OwI8WA342DKfJq0Cx+BjaFsglhjLVkbqP74XjgQUSqt+
+yp09xgrZdIP8yMJr7mjqQpV7tolB1yrD0h0jtoMe0Pc1+wVwjkMQjsewwSDJvV9O
+RjXqmzoIjUAXmASk5JdZ5oDBK6bg7m8/hJyEJnWdOuiVUJTyPz+Y1/6rpzwH/+xX
+x3sO6OaWMSIc/ovF1Y7kQ4mwgPjLuq1X8Dx7xM9rcJak5cGAwUwQqvOaD3y9ZQzg
+E+kH4mtWw29xRTejbiMSbnbfR8LfZdS2+10ESK66SXChVz3Q7shkR9Gs6kTJiXTu
+FUtRtO0G5aoSHeA=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem
new file mode 100644
index 0000000..2142987
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha256_mgf1_sha384_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUVvhEAv0+q7jndH6xf2RJjaiaIVswQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIF
+AKIDAgEgMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM2WhcNMzQwMjAz
+MDMwNjM2WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsC6mgspqVdpZrhHUz5zoY4Sklg+spYAz8axxEyJf3Tdg427ildSH
+BLi62qZ+gTXLHgUvdkMWEv8NWODHY5DNuTiL65LjV0gN7eIzVbzc0alTdbp+nc7H
+HHZHBedD3CRT4LHqlK9LVTYcQk8qNtnKJ7vxNJHN3vpmr0zxxJf3nle7Ymnx2FiK
+mXDu3vQEDSU6eyOu5dk0IsEWlMOCYu5w8dpbdGaE4az3IaHXsDnmfLfIWUaILQxw
+Mj0UhNg+vnZsanB7CWiLZOIawEpa6dx2Zcasyi8V6lZV1sxYQrskRbAj9uGtDmtd
+kTKeCxtj7Bgj9e6aIN2rh7keH5+pqissxwIDAQABo1MwUTAdBgNVHQ4EFgQUpXlx
+cN8y7RSFQjPjO3Kb1x0GWcYwHwYDVR0jBBgwFoAUpXlxcN8y7RSFQjPjO3Kb1x0G
+WcYwDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCASADggEBAKcp+pEt
+hei70aV9+j+p/cjChDSqQyipIvyqt0wdHiM/5WwGO4LnR/JxWqHHGtlQjgphKVpV
+bjRMBivrbb//LNb0cx5z3o9hx2H6ITwpwrRA9dvfE1C+hOBbxhUYNtk4jpH1DneX
+8l/A9Lm6kmZ02KrJok8VEGthtZqypkRAQFxStY2EuopzWVzdceJYa4AOB26r7F0T
+Z5BzO/Piy39lQtZGyMZ2R71ppEDWXVrSaqxV64aFnh3/2aCgFUzuV2FYr1NZ+zQu
+EBJZIdd0IvhcgHkiUD6qtlYNJ1H7Jf9hSRDM7d7AblhgxVB1eRU+7Ve1o47LfQqz
+iVybceMUSCyMFLk=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha384_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha384_cert.pem
new file mode 100644
index 0000000..52fadb9
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha384_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUI7KZPJ4nCR/qFZWD2bagetWGJRQwQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIF
+AKIDAgEwMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM2WhcNMzQwMjAz
+MDMwNjM2WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA3/WFdrzn9QQjowwZqCmgIJcCWy+8aIjHLlE4OLWyv4rVAdQluFRA
+Af1lsKY+ZD6gvWOKhnSNT7z4WfH03vUNpkqOt1kqtjMsuRsy4Zh2n54LYM2IHkVh
++oZpq8dBESkffAHOLwkl6Be/iZE3t4Z2hdrzhFEt1iGxtfwduIcku/geciLKM0D0
+1UHtVzb0762hx94IX506vxAdvNAK48gTSobBBWQJhHz2a2tvt2ON3eRian41qwxW
+Nsl7OUyss6PmVXYS3JbmZgyzT0nSHQWEVlEcQOI3UxjNqeK2HMyMDYLgIvVD+IER
+1nMT4AUyWDGkRR6bFITS2BmW7JbxQ40ZiwIDAQABo1MwUTAdBgNVHQ4EFgQUn9rP
+CfGH3OFxdNXCFbAQrYlgXd4wHwYDVR0jBBgwFoAUn9rPCfGH3OFxdNXCFbAQrYlg
+Xd4wDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AgUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATADggEBADJ6+6Us
+KXbcpyzapxqQGr0rGkGOPf45Vo2EY0H2A0YY9CHYrU/jq2xijHgnw89I3z9Z3h4q
+kSD51Rr/3V/e5ffcYwRabC6S515anOgxtCE4cgHEqgIdzrwD3EFGm74D+MnJRTy8
+UpE5pZTaHNRno80rz1kRYN2MHO/sHqQCpFoZ8SI3+Nik5m3FSe2Umb3FLTvrOAcm
+biGgj4cF52w1D7XAAo/3bAH9Rt0/FRK46nULoEX54RJHlFN8f3kzBucNNfoHNPBR
+2lMQJM1VzpPkjR5rLOwAYKEfvIvwDVEgDMpD4+P5/FMX/fnSm4kVaiZMwLLrtJlX
+pS9N0mDlr4wk9hw=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/test/rsa_pss_sha512_cert.pem b/libfreerdp/crypto/test/rsa_pss_sha512_cert.pem
new file mode 100644
index 0000000..85a3694
--- /dev/null
+++ b/libfreerdp/crypto/test/rsa_pss_sha512_cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAhugAwIBAgIUCJHdINzBcxL45k+BWvKYcpH8vzQwQQYJKoZIhvcNAQEK
+MDSgDzANBglghkgBZQMEAgMFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgMF
+AKIDAgFAMA8xDTALBgNVBAMMBFRlc3QwHhcNMjQwMjA2MDMwNjM2WhcNMzQwMjAz
+MDMwNjM2WjAPMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA6jzuBGtb+D/0ZWRqZAcZh6bkClCbHdiz1yOPuh4APfyfsFGDSRBt
+ArMH+l2/Mr7TXeAfdDHX//bNBclPzu0mKWtVamh7s3WWoPBX+r10ie94YxErFY+6
+Po5pGDnSabVawdnXd5FqCdFVpmXP12Ii9qKuRD13XAPJML0Cz9z4pOL0ioWvvUqn
+MyWwa8zT8pfK4AJe4XGilQ2uxwJV922XQxv6rY8aOFuwozkNa0/ceN1A2EKIwShB
+P3/3Z+9maz4YMg/VgmJfEY/xekawDZ7MC8CY9G/alE1YqHERvR5BwekrnFHSErM4
+ArSUJnavXm7rdB1OCp4kAKXkLvu2H/8AMQIDAQABo1MwUTAdBgNVHQ4EFgQUrLTl
+C9kRXzElbsDUCc/ePO75qNkwHwYDVR0jBBgwFoAUrLTlC9kRXzElbsDUCc/ePO75
+qNkwDwYDVR0TAQH/BAUwAwEB/zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC
+AwUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAwUAogMCAUADggEBAGMyu2fZ
+NpvIJQxXPSfhgf8idvGwu7YFdZ/Ct0/HIHJ1h+j2WFwubr/Rcwqu+u6Nq09oMq+H
+5EWDtOona78WIQ/RrIs6ltVJBDpirIGjra0IKpYGqYHUEj00u1OZkiQzmMLRT80W
+jEe38fATXbpmLhXA8bqlOHuMot2OTWzKEtST4knAAYUCFPIS94mODR4faeqDBwIB
+JpYBj2sRwZDU4QbERvLTQMD27kE2ynF4duI4NB6k9w3fJSe60ki5m4avYmiQgj5/
+304UD/AEf+xlMCLh3R8ZGST10zV5M1Wm7czuQ0AKsv45pSltDvWl52OjL3W5CLAJ
+iu1eLWBVbFPTK68=
+-----END CERTIFICATE-----
diff --git a/libfreerdp/crypto/tls.c b/libfreerdp/crypto/tls.c
new file mode 100644
index 0000000..371b81b
--- /dev/null
+++ b/libfreerdp/crypto/tls.c
@@ -0,0 +1,1887 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transport Layer Security
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include "../core/settings.h"
+
+#include <winpr/assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/sspi.h>
+#include <winpr/ssl.h>
+
+#include <winpr/stream.h>
+#include <freerdp/utils/ringbuffer.h>
+
+#include <freerdp/crypto/certificate.h>
+#include <freerdp/crypto/certificate_data.h>
+
+#include <freerdp/log.h>
+#include "../crypto/tls.h"
+#include "../core/tcp.h"
+
+#include "opensslcompat.h"
+#include "certificate.h"
+#include "privatekey.h"
+
+#ifdef WINPR_HAVE_POLL_H
+#include <poll.h>
+#endif
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#define TAG FREERDP_TAG("crypto")
+
+/**
+ * Earlier Microsoft iOS RDP clients have sent a null or even double null
+ * terminated hostname in the SNI TLS extension.
+ * If the length indicator does not equal the hostname strlen OpenSSL
+ * will abort (see openssl:ssl/t1_lib.c).
+ * Here is a tcpdump segment of Microsoft Remote Desktop Client Version
+ * 8.1.7 running on an iPhone 4 with iOS 7.1.2 showing the transmitted
+ * SNI hostname TLV blob when connection to server "abcd":
+ * 00 name_type 0x00 (host_name)
+ * 00 06 length_in_bytes 0x0006
+ * 61 62 63 64 00 00 host_name "abcd\0\0"
+ *
+ * Currently the only (runtime) workaround is setting an openssl tls
+ * extension debug callback that sets the SSL context's servername_done
+ * to 1 which effectively disables the parsing of that extension type.
+ *
+ * Nowadays this workaround is not required anymore but still can be
+ * activated by adding the following define:
+ *
+ * #define MICROSOFT_IOS_SNI_BUG
+ */
+
+typedef struct
+{
+ SSL* ssl;
+ CRITICAL_SECTION lock;
+} BIO_RDP_TLS;
+
+static int tls_verify_certificate(rdpTls* tls, const rdpCertificate* cert, const char* hostname,
+ UINT16 port);
+static void tls_print_certificate_name_mismatch_error(const char* hostname, UINT16 port,
+ const char* common_name, char** alt_names,
+ size_t alt_names_count);
+static void tls_print_new_certificate_warn(rdpCertificateStore* store, const char* hostname,
+ UINT16 port, const char* fingerprint);
+static void tls_print_certificate_error(rdpCertificateStore* store, rdpCertificateData* stored_data,
+ const char* hostname, UINT16 port, const char* fingerprint);
+
+static void free_tls_public_key(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+ free(tls->PublicKey);
+ tls->PublicKey = NULL;
+ tls->PublicKeyLength = 0;
+}
+
+static void free_tls_bindings(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ if (tls->Bindings)
+ free(tls->Bindings->Bindings);
+
+ free(tls->Bindings);
+ tls->Bindings = NULL;
+}
+
+static int bio_rdp_tls_write(BIO* bio, const char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ BIO_RDP_TLS* tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!buf || !tls)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_READ | BIO_FLAGS_IO_SPECIAL);
+ EnterCriticalSection(&tls->lock);
+ status = SSL_write(tls->ssl, buf, size);
+ error = SSL_get_error(tls->ssl, status);
+ LeaveCriticalSection(&tls->lock);
+
+ if (status <= 0)
+ {
+ switch (error)
+ {
+ case SSL_ERROR_NONE:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_SSL_X509_LOOKUP);
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_CONNECT);
+ break;
+
+ case SSL_ERROR_SYSCALL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_SSL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+ }
+ }
+
+ return status;
+}
+
+static int bio_rdp_tls_read(BIO* bio, char* buf, int size)
+{
+ int error = 0;
+ int status = 0;
+ BIO_RDP_TLS* tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!buf || !tls)
+ return 0;
+
+ BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_READ | BIO_FLAGS_IO_SPECIAL);
+ EnterCriticalSection(&tls->lock);
+ status = SSL_read(tls->ssl, buf, size);
+ error = SSL_get_error(tls->ssl, status);
+ LeaveCriticalSection(&tls->lock);
+
+ if (status <= 0)
+ {
+ switch (error)
+ {
+ case SSL_ERROR_NONE:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_SSL_X509_LOOKUP);
+ break;
+
+ case SSL_ERROR_WANT_ACCEPT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_ACCEPT);
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, BIO_RR_CONNECT);
+ break;
+
+ case SSL_ERROR_SSL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_SYSCALL:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+ }
+ }
+
+#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
+
+ if (status > 0)
+ {
+ VALGRIND_MAKE_MEM_DEFINED(buf, status);
+ }
+
+#endif
+ return status;
+}
+
+static int bio_rdp_tls_puts(BIO* bio, const char* str)
+{
+ size_t size = 0;
+ int status = 0;
+
+ if (!str)
+ return 0;
+
+ size = strlen(str);
+ ERR_clear_error();
+ status = BIO_write(bio, str, size);
+ return status;
+}
+
+static int bio_rdp_tls_gets(BIO* bio, char* str, int size)
+{
+ return 1;
+}
+
+static long bio_rdp_tls_ctrl(BIO* bio, int cmd, long num, void* ptr)
+{
+ BIO* ssl_rbio = NULL;
+ BIO* ssl_wbio = NULL;
+ BIO* next_bio = NULL;
+ int status = -1;
+ BIO_RDP_TLS* tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!tls)
+ return 0;
+
+ if (!tls->ssl && (cmd != BIO_C_SET_SSL))
+ return 0;
+
+ next_bio = BIO_next(bio);
+ ssl_rbio = tls->ssl ? SSL_get_rbio(tls->ssl) : NULL;
+ ssl_wbio = tls->ssl ? SSL_get_wbio(tls->ssl) : NULL;
+
+ switch (cmd)
+ {
+ case BIO_CTRL_RESET:
+ SSL_shutdown(tls->ssl);
+
+ if (SSL_in_connect_init(tls->ssl))
+ SSL_set_connect_state(tls->ssl);
+ else if (SSL_in_accept_init(tls->ssl))
+ SSL_set_accept_state(tls->ssl);
+
+ SSL_clear(tls->ssl);
+
+ if (next_bio)
+ status = BIO_ctrl(next_bio, cmd, num, ptr);
+ else if (ssl_rbio)
+ status = BIO_ctrl(ssl_rbio, cmd, num, ptr);
+ else
+ status = 1;
+
+ break;
+
+ case BIO_C_GET_FD:
+ status = BIO_ctrl(ssl_rbio, cmd, num, ptr);
+ break;
+
+ case BIO_CTRL_INFO:
+ status = 0;
+ break;
+
+ case BIO_CTRL_SET_CALLBACK:
+ status = 0;
+ break;
+
+ case BIO_CTRL_GET_CALLBACK:
+ *((ULONG_PTR*)ptr) = (ULONG_PTR)SSL_get_info_callback(tls->ssl);
+ status = 1;
+ break;
+
+ case BIO_C_SSL_MODE:
+ if (num)
+ SSL_set_connect_state(tls->ssl);
+ else
+ SSL_set_accept_state(tls->ssl);
+
+ status = 1;
+ break;
+
+ case BIO_CTRL_GET_CLOSE:
+ status = BIO_get_shutdown(bio);
+ break;
+
+ case BIO_CTRL_SET_CLOSE:
+ BIO_set_shutdown(bio, (int)num);
+ status = 1;
+ break;
+
+ case BIO_CTRL_WPENDING:
+ status = BIO_ctrl(ssl_wbio, cmd, num, ptr);
+ break;
+
+ case BIO_CTRL_PENDING:
+ status = SSL_pending(tls->ssl);
+
+ if (status == 0)
+ status = BIO_pending(ssl_rbio);
+
+ break;
+
+ case BIO_CTRL_FLUSH:
+ BIO_clear_retry_flags(bio);
+ status = BIO_ctrl(ssl_wbio, cmd, num, ptr);
+ if (status != 1)
+ WLog_DBG(TAG, "BIO_ctrl returned %d", status);
+ BIO_copy_next_retry(bio);
+ status = 1;
+ break;
+
+ case BIO_CTRL_PUSH:
+ if (next_bio && (next_bio != ssl_rbio))
+ {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ SSL_set_bio(tls->ssl, next_bio, next_bio);
+ CRYPTO_add(&(bio->next_bio->references), 1, CRYPTO_LOCK_BIO);
+#else
+ /*
+ * We are going to pass ownership of next to the SSL object...but
+ * we don't own a reference to pass yet - so up ref
+ */
+ BIO_up_ref(next_bio);
+ SSL_set_bio(tls->ssl, next_bio, next_bio);
+#endif
+ }
+
+ status = 1;
+ break;
+
+ case BIO_CTRL_POP:
+
+ /* Only detach if we are the BIO explicitly being popped */
+ if (bio == ptr)
+ {
+ if (ssl_rbio != ssl_wbio)
+ BIO_free_all(ssl_wbio);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+
+ if (next_bio)
+ CRYPTO_add(&(bio->next_bio->references), -1, CRYPTO_LOCK_BIO);
+
+ tls->ssl->wbio = tls->ssl->rbio = NULL;
+#else
+ /* OpenSSL 1.1: This will also clear the reference we obtained during push */
+ SSL_set_bio(tls->ssl, NULL, NULL);
+#endif
+ }
+
+ status = 1;
+ break;
+
+ case BIO_C_GET_SSL:
+ if (ptr)
+ {
+ *((SSL**)ptr) = tls->ssl;
+ status = 1;
+ }
+
+ break;
+
+ case BIO_C_SET_SSL:
+ BIO_set_shutdown(bio, (int)num);
+
+ if (ptr)
+ {
+ tls->ssl = (SSL*)ptr;
+ ssl_rbio = SSL_get_rbio(tls->ssl);
+ }
+
+ if (ssl_rbio)
+ {
+ if (next_bio)
+ BIO_push(ssl_rbio, next_bio);
+
+ BIO_set_next(bio, ssl_rbio);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ CRYPTO_add(&(ssl_rbio->references), 1, CRYPTO_LOCK_BIO);
+#else
+ BIO_up_ref(ssl_rbio);
+#endif
+ }
+
+ BIO_set_init(bio, 1);
+ status = 1;
+ break;
+
+ case BIO_C_DO_STATE_MACHINE:
+ BIO_clear_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_WRITE | BIO_FLAGS_IO_SPECIAL);
+ BIO_set_retry_reason(bio, 0);
+ status = SSL_do_handshake(tls->ssl);
+
+ if (status <= 0)
+ {
+ switch (SSL_get_error(tls->ssl, status))
+ {
+ case SSL_ERROR_WANT_READ:
+ BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL | BIO_FLAGS_SHOULD_RETRY);
+ BIO_set_retry_reason(bio, BIO_get_retry_reason(next_bio));
+ break;
+
+ default:
+ BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+ break;
+ }
+ }
+
+ break;
+
+ default:
+ status = BIO_ctrl(ssl_rbio, cmd, num, ptr);
+ break;
+ }
+
+ return status;
+}
+
+static int bio_rdp_tls_new(BIO* bio)
+{
+ BIO_RDP_TLS* tls = NULL;
+ BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
+
+ if (!(tls = calloc(1, sizeof(BIO_RDP_TLS))))
+ return 0;
+
+ InitializeCriticalSectionAndSpinCount(&tls->lock, 4000);
+ BIO_set_data(bio, (void*)tls);
+ return 1;
+}
+
+static int bio_rdp_tls_free(BIO* bio)
+{
+ BIO_RDP_TLS* tls = NULL;
+
+ if (!bio)
+ return 0;
+
+ tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!tls)
+ return 0;
+
+ BIO_set_data(bio, NULL);
+ if (BIO_get_shutdown(bio))
+ {
+ if (BIO_get_init(bio) && tls->ssl)
+ {
+ SSL_shutdown(tls->ssl);
+ SSL_free(tls->ssl);
+ }
+
+ BIO_set_init(bio, 0);
+ BIO_set_flags(bio, 0);
+ }
+
+ DeleteCriticalSection(&tls->lock);
+ free(tls);
+
+ return 1;
+}
+
+static long bio_rdp_tls_callback_ctrl(BIO* bio, int cmd, bio_info_cb* fp)
+{
+ long status = 0;
+ BIO_RDP_TLS* tls = NULL;
+
+ if (!bio)
+ return 0;
+
+ tls = (BIO_RDP_TLS*)BIO_get_data(bio);
+
+ if (!tls)
+ return 0;
+
+ switch (cmd)
+ {
+ case BIO_CTRL_SET_CALLBACK:
+ {
+ typedef void (*fkt_t)(const SSL*, int, int);
+ /* Documented since https://www.openssl.org/docs/man1.1.1/man3/BIO_set_callback.html
+ * the argument is not really of type bio_info_cb* and must be cast
+ * to the required type */
+ fkt_t fkt = (fkt_t)(void*)fp;
+ SSL_set_info_callback(tls->ssl, fkt);
+ status = 1;
+ }
+ break;
+
+ default:
+ status = BIO_callback_ctrl(SSL_get_rbio(tls->ssl), cmd, fp);
+ break;
+ }
+
+ return status;
+}
+
+#define BIO_TYPE_RDP_TLS 68
+
+static BIO_METHOD* BIO_s_rdp_tls(void)
+{
+ static BIO_METHOD* bio_methods = NULL;
+
+ if (bio_methods == NULL)
+ {
+ if (!(bio_methods = BIO_meth_new(BIO_TYPE_RDP_TLS, "RdpTls")))
+ return NULL;
+
+ BIO_meth_set_write(bio_methods, bio_rdp_tls_write);
+ BIO_meth_set_read(bio_methods, bio_rdp_tls_read);
+ BIO_meth_set_puts(bio_methods, bio_rdp_tls_puts);
+ BIO_meth_set_gets(bio_methods, bio_rdp_tls_gets);
+ BIO_meth_set_ctrl(bio_methods, bio_rdp_tls_ctrl);
+ BIO_meth_set_create(bio_methods, bio_rdp_tls_new);
+ BIO_meth_set_destroy(bio_methods, bio_rdp_tls_free);
+ BIO_meth_set_callback_ctrl(bio_methods, bio_rdp_tls_callback_ctrl);
+ }
+
+ return bio_methods;
+}
+
+static BIO* BIO_new_rdp_tls(SSL_CTX* ctx, int client)
+{
+ BIO* bio = NULL;
+ SSL* ssl = NULL;
+ bio = BIO_new(BIO_s_rdp_tls());
+
+ if (!bio)
+ return NULL;
+
+ ssl = SSL_new(ctx);
+
+ if (!ssl)
+ {
+ BIO_free_all(bio);
+ return NULL;
+ }
+
+ if (client)
+ SSL_set_connect_state(ssl);
+ else
+ SSL_set_accept_state(ssl);
+
+ BIO_set_ssl(bio, ssl, BIO_CLOSE);
+ return bio;
+}
+
+static rdpCertificate* tls_get_certificate(rdpTls* tls, BOOL peer)
+{
+ X509* remote_cert = NULL;
+
+ if (peer)
+ remote_cert = SSL_get_peer_certificate(tls->ssl);
+ else
+ remote_cert = X509_dup(SSL_get_certificate(tls->ssl));
+
+ if (!remote_cert)
+ {
+ WLog_ERR(TAG, "failed to get the server TLS certificate");
+ return NULL;
+ }
+
+ /* Get the peer's chain. If it does not exist, we're setting NULL (clean data either way) */
+ STACK_OF(X509)* chain = SSL_get_peer_cert_chain(tls->ssl);
+ rdpCertificate* cert = freerdp_certificate_new_from_x509(remote_cert, chain);
+ X509_free(remote_cert);
+
+ return cert;
+}
+
+static const char* tls_get_server_name(rdpTls* tls)
+{
+ return tls->serverName ? tls->serverName : tls->hostname;
+}
+
+#define TLS_SERVER_END_POINT "tls-server-end-point:"
+
+static SecPkgContext_Bindings* tls_get_channel_bindings(const rdpCertificate* cert)
+{
+ size_t CertificateHashLength = 0;
+ BYTE* ChannelBindingToken = NULL;
+ UINT32 ChannelBindingTokenLength = 0;
+ SEC_CHANNEL_BINDINGS* ChannelBindings = NULL;
+ SecPkgContext_Bindings* ContextBindings = NULL;
+ const size_t PrefixLength = strnlen(TLS_SERVER_END_POINT, ARRAYSIZE(TLS_SERVER_END_POINT));
+
+ WINPR_ASSERT(cert);
+
+ /* See https://www.rfc-editor.org/rfc/rfc5929 for details about hashes */
+ WINPR_MD_TYPE alg = freerdp_certificate_get_signature_alg(cert);
+ const char* hash = NULL;
+ switch (alg)
+ {
+
+ case WINPR_MD_MD5:
+ case WINPR_MD_SHA1:
+ hash = winpr_md_type_to_string(WINPR_MD_SHA256);
+ break;
+ default:
+ hash = winpr_md_type_to_string(alg);
+ break;
+ }
+ if (!hash)
+ return NULL;
+
+ char* CertificateHash = freerdp_certificate_get_hash(cert, hash, &CertificateHashLength);
+ if (!CertificateHash)
+ return NULL;
+
+ ChannelBindingTokenLength = PrefixLength + CertificateHashLength;
+ ContextBindings = (SecPkgContext_Bindings*)calloc(1, sizeof(SecPkgContext_Bindings));
+
+ if (!ContextBindings)
+ goto out_free;
+
+ ContextBindings->BindingsLength = sizeof(SEC_CHANNEL_BINDINGS) + ChannelBindingTokenLength;
+ ChannelBindings = (SEC_CHANNEL_BINDINGS*)calloc(1, ContextBindings->BindingsLength);
+
+ if (!ChannelBindings)
+ goto out_free;
+
+ ContextBindings->Bindings = ChannelBindings;
+ ChannelBindings->cbApplicationDataLength = ChannelBindingTokenLength;
+ ChannelBindings->dwApplicationDataOffset = sizeof(SEC_CHANNEL_BINDINGS);
+ ChannelBindingToken = &((BYTE*)ChannelBindings)[ChannelBindings->dwApplicationDataOffset];
+ memcpy(ChannelBindingToken, TLS_SERVER_END_POINT, PrefixLength);
+ memcpy(ChannelBindingToken + PrefixLength, CertificateHash, CertificateHashLength);
+ free(CertificateHash);
+ return ContextBindings;
+out_free:
+ free(CertificateHash);
+ free(ContextBindings);
+ return NULL;
+}
+
+static INIT_ONCE secrets_file_idx_once = INIT_ONCE_STATIC_INIT;
+static int secrets_file_idx = -1;
+
+static BOOL CALLBACK secrets_file_init_cb(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ secrets_file_idx = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+ return (secrets_file_idx != -1);
+}
+
+static void SSLCTX_keylog_cb(const SSL* ssl, const char* line)
+{
+ char* dfile = NULL;
+
+ if (secrets_file_idx == -1)
+ return;
+
+ dfile = SSL_get_ex_data(ssl, secrets_file_idx);
+ if (dfile)
+ {
+ FILE* f = winpr_fopen(dfile, "a+");
+ if (f)
+ {
+ fwrite(line, strlen(line), 1, f);
+ fwrite("\n", 1, 1, f);
+ fclose(f);
+ }
+ }
+}
+
+static void tls_reset(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ if (tls->ctx)
+ {
+ SSL_CTX_free(tls->ctx);
+ tls->ctx = NULL;
+ }
+
+ /* tls->underlying is a stacked BIO under tls->bio.
+ * BIO_free_all will free recursivly. */
+ if (tls->bio)
+ BIO_free_all(tls->bio);
+ else if (tls->underlying)
+ BIO_free_all(tls->underlying);
+ tls->bio = NULL;
+ tls->underlying = NULL;
+
+ free_tls_public_key(tls);
+ free_tls_bindings(tls);
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x010000000L
+static BOOL tls_prepare(rdpTls* tls, BIO* underlying, const SSL_METHOD* method, int options,
+ BOOL clientMode)
+#else
+static BOOL tls_prepare(rdpTls* tls, BIO* underlying, SSL_METHOD* method, int options,
+ BOOL clientMode)
+#endif
+{
+ WINPR_ASSERT(tls);
+
+ rdpSettings* settings = tls->settings;
+ WINPR_ASSERT(settings);
+
+ tls_reset(tls);
+ tls->ctx = SSL_CTX_new(method);
+
+ tls->underlying = underlying;
+
+ if (!tls->ctx)
+ {
+ WLog_ERR(TAG, "SSL_CTX_new failed");
+ return FALSE;
+ }
+
+ SSL_CTX_set_mode(tls->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);
+ SSL_CTX_set_options(tls->ctx, options);
+ SSL_CTX_set_read_ahead(tls->ctx, 1);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ UINT16 version = freerdp_settings_get_uint16(settings, FreeRDP_TLSMinVersion);
+ if (!SSL_CTX_set_min_proto_version(tls->ctx, version))
+ {
+ WLog_ERR(TAG, "SSL_CTX_set_min_proto_version %s failed", version);
+ return FALSE;
+ }
+ version = freerdp_settings_get_uint16(settings, FreeRDP_TLSMaxVersion);
+ if (!SSL_CTX_set_max_proto_version(tls->ctx, version))
+ {
+ WLog_ERR(TAG, "SSL_CTX_set_max_proto_version %s failed", version);
+ return FALSE;
+ }
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+ SSL_CTX_set_security_level(tls->ctx, settings->TlsSecLevel);
+#endif
+
+ if (settings->AllowedTlsCiphers)
+ {
+ if (!SSL_CTX_set_cipher_list(tls->ctx, settings->AllowedTlsCiphers))
+ {
+ WLog_ERR(TAG, "SSL_CTX_set_cipher_list %s failed", settings->AllowedTlsCiphers);
+ return FALSE;
+ }
+ }
+
+ tls->bio = BIO_new_rdp_tls(tls->ctx, clientMode);
+
+ if (BIO_get_ssl(tls->bio, &tls->ssl) < 0)
+ {
+ WLog_ERR(TAG, "unable to retrieve the SSL of the connection");
+ return FALSE;
+ }
+
+ if (settings->TlsSecretsFile)
+ {
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+ InitOnceExecuteOnce(&secrets_file_idx_once, secrets_file_init_cb, NULL, NULL);
+
+ if (secrets_file_idx != -1)
+ {
+ SSL_set_ex_data(tls->ssl, secrets_file_idx, settings->TlsSecretsFile);
+ SSL_CTX_set_keylog_callback(tls->ctx, SSLCTX_keylog_cb);
+ }
+#else
+ WLog_WARN(TAG, "Key-Logging not available - requires OpenSSL 1.1.1 or higher");
+#endif
+ }
+
+ BIO_push(tls->bio, underlying);
+ return TRUE;
+}
+
+static void adjustSslOptions(int* options)
+{
+ WINPR_ASSERT(options);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ *options |= SSL_OP_NO_SSLv2;
+ *options |= SSL_OP_NO_SSLv3;
+#endif
+}
+
+const SSL_METHOD* freerdp_tls_get_ssl_method(BOOL isDtls, BOOL isClient)
+{
+ if (isClient)
+ {
+ if (isDtls)
+ return DTLS_client_method();
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ return SSLv23_client_method();
+#else
+ return TLS_client_method();
+#endif
+ }
+
+ if (isDtls)
+ return DTLS_server_method();
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ return SSLv23_server_method();
+#else
+ return TLS_server_method();
+#endif
+}
+
+TlsHandshakeResult freerdp_tls_connect_ex(rdpTls* tls, BIO* underlying, const SSL_METHOD* methods)
+{
+ WINPR_ASSERT(tls);
+
+ int options = 0;
+ /**
+ * SSL_OP_NO_COMPRESSION:
+ *
+ * The Microsoft RDP server does not advertise support
+ * for TLS compression, but alternative servers may support it.
+ * This was observed between early versions of the FreeRDP server
+ * and the FreeRDP client, and caused major performance issues,
+ * which is why we're disabling it.
+ */
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+ /**
+ * SSL_OP_TLS_BLOCK_PADDING_BUG:
+ *
+ * The Microsoft RDP server does *not* support TLS padding.
+ * It absolutely needs to be disabled otherwise it won't work.
+ */
+ options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
+ /**
+ * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
+ *
+ * Just like TLS padding, the Microsoft RDP server does not
+ * support empty fragments. This needs to be disabled.
+ */
+ options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+
+ tls->isClientMode = TRUE;
+ adjustSslOptions(&options);
+
+ if (!tls_prepare(tls, underlying, methods, options, TRUE))
+ return 0;
+
+#if !defined(OPENSSL_NO_TLSEXT) && !defined(LIBRESSL_VERSION_NUMBER)
+ SSL_set_tlsext_host_name(tls->ssl, tls_get_server_name(tls));
+#endif
+
+ return freerdp_tls_handshake(tls);
+}
+
+TlsHandshakeResult freerdp_tls_handshake(rdpTls* tls)
+{
+ TlsHandshakeResult ret = TLS_HANDSHAKE_ERROR;
+
+ WINPR_ASSERT(tls);
+ int status = BIO_do_handshake(tls->bio);
+ if (status != 1)
+ {
+ if (!BIO_should_retry(tls->bio))
+ return TLS_HANDSHAKE_ERROR;
+
+ return TLS_HANDSHAKE_CONTINUE;
+ }
+
+ int verify_status = 0;
+ rdpCertificate* cert = tls_get_certificate(tls, tls->isClientMode);
+
+ if (!cert)
+ {
+ WLog_ERR(TAG, "tls_get_certificate failed to return the server certificate.");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ do
+ {
+ free_tls_bindings(tls);
+ tls->Bindings = tls_get_channel_bindings(cert);
+ if (!tls->Bindings)
+ {
+ WLog_ERR(TAG, "unable to retrieve bindings");
+ break;
+ }
+
+ free_tls_public_key(tls);
+ if (!freerdp_certificate_get_public_key(cert, &tls->PublicKey, &tls->PublicKeyLength))
+ {
+ WLog_ERR(TAG,
+ "freerdp_certificate_get_public_key failed to return the server public key.");
+ break;
+ }
+
+ /* server-side NLA needs public keys (keys from us, the server) but no certificate verify */
+ ret = TLS_HANDSHAKE_SUCCESS;
+
+ if (tls->isClientMode)
+ {
+ verify_status = tls_verify_certificate(tls, cert, tls_get_server_name(tls), tls->port);
+
+ if (verify_status < 1)
+ {
+ WLog_ERR(TAG, "certificate not trusted, aborting.");
+ freerdp_tls_send_alert(tls);
+ ret = TLS_HANDSHAKE_VERIFY_ERROR;
+ }
+ }
+ } while (0);
+
+ freerdp_certificate_free(cert);
+ return ret;
+}
+
+static int pollAndHandshake(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ do
+ {
+ HANDLE event = NULL;
+ DWORD status = 0;
+ if (BIO_get_event(tls->bio, &event) < 0)
+ {
+ WLog_ERR(TAG, "unable to retrieve BIO associated event");
+ return -1;
+ }
+
+ if (!event)
+ {
+ WLog_ERR(TAG, "unable to retrieve BIO event");
+ return -1;
+ }
+
+ status = WaitForSingleObjectEx(event, 50, TRUE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_TIMEOUT:
+ continue;
+ default:
+ WLog_ERR(TAG, "error during WaitForSingleObject(): 0x%08" PRIX32 "", status);
+ return -1;
+ }
+
+ TlsHandshakeResult result = freerdp_tls_handshake(tls);
+ switch (result)
+ {
+ case TLS_HANDSHAKE_CONTINUE:
+ break;
+ case TLS_HANDSHAKE_SUCCESS:
+ return 1;
+ case TLS_HANDSHAKE_ERROR:
+ case TLS_HANDSHAKE_VERIFY_ERROR:
+ default:
+ return -1;
+ }
+ } while (TRUE);
+}
+
+int freerdp_tls_connect(rdpTls* tls, BIO* underlying)
+{
+ const SSL_METHOD* method = freerdp_tls_get_ssl_method(FALSE, TRUE);
+
+ WINPR_ASSERT(tls);
+ TlsHandshakeResult result = freerdp_tls_connect_ex(tls, underlying, method);
+ switch (result)
+ {
+ case TLS_HANDSHAKE_SUCCESS:
+ return 1;
+ case TLS_HANDSHAKE_CONTINUE:
+ break;
+ case TLS_HANDSHAKE_ERROR:
+ case TLS_HANDSHAKE_VERIFY_ERROR:
+ return -1;
+ }
+
+ return pollAndHandshake(tls);
+}
+
+#if defined(MICROSOFT_IOS_SNI_BUG) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+static void tls_openssl_tlsext_debug_callback(SSL* s, int client_server, int type,
+ unsigned char* data, int len, void* arg)
+{
+ if (type == TLSEXT_TYPE_server_name)
+ {
+ WLog_DBG(TAG, "Client uses SNI (extension disabled)");
+ s->servername_done = 2;
+ }
+}
+#endif
+
+BOOL freerdp_tls_accept(rdpTls* tls, BIO* underlying, rdpSettings* settings)
+{
+ WINPR_ASSERT(tls);
+ TlsHandshakeResult res =
+ freerdp_tls_accept_ex(tls, underlying, settings, freerdp_tls_get_ssl_method(FALSE, FALSE));
+ switch (res)
+ {
+ case TLS_HANDSHAKE_SUCCESS:
+ return TRUE;
+ case TLS_HANDSHAKE_CONTINUE:
+ break;
+ case TLS_HANDSHAKE_ERROR:
+ case TLS_HANDSHAKE_VERIFY_ERROR:
+ default:
+ return FALSE;
+ }
+
+ return pollAndHandshake(tls) > 0;
+}
+
+TlsHandshakeResult freerdp_tls_accept_ex(rdpTls* tls, BIO* underlying, rdpSettings* settings,
+ const SSL_METHOD* methods)
+{
+ WINPR_ASSERT(tls);
+
+ long options = 0;
+ int status = 0;
+
+ /**
+ * SSL_OP_NO_SSLv2:
+ *
+ * We only want SSLv3 and TLSv1, so disable SSLv2.
+ * SSLv3 is used by, eg. Microsoft RDC for Mac OS X.
+ */
+ options |= SSL_OP_NO_SSLv2;
+ /**
+ * SSL_OP_NO_COMPRESSION:
+ *
+ * The Microsoft RDP server does not advertise support
+ * for TLS compression, but alternative servers may support it.
+ * This was observed between early versions of the FreeRDP server
+ * and the FreeRDP client, and caused major performance issues,
+ * which is why we're disabling it.
+ */
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+ /**
+ * SSL_OP_TLS_BLOCK_PADDING_BUG:
+ *
+ * The Microsoft RDP server does *not* support TLS padding.
+ * It absolutely needs to be disabled otherwise it won't work.
+ */
+ options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
+ /**
+ * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
+ *
+ * Just like TLS padding, the Microsoft RDP server does not
+ * support empty fragments. This needs to be disabled.
+ */
+ options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+
+ /**
+ * SSL_OP_NO_RENEGOTIATION
+ *
+ * Disable SSL client site renegotiation.
+ */
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && (OPENSSL_VERSION_NUMBER < 0x30000000L) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+ options |= SSL_OP_NO_RENEGOTIATION;
+#endif
+
+ if (!tls_prepare(tls, underlying, methods, options, FALSE))
+ return TLS_HANDSHAKE_ERROR;
+
+ const rdpPrivateKey* key = freerdp_settings_get_pointer(settings, FreeRDP_RdpServerRsaKey);
+ if (!key)
+ {
+ WLog_ERR(TAG, "invalid private key");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ EVP_PKEY* privkey = freerdp_key_get_evp_pkey(key);
+ if (!privkey)
+ {
+ WLog_ERR(TAG, "invalid private key");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ status = SSL_use_PrivateKey(tls->ssl, privkey);
+ /* The local reference to the private key will anyway go out of
+ * scope; so the reference count should be decremented weither
+ * SSL_use_PrivateKey succeeds or fails.
+ */
+ EVP_PKEY_free(privkey);
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "SSL_CTX_use_PrivateKey_file failed");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ rdpCertificate* cert =
+ freerdp_settings_get_pointer_writable(settings, FreeRDP_RdpServerCertificate);
+ if (!cert)
+ {
+ WLog_ERR(TAG, "invalid certificate");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+ status = SSL_use_certificate(tls->ssl, freerdp_certificate_get_x509(cert));
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "SSL_use_certificate_file failed");
+ return TLS_HANDSHAKE_ERROR;
+ }
+
+#if defined(MICROSOFT_IOS_SNI_BUG) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+ SSL_set_tlsext_debug_callback(tls->ssl, tls_openssl_tlsext_debug_callback);
+#endif
+
+ return freerdp_tls_handshake(tls);
+}
+
+BOOL freerdp_tls_send_alert(rdpTls* tls)
+{
+ WINPR_ASSERT(tls);
+
+ if (!tls)
+ return FALSE;
+
+ if (!tls->ssl)
+ return TRUE;
+
+ /**
+ * FIXME: The following code does not work on OpenSSL > 1.1.0 because the
+ * SSL struct is opaqe now
+ */
+#if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10100000L)) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER <= 0x2080300fL))
+
+ if (tls->alertDescription != TLS_ALERT_DESCRIPTION_CLOSE_NOTIFY)
+ {
+ /**
+ * OpenSSL doesn't really expose an API for sending a TLS alert manually.
+ *
+ * The following code disables the sending of the default "close notify"
+ * and then proceeds to force sending a custom TLS alert before shutting down.
+ *
+ * Manually sending a TLS alert is necessary in certain cases,
+ * like when server-side NLA results in an authentication failure.
+ */
+ SSL_SESSION* ssl_session = SSL_get_session(tls->ssl);
+ SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(tls->ssl);
+ SSL_set_quiet_shutdown(tls->ssl, 1);
+
+ if ((tls->alertLevel == TLS_ALERT_LEVEL_FATAL) && (ssl_session))
+ SSL_CTX_remove_session(ssl_ctx, ssl_session);
+
+ tls->ssl->s3->alert_dispatch = 1;
+ tls->ssl->s3->send_alert[0] = tls->alertLevel;
+ tls->ssl->s3->send_alert[1] = tls->alertDescription;
+
+ if (tls->ssl->s3->wbuf.left == 0)
+ tls->ssl->method->ssl_dispatch_alert(tls->ssl);
+ }
+
+#endif
+ return TRUE;
+}
+
+int freerdp_tls_write_all(rdpTls* tls, const BYTE* data, int length)
+{
+ WINPR_ASSERT(tls);
+ int status = 0;
+ int offset = 0;
+ BIO* bio = tls->bio;
+
+ while (offset < length)
+ {
+ ERR_clear_error();
+ status = BIO_write(bio, &data[offset], length - offset);
+
+ if (status > 0)
+ {
+ offset += status;
+ }
+ else
+ {
+ if (!BIO_should_retry(bio))
+ return -1;
+
+ if (BIO_write_blocked(bio))
+ status = BIO_wait_write(bio, 100);
+ else if (BIO_read_blocked(bio))
+ return -2; /* Abort write, there is data that must be read */
+ else
+ USleep(100);
+
+ if (status < 0)
+ return -1;
+ }
+ }
+
+ return length;
+}
+
+int freerdp_tls_set_alert_code(rdpTls* tls, int level, int description)
+{
+ WINPR_ASSERT(tls);
+ tls->alertLevel = level;
+ tls->alertDescription = description;
+ return 0;
+}
+
+static BOOL tls_match_hostname(const char* pattern, const size_t pattern_length,
+ const char* hostname)
+{
+ if (strlen(hostname) == pattern_length)
+ {
+ if (_strnicmp(hostname, pattern, pattern_length) == 0)
+ return TRUE;
+ }
+
+ if ((pattern_length > 2) && (pattern[0] == '*') && (pattern[1] == '.') &&
+ ((strlen(hostname)) >= pattern_length))
+ {
+ const char* check_hostname = &hostname[strlen(hostname) - pattern_length + 1];
+
+ if (_strnicmp(check_hostname, &pattern[1], pattern_length - 1) == 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL is_redirected(rdpTls* tls)
+{
+ rdpSettings* settings = tls->settings;
+
+ if (LB_NOREDIRECT & settings->RedirectionFlags)
+ return FALSE;
+
+ return settings->RedirectionFlags != 0;
+}
+
+static BOOL is_accepted(rdpTls* tls, const BYTE* pem, size_t length)
+{
+ rdpSettings* settings = tls->settings;
+ char* AccpetedKey = NULL;
+ UINT32 AcceptedKeyLength = 0;
+
+ if (tls->isGatewayTransport)
+ {
+ AccpetedKey = settings->GatewayAcceptedCert;
+ AcceptedKeyLength = settings->GatewayAcceptedCertLength;
+ }
+ else if (is_redirected(tls))
+ {
+ 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 (is_redirected(tls))
+ {
+ free(settings->RedirectionAcceptedCert);
+ settings->RedirectionAcceptedCert = NULL;
+ settings->RedirectionAcceptedCertLength = 0;
+ }
+ else
+ {
+ free(settings->AcceptedCert);
+ settings->AcceptedCert = NULL;
+ settings->AcceptedCertLength = 0;
+ }
+
+ return FALSE;
+}
+
+static BOOL compare_fingerprint(const char* fp, const char* hash, const rdpCertificate* cert,
+ BOOL separator)
+{
+ BOOL equal = 0;
+ char* strhash = NULL;
+
+ WINPR_ASSERT(fp);
+ WINPR_ASSERT(hash);
+ WINPR_ASSERT(cert);
+
+ strhash = freerdp_certificate_get_fingerprint_by_hash_ex(cert, hash, separator);
+ if (!strhash)
+ return FALSE;
+
+ equal = (_stricmp(strhash, fp) == 0);
+ free(strhash);
+ return equal;
+}
+
+static BOOL compare_fingerprint_all(const char* fp, const char* hash, const rdpCertificate* cert)
+{
+ WINPR_ASSERT(fp);
+ WINPR_ASSERT(hash);
+ WINPR_ASSERT(cert);
+ if (compare_fingerprint(fp, hash, cert, FALSE))
+ return TRUE;
+ if (compare_fingerprint(fp, hash, cert, TRUE))
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL is_accepted_fingerprint(const rdpCertificate* cert,
+ const char* CertificateAcceptedFingerprints)
+{
+ WINPR_ASSERT(cert);
+
+ BOOL rc = FALSE;
+ if (CertificateAcceptedFingerprints)
+ {
+ char* context = NULL;
+ char* copy = _strdup(CertificateAcceptedFingerprints);
+ char* cur = strtok_s(copy, ",", &context);
+ while (cur)
+ {
+ char* subcontext = NULL;
+ const char* h = strtok_s(cur, ":", &subcontext);
+ const char* fp = NULL;
+
+ if (!h)
+ goto next;
+
+ fp = h + strlen(h) + 1;
+ if (!fp)
+ goto next;
+
+ if (compare_fingerprint_all(fp, h, cert))
+ {
+ rc = TRUE;
+ break;
+ }
+ next:
+ cur = strtok_s(NULL, ",", &context);
+ }
+ free(copy);
+ }
+
+ return rc;
+}
+
+static BOOL accept_cert(rdpTls* tls, const BYTE* pem, UINT32 length)
+{
+ WINPR_ASSERT(tls);
+ FreeRDP_Settings_Keys_String id = FreeRDP_AcceptedCert;
+ FreeRDP_Settings_Keys_UInt32 lid = FreeRDP_AcceptedCertLength;
+
+ rdpSettings* settings = tls->settings;
+
+ if (tls->isGatewayTransport)
+ {
+ id = FreeRDP_GatewayAcceptedCert;
+ lid = FreeRDP_GatewayAcceptedCertLength;
+ }
+ else if (is_redirected(tls))
+ {
+ id = FreeRDP_RedirectionAcceptedCert;
+ lid = FreeRDP_RedirectionAcceptedCertLength;
+ }
+
+ if (!freerdp_settings_set_string_len(settings, id, (const char*)pem, length))
+ return FALSE;
+
+ return freerdp_settings_set_uint32(settings, lid, length);
+}
+
+static BOOL tls_extract_pem(const rdpCertificate* cert, BYTE** PublicKey, size_t* PublicKeyLength)
+{
+ if (!cert || !PublicKey)
+ return FALSE;
+ *PublicKey = (BYTE*)freerdp_certificate_get_pem(cert, PublicKeyLength);
+ return *PublicKey != NULL;
+}
+
+int tls_verify_certificate(rdpTls* tls, const rdpCertificate* cert, const char* hostname,
+ UINT16 port)
+{
+ int match = 0;
+ size_t length = 0;
+ BOOL certificate_status = 0;
+ char* common_name = NULL;
+ size_t common_name_length = 0;
+ char** dns_names = 0;
+ size_t dns_names_count = 0;
+ size_t* dns_names_lengths = NULL;
+ int verification_status = -1;
+ BOOL hostname_match = FALSE;
+ rdpCertificateData* certificate_data = NULL;
+ BYTE* pemCert = NULL;
+ DWORD flags = VERIFY_CERT_FLAG_NONE;
+ freerdp* instance = NULL;
+
+ WINPR_ASSERT(tls);
+ WINPR_ASSERT(tls->settings);
+
+ instance = (freerdp*)tls->settings->instance;
+ WINPR_ASSERT(instance);
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ return -1;
+
+ if (!tls_extract_pem(cert, &pemCert, &length))
+ goto end;
+
+ /* Check, if we already accepted this key. */
+ if (is_accepted(tls, pemCert, length))
+ {
+ verification_status = 1;
+ goto end;
+ }
+
+ if (is_accepted_fingerprint(cert, tls->settings->CertificateAcceptedFingerprints))
+ {
+ verification_status = 1;
+ goto end;
+ }
+
+ if (tls->isGatewayTransport || is_redirected(tls))
+ flags |= VERIFY_CERT_FLAG_LEGACY;
+
+ if (tls->isGatewayTransport)
+ flags |= VERIFY_CERT_FLAG_GATEWAY;
+
+ if (is_redirected(tls))
+ flags |= VERIFY_CERT_FLAG_REDIRECT;
+
+ /* Certificate management is done by the application */
+ if (tls->settings->ExternalCertificateManagement)
+ {
+ if (instance->VerifyX509Certificate)
+ verification_status =
+ instance->VerifyX509Certificate(instance, pemCert, length, hostname, port, flags);
+ else
+ WLog_ERR(TAG, "No VerifyX509Certificate callback registered!");
+
+ if (verification_status > 0)
+ accept_cert(tls, pemCert, length);
+ else if (verification_status < 0)
+ {
+ WLog_ERR(TAG, "VerifyX509Certificate failed: (length = %" PRIuz ") status: [%d] %s",
+ length, verification_status, pemCert);
+ goto end;
+ }
+ }
+ /* ignore certificate verification if user explicitly required it (discouraged) */
+ else if (tls->settings->IgnoreCertificate)
+ verification_status = 1; /* success! */
+ else if (!tls->isGatewayTransport && (tls->settings->AuthenticationLevel == 0))
+ verification_status = 1; /* success! */
+ else
+ {
+ /* if user explicitly specified a certificate name, use it instead of the hostname */
+ if (!tls->isGatewayTransport && tls->settings->CertificateName)
+ hostname = tls->settings->CertificateName;
+
+ /* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */
+ certificate_status = freerdp_certificate_verify(
+ cert, freerdp_certificate_store_get_certs_path(tls->certificate_store));
+ /* verify certificate name match */
+ certificate_data = freerdp_certificate_data_new(hostname, port, cert);
+ if (!certificate_data)
+ goto end;
+ /* extra common name and alternative names */
+ common_name = freerdp_certificate_get_common_name(cert, &common_name_length);
+ dns_names = freerdp_certificate_get_dns_names(cert, &dns_names_count, &dns_names_lengths);
+
+ /* compare against common name */
+
+ if (common_name)
+ {
+ if (tls_match_hostname(common_name, common_name_length, hostname))
+ hostname_match = TRUE;
+ }
+
+ /* compare against alternative names */
+
+ if (dns_names)
+ {
+ for (size_t index = 0; index < dns_names_count; index++)
+ {
+ if (tls_match_hostname(dns_names[index], dns_names_lengths[index], hostname))
+ {
+ hostname_match = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* if the certificate is valid and the certificate name matches, verification succeeds
+ */
+ if (certificate_status && hostname_match)
+ verification_status = 1; /* success! */
+
+ if (!hostname_match)
+ flags |= VERIFY_CERT_FLAG_MISMATCH;
+
+ /* verification could not succeed with OpenSSL, use known_hosts file and prompt user for
+ * manual verification */
+ if (!certificate_status || !hostname_match)
+ {
+ DWORD accept_certificate = 0;
+ size_t pem_length = 0;
+ char* issuer = freerdp_certificate_get_issuer(cert);
+ char* subject = freerdp_certificate_get_subject(cert);
+ char* pem = freerdp_certificate_get_pem(cert, &pem_length);
+
+ if (!pem)
+ goto end;
+
+ /* search for matching entry in known_hosts file */
+ match =
+ freerdp_certificate_store_contains_data(tls->certificate_store, certificate_data);
+
+ if (match == 1)
+ {
+ /* no entry was found in known_hosts file, prompt user for manual verification
+ */
+ if (!hostname_match)
+ tls_print_certificate_name_mismatch_error(hostname, port, common_name,
+ dns_names, dns_names_count);
+
+ {
+ char* efp = freerdp_certificate_get_fingerprint(cert);
+ tls_print_new_certificate_warn(tls->certificate_store, hostname, port, efp);
+ free(efp);
+ }
+
+ /* Automatically accept certificate on first use */
+ if (tls->settings->AutoAcceptCertificate)
+ {
+ WLog_INFO(TAG, "No certificate stored, automatically accepting.");
+ accept_certificate = 1;
+ }
+ else if (tls->settings->AutoDenyCertificate)
+ {
+ WLog_INFO(TAG, "No certificate stored, automatically denying.");
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyX509Certificate)
+ {
+ int rc = instance->VerifyX509Certificate(instance, pemCert, pem_length,
+ hostname, port, flags);
+
+ if (rc == 1)
+ accept_certificate = 1;
+ else if (rc > 1)
+ accept_certificate = 2;
+ else
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyCertificateEx)
+ {
+ const BOOL use_pem = freerdp_settings_get_bool(
+ tls->settings, FreeRDP_CertificateCallbackPreferPEM);
+ char* fp = NULL;
+ DWORD cflags = flags;
+ if (use_pem)
+ {
+ cflags |= VERIFY_CERT_FLAG_FP_IS_PEM;
+ fp = pem;
+ }
+ else
+ fp = freerdp_certificate_get_fingerprint(cert);
+ accept_certificate = instance->VerifyCertificateEx(
+ instance, hostname, port, common_name, subject, issuer, fp, cflags);
+ if (!use_pem)
+ free(fp);
+ }
+#if defined(WITH_FREERDP_DEPRECATED)
+ else if (instance->VerifyCertificate)
+ {
+ char* fp = freerdp_certificate_get_fingerprint(cert);
+
+ WLog_WARN(TAG, "The VerifyCertificate callback is deprecated, migrate your "
+ "application to VerifyCertificateEx");
+ accept_certificate = instance->VerifyCertificate(instance, common_name, subject,
+ issuer, fp, !hostname_match);
+ free(fp);
+ }
+#endif
+ }
+ else if (match == -1)
+ {
+ rdpCertificateData* stored_data =
+ freerdp_certificate_store_load_data(tls->certificate_store, hostname, port);
+ /* entry was found in known_hosts file, but fingerprint does not match. ask user
+ * to use it */
+ {
+ char* efp = freerdp_certificate_get_fingerprint(cert);
+ tls_print_certificate_error(tls->certificate_store, stored_data, hostname, port,
+ efp);
+ free(efp);
+ }
+
+ if (!stored_data)
+ WLog_WARN(TAG, "Failed to get certificate entry for %s:%" PRIu16 "", hostname,
+ port);
+
+ if (tls->settings->AutoDenyCertificate)
+ {
+ WLog_INFO(TAG, "No certificate stored, automatically denying.");
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyX509Certificate)
+ {
+ const int rc =
+ instance->VerifyX509Certificate(instance, pemCert, pem_length, hostname,
+ port, flags | VERIFY_CERT_FLAG_CHANGED);
+
+ if (rc == 1)
+ accept_certificate = 1;
+ else if (rc > 1)
+ accept_certificate = 2;
+ else
+ accept_certificate = 0;
+ }
+ else if (instance->VerifyChangedCertificateEx)
+ {
+ DWORD cflags = flags | VERIFY_CERT_FLAG_CHANGED;
+ const char* old_subject = freerdp_certificate_data_get_subject(stored_data);
+ const char* old_issuer = freerdp_certificate_data_get_issuer(stored_data);
+ const char* old_fp = freerdp_certificate_data_get_fingerprint(stored_data);
+ const char* old_pem = freerdp_certificate_data_get_pem(stored_data);
+ const BOOL fpIsAllocated =
+ !old_pem || !freerdp_settings_get_bool(
+ tls->settings, FreeRDP_CertificateCallbackPreferPEM);
+ char* fp = NULL;
+ if (!fpIsAllocated)
+ {
+ cflags |= VERIFY_CERT_FLAG_FP_IS_PEM;
+ fp = pem;
+ old_fp = old_pem;
+ }
+ else
+ {
+ fp = freerdp_certificate_get_fingerprint(cert);
+ }
+ accept_certificate = instance->VerifyChangedCertificateEx(
+ instance, hostname, port, common_name, subject, issuer, fp, old_subject,
+ old_issuer, old_fp, cflags);
+ if (fpIsAllocated)
+ free(fp);
+ }
+#if defined(WITH_FREERDP_DEPRECATED)
+ else if (instance->VerifyChangedCertificate)
+ {
+ char* fp = freerdp_certificate_get_fingerprint(cert);
+ const char* old_subject = freerdp_certificate_data_get_subject(stored_data);
+ const char* old_issuer = freerdp_certificate_data_get_issuer(stored_data);
+ const char* old_fingerprint =
+ freerdp_certificate_data_get_fingerprint(stored_data);
+
+ WLog_WARN(TAG, "The VerifyChangedCertificate callback is deprecated, migrate "
+ "your application to VerifyChangedCertificateEx");
+ accept_certificate = instance->VerifyChangedCertificate(
+ instance, common_name, subject, issuer, fp, old_subject, old_issuer,
+ old_fingerprint);
+ free(fp);
+ }
+#endif
+
+ freerdp_certificate_data_free(stored_data);
+ }
+ else if (match == 0)
+ accept_certificate = 2; /* success! */
+
+ /* Save certificate or do a simple accept / reject */
+ switch (accept_certificate)
+ {
+ case 1:
+
+ /* user accepted certificate, add entry in known_hosts file */
+ verification_status = freerdp_certificate_store_save_data(
+ tls->certificate_store, certificate_data)
+ ? 1
+ : -1;
+ break;
+
+ case 2:
+ /* user did accept temporaty, do not add to known hosts file */
+ verification_status = 1;
+ break;
+
+ default:
+ /* user did not accept, abort and do not add entry in known_hosts file */
+ verification_status = -1; /* failure! */
+ break;
+ }
+
+ free(issuer);
+ free(subject);
+ free(pem);
+ }
+
+ if (verification_status > 0)
+ accept_cert(tls, pemCert, length);
+ }
+
+end:
+ freerdp_certificate_data_free(certificate_data);
+ free(common_name);
+ freerdp_certificate_free_dns_names(dns_names_count, dns_names_lengths, dns_names);
+ free(pemCert);
+ return verification_status;
+}
+
+void tls_print_new_certificate_warn(rdpCertificateStore* store, const char* hostname, UINT16 port,
+ const char* fingerprint)
+{
+ char* path = freerdp_certificate_store_get_cert_path(store, hostname, port);
+
+ WLog_ERR(TAG, "The host key for %s:%" PRIu16 " has changed", hostname, port);
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @");
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!");
+ WLog_ERR(TAG, "Someone could be eavesdropping on you right now (man-in-the-middle attack)!");
+ WLog_ERR(TAG, "It is also possible that a host key has just been changed.");
+ WLog_ERR(TAG, "The fingerprint for the host key sent by the remote host is %s", fingerprint);
+ WLog_ERR(TAG, "Please contact your system administrator.");
+ WLog_ERR(TAG, "Add correct host key in %s to get rid of this message.", path);
+ WLog_ERR(TAG, "Host key for %s has changed and you have requested strict checking.", hostname);
+ WLog_ERR(TAG, "Host key verification failed.");
+
+ free(path);
+}
+
+void tls_print_certificate_error(rdpCertificateStore* store, rdpCertificateData* stored_data,
+ const char* hostname, UINT16 port, const char* fingerprint)
+{
+ char* path = freerdp_certificate_store_get_cert_path(store, hostname, port);
+
+ WLog_ERR(TAG, "New host key for %s:%" PRIu16, hostname, port);
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "@ WARNING: NEW HOST IDENTIFICATION! @");
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+
+ WLog_ERR(TAG, "The fingerprint for the host key sent by the remote host is %s", fingerprint);
+ WLog_ERR(TAG, "Please contact your system administrator.");
+ WLog_ERR(TAG, "Add correct host key in %s to get rid of this message.", path);
+
+ free(path);
+}
+
+void tls_print_certificate_name_mismatch_error(const char* hostname, UINT16 port,
+ const char* common_name, char** alt_names,
+ size_t alt_names_count)
+{
+ WINPR_ASSERT(NULL != hostname);
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "@ WARNING: CERTIFICATE NAME MISMATCH! @");
+ WLog_ERR(TAG, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ WLog_ERR(TAG, "The hostname used for this connection (%s:%" PRIu16 ") ", hostname, port);
+ WLog_ERR(TAG, "does not match %s given in the certificate:",
+ alt_names_count < 1 ? "the name" : "any of the names");
+ WLog_ERR(TAG, "Common Name (CN):");
+ WLog_ERR(TAG, "\t%s", common_name ? common_name : "no CN found in certificate");
+
+ if (alt_names_count > 0)
+ {
+ WINPR_ASSERT(NULL != alt_names);
+ WLog_ERR(TAG, "Alternative names:");
+
+ for (size_t index = 0; index < alt_names_count; index++)
+ {
+ WINPR_ASSERT(alt_names[index]);
+ WLog_ERR(TAG, "\t %s", alt_names[index]);
+ }
+ }
+
+ WLog_ERR(TAG, "A valid certificate for the wrong name should NOT be trusted!");
+}
+
+rdpTls* freerdp_tls_new(rdpSettings* settings)
+{
+ rdpTls* tls = NULL;
+ tls = (rdpTls*)calloc(1, sizeof(rdpTls));
+
+ if (!tls)
+ return NULL;
+
+ tls->settings = settings;
+
+ if (!settings->ServerMode)
+ {
+ tls->certificate_store = freerdp_certificate_store_new(settings);
+
+ if (!tls->certificate_store)
+ goto out_free;
+ }
+
+ tls->alertLevel = TLS_ALERT_LEVEL_WARNING;
+ tls->alertDescription = TLS_ALERT_DESCRIPTION_CLOSE_NOTIFY;
+ return tls;
+out_free:
+ free(tls);
+ return NULL;
+}
+
+void freerdp_tls_free(rdpTls* tls)
+{
+ if (!tls)
+ return;
+
+ tls_reset(tls);
+
+ if (tls->certificate_store)
+ {
+ freerdp_certificate_store_free(tls->certificate_store);
+ tls->certificate_store = NULL;
+ }
+
+ free(tls);
+}
diff --git a/libfreerdp/crypto/tls.h b/libfreerdp/crypto/tls.h
new file mode 100644
index 0000000..efc62a1
--- /dev/null
+++ b/libfreerdp/crypto/tls.h
@@ -0,0 +1,131 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Transport Layer Security
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_LIB_CRYPTO_TLS_H
+#define FREERDP_LIB_CRYPTO_TLS_H
+
+#include <winpr/crt.h>
+#include <winpr/sspi.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+#include <freerdp/crypto/certificate_store.h>
+
+#include <winpr/stream.h>
+
+#define TLS_ALERT_LEVEL_WARNING 1
+#define TLS_ALERT_LEVEL_FATAL 2
+
+#define TLS_ALERT_DESCRIPTION_CLOSE_NOTIFY 0
+#define TLS_ALERT_DESCRIPTION_UNEXPECTED_MESSAGE 10
+#define TLS_ALERT_DESCRIPTION_BAD_RECORD_MAC 20
+#define TLS_ALERT_DESCRIPTION_DECRYPTION_FAILED 21
+#define TLS_ALERT_DESCRIPTION_RECORD_OVERFLOW 22
+#define TLS_ALERT_DESCRIPTION_DECOMPRESSION_FAILURE 30
+#define TLS_ALERT_DESCRIPTION_HANSHAKE_FAILURE 40
+#define TLS_ALERT_DESCRIPTION_NO_CERTIFICATE 41
+#define TLS_ALERT_DESCRIPTION_BAD_CERTIFICATE 42
+#define TLS_ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE 43
+#define TLS_ALERT_DESCRIPTION_CERTIFICATE_REVOKED 44
+#define TLS_ALERT_DESCRIPTION_CERTIFICATE_EXPIRED 45
+#define TLS_ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN 46
+#define TLS_ALERT_DESCRIPTION_ILLEGAL_PARAMETER 47
+#define TLS_ALERT_DESCRIPTION_UNKNOWN_CA 48
+#define TLS_ALERT_DESCRIPTION_ACCESS_DENIED 49
+#define TLS_ALERT_DESCRIPTION_DECODE_ERROR 50
+#define TLS_ALERT_DESCRIPTION_DECRYPT_ERROR 51
+#define TLS_ALERT_DESCRIPTION_EXPORT_RESTRICTION 60
+#define TLS_ALERT_DESCRIPTION_PROTOCOL_VERSION 70
+#define TLS_ALERT_DESCRIPTION_INSUFFICIENT_SECURITY 71
+#define TLS_ALERT_DESCRIPTION_INTERNAL_ERROR 80
+#define TLS_ALERT_DESCRIPTION_USER_CANCELED 90
+#define TLS_ALERT_DESCRIPTION_NO_RENEGOTIATION 100
+#define TLS_ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION 110
+
+typedef struct rdp_tls rdpTls;
+
+struct rdp_tls
+{
+ SSL* ssl;
+ BIO* bio;
+ void* tsg;
+ SSL_CTX* ctx;
+ BYTE* PublicKey;
+ DWORD PublicKeyLength;
+ rdpSettings* settings;
+ SecPkgContext_Bindings* Bindings;
+ rdpCertificateStore* certificate_store;
+ BIO* underlying;
+ const char* hostname;
+ const char* serverName;
+ int port;
+ int alertLevel;
+ int alertDescription;
+ BOOL isGatewayTransport;
+ BOOL isClientMode;
+};
+
+/** @brief result of a handshake operation */
+typedef enum
+{
+ TLS_HANDSHAKE_SUCCESS, /*!< handshake was successful */
+ TLS_HANDSHAKE_CONTINUE, /*!< handshake is not completed */
+ TLS_HANDSHAKE_ERROR, /*!< an error (probably IO error) happened */
+ TLS_HANDSHAKE_VERIFY_ERROR /*!< Certificate verification failed (client mode) */
+} TlsHandshakeResult;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL const SSL_METHOD* freerdp_tls_get_ssl_method(BOOL isDtls, BOOL isClient);
+
+ FREERDP_LOCAL int freerdp_tls_connect(rdpTls* tls, BIO* underlying);
+
+ FREERDP_LOCAL TlsHandshakeResult freerdp_tls_connect_ex(rdpTls* tls, BIO* underlying,
+ const SSL_METHOD* methods);
+
+ FREERDP_LOCAL BOOL freerdp_tls_accept(rdpTls* tls, BIO* underlying, rdpSettings* settings);
+
+ FREERDP_LOCAL TlsHandshakeResult freerdp_tls_accept_ex(rdpTls* tls, BIO* underlying,
+ rdpSettings* settings,
+ const SSL_METHOD* methods);
+
+ FREERDP_LOCAL TlsHandshakeResult freerdp_tls_handshake(rdpTls* tls);
+
+ FREERDP_LOCAL BOOL freerdp_tls_send_alert(rdpTls* tls);
+
+ FREERDP_LOCAL int freerdp_tls_write_all(rdpTls* tls, const BYTE* data, int length);
+
+ FREERDP_LOCAL int freerdp_tls_set_alert_code(rdpTls* tls, int level, int description);
+
+ FREERDP_LOCAL void freerdp_tls_free(rdpTls* tls);
+
+ WINPR_ATTR_MALLOC(freerdp_tls_free, 1)
+ FREERDP_LOCAL rdpTls* freerdp_tls_new(rdpSettings* settings);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CRYPTO_TLS_H */
diff --git a/libfreerdp/crypto/x509_utils.c b/libfreerdp/crypto/x509_utils.c
new file mode 100644
index 0000000..dc388e8
--- /dev/null
+++ b/libfreerdp/crypto/x509_utils.c
@@ -0,0 +1,986 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <openssl/objects.h>
+#include <openssl/x509v3.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+
+#include "x509_utils.h"
+
+#define TAG FREERDP_TAG("crypto")
+
+BYTE* x509_utils_get_hash(const X509* xcert, const char* hash, size_t* length)
+{
+ UINT32 fp_len = EVP_MAX_MD_SIZE;
+ BYTE* fp = NULL;
+ const EVP_MD* md = EVP_get_digestbyname(hash);
+ if (!md)
+ {
+ WLog_ERR(TAG, "System does not support %s hash!", hash);
+ return NULL;
+ }
+ if (!xcert || !length)
+ {
+ WLog_ERR(TAG, "Invalid arugments: xcert=%p, length=%p", xcert, length);
+ return NULL;
+ }
+
+ fp = calloc(fp_len + 1, sizeof(BYTE));
+ if (!fp)
+ {
+ WLog_ERR(TAG, "could not allocate %" PRIuz " bytes", fp_len);
+ return NULL;
+ }
+
+ if (X509_digest(xcert, md, fp, &fp_len) != 1)
+ {
+ free(fp);
+ WLog_ERR(TAG, "certificate does not have a %s hash!", hash);
+ return NULL;
+ }
+
+ *length = fp_len;
+ return fp;
+}
+
+static char* crypto_print_name(const X509_NAME* name)
+{
+ char* buffer = NULL;
+ BIO* outBIO = BIO_new(BIO_s_mem());
+
+ if (X509_NAME_print_ex(outBIO, name, 0, XN_FLAG_ONELINE) > 0)
+ {
+ UINT64 size = BIO_number_written(outBIO);
+ if (size > INT_MAX)
+ return NULL;
+ buffer = calloc(1, (size_t)size + 1);
+
+ if (!buffer)
+ return NULL;
+
+ ERR_clear_error();
+ BIO_read(outBIO, buffer, (int)size);
+ }
+
+ BIO_free_all(outBIO);
+ return buffer;
+}
+
+char* x509_utils_get_subject(const X509* xcert)
+{
+ char* subject = NULL;
+ if (!xcert)
+ {
+ WLog_ERR(TAG, "Invalid certificate %p", xcert);
+ return NULL;
+ }
+ subject = crypto_print_name(X509_get_subject_name(xcert));
+ if (!subject)
+ WLog_WARN(TAG, "certificate does not have a subject!");
+ return subject;
+}
+
+/* GENERAL_NAME type labels */
+
+static const char* general_name_type_labels[] = { "OTHERNAME", "EMAIL ", "DNS ",
+ "X400 ", "DIRNAME ", "EDIPARTY ",
+ "URI ", "IPADD ", "RID " };
+
+static const char* general_name_type_label(int general_name_type)
+{
+ if ((0 <= general_name_type) &&
+ ((size_t)general_name_type < ARRAYSIZE(general_name_type_labels)))
+ {
+ return general_name_type_labels[general_name_type];
+ }
+ else
+ {
+ static char buffer[80];
+ sprintf(buffer, "Unknown general name type (%d)", general_name_type);
+ return buffer;
+ }
+}
+
+/*
+
+map_subject_alt_name(x509, general_name_type, mapper, data)
+
+Call the function mapper with subjectAltNames found in the x509
+certificate and data. if generate_name_type is GEN_ALL, the the
+mapper is called for all the names, else it's called only for names
+of the given type.
+
+
+We implement two extractors:
+
+ - a string extractor that can be used to get the subjectAltNames of
+ the following types: GEN_URI, GEN_DNS, GEN_EMAIL
+
+ - a ASN1_OBJECT filter/extractor that can be used to get the
+ subjectAltNames of OTHERNAME type.
+
+ Note: usually, it's a string, but some type of otherNames can be
+ associated with different classes of objects. eg. a KPN may be a
+ sequence of realm and principal name, instead of a single string
+ object.
+
+Not implemented yet: extractors for the types: GEN_X400, GEN_DIRNAME,
+GEN_EDIPARTY, GEN_RID, GEN_IPADD (the later can contain nul-bytes).
+
+
+mapper(name, data, index, count)
+
+The mapper is passed:
+ - the GENERAL_NAME selected,
+ - the data,
+ - the index of the general name in the subjectAltNames,
+ - the total number of names in the subjectAltNames.
+
+The last parameter let's the mapper allocate arrays to collect objects.
+Note: if names are filtered, not all the indices from 0 to count-1 are
+passed to mapper, only the indices selected.
+
+When the mapper returns 0, map_subject_alt_name stops the iteration immediately.
+
+*/
+
+#define GEN_ALL (-1)
+
+typedef int (*general_name_mapper_pr)(GENERAL_NAME* name, void* data, int index, int count);
+
+static void map_subject_alt_name(const X509* x509, int general_name_type,
+ general_name_mapper_pr mapper, void* data)
+{
+ int num = 0;
+ STACK_OF(GENERAL_NAME)* gens = NULL;
+ gens = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL);
+
+ if (!gens)
+ {
+ return;
+ }
+
+ num = sk_GENERAL_NAME_num(gens);
+
+ for (int i = 0; (i < num); i++)
+ {
+ GENERAL_NAME* name = sk_GENERAL_NAME_value(gens, i);
+
+ if (name)
+ {
+ if ((general_name_type == GEN_ALL) || (general_name_type == name->type))
+ {
+ if (!mapper(name, data, i, num))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+}
+
+/*
+extract_string -- string extractor
+
+- the strings array is allocated lazily, when we first have to store a
+ string.
+
+- allocated contains the size of the strings array, or -1 if
+ allocation failed.
+
+- count contains the actual count of strings in the strings array.
+
+- maximum limits the number of strings we can store in the strings
+ array: beyond, the extractor returns 0 to short-cut the search.
+
+extract_string stores in the string list OPENSSL strings,
+that must be freed with OPENSSL_free.
+
+*/
+
+typedef struct string_list
+{
+ char** strings;
+ int allocated;
+ int count;
+ int maximum;
+} string_list;
+
+static void string_list_initialize(string_list* list)
+{
+ list->strings = 0;
+ list->allocated = 0;
+ list->count = 0;
+ list->maximum = INT_MAX;
+}
+
+static void string_list_allocate(string_list* list, int allocate_count)
+{
+ if (!list->strings && list->allocated == 0)
+ {
+ list->strings = calloc((size_t)allocate_count, sizeof(char*));
+ list->allocated = list->strings ? allocate_count : -1;
+ list->count = 0;
+ }
+}
+
+static void string_list_free(string_list* list)
+{
+ /* Note: we don't free the contents of the strings array: this */
+ /* is handled by the caller, either by returning this */
+ /* content, or freeing it itself. */
+ free(list->strings);
+}
+
+static int extract_string(GENERAL_NAME* name, void* data, int index, int count)
+{
+ string_list* list = data;
+ unsigned char* cstring = 0;
+ ASN1_STRING* str = NULL;
+
+ switch (name->type)
+ {
+ case GEN_URI:
+ str = name->d.uniformResourceIdentifier;
+ break;
+
+ case GEN_DNS:
+ str = name->d.dNSName;
+ break;
+
+ case GEN_EMAIL:
+ str = name->d.rfc822Name;
+ break;
+
+ default:
+ return 1;
+ }
+
+ if ((ASN1_STRING_to_UTF8(&cstring, str)) < 0)
+ {
+ WLog_ERR(TAG, "ASN1_STRING_to_UTF8() failed for %s: %s",
+ general_name_type_label(name->type), ERR_error_string(ERR_get_error(), NULL));
+ return 1;
+ }
+
+ string_list_allocate(list, count);
+
+ if (list->allocated <= 0)
+ {
+ OPENSSL_free(cstring);
+ return 0;
+ }
+
+ list->strings[list->count] = (char*)cstring;
+ list->count++;
+
+ if (list->count >= list->maximum)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+extract_othername_object -- object extractor.
+
+- the objects array is allocated lazily, when we first have to store a
+ string.
+
+- allocated contains the size of the objects array, or -1 if
+ allocation failed.
+
+- count contains the actual count of objects in the objects array.
+
+- maximum limits the number of objects we can store in the objects
+ array: beyond, the extractor returns 0 to short-cut the search.
+
+extract_othername_objects stores in the objects array ASN1_TYPE *
+pointers directly obtained from the GENERAL_NAME.
+*/
+
+typedef struct object_list
+{
+ ASN1_OBJECT* type_id;
+ char** strings;
+ int allocated;
+ int count;
+ int maximum;
+} object_list;
+
+static void object_list_initialize(object_list* list)
+{
+ list->type_id = 0;
+ list->strings = 0;
+ list->allocated = 0;
+ list->count = 0;
+ list->maximum = INT_MAX;
+}
+
+static void object_list_allocate(object_list* list, int allocate_count)
+{
+ if (!list->strings && list->allocated == 0)
+ {
+ list->strings = calloc(allocate_count, sizeof(list->strings[0]));
+ list->allocated = list->strings ? allocate_count : -1;
+ list->count = 0;
+ }
+}
+
+static char* object_string(ASN1_TYPE* object)
+{
+ char* result = NULL;
+ unsigned char* utf8String = NULL;
+ int length = 0;
+ /* TODO: check that object.type is a string type. */
+ length = ASN1_STRING_to_UTF8(&utf8String, object->value.asn1_string);
+
+ if (length < 0)
+ {
+ return 0;
+ }
+
+ result = (char*)_strdup((char*)utf8String);
+ OPENSSL_free(utf8String);
+ return result;
+}
+
+static void object_list_free(object_list* list)
+{
+ free(list->strings);
+}
+
+static int extract_othername_object_as_string(GENERAL_NAME* name, void* data, int index, int count)
+{
+ object_list* list = data;
+
+ if (name->type != GEN_OTHERNAME)
+ {
+ return 1;
+ }
+
+ if (0 != OBJ_cmp(name->d.otherName->type_id, list->type_id))
+ {
+ return 1;
+ }
+
+ object_list_allocate(list, count);
+
+ if (list->allocated <= 0)
+ {
+ return 0;
+ }
+
+ list->strings[list->count] = object_string(name->d.otherName->value);
+
+ if (list->strings[list->count])
+ {
+ list->count++;
+ }
+
+ if (list->count >= list->maximum)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+char* x509_utils_get_email(const X509* x509)
+{
+ char* result = 0;
+ string_list list;
+ string_list_initialize(&list);
+ list.maximum = 1;
+ map_subject_alt_name(x509, GEN_EMAIL, extract_string, &list);
+
+ if (list.count == 0)
+ {
+ string_list_free(&list);
+ return 0;
+ }
+
+ result = _strdup(list.strings[0]);
+ OPENSSL_free(list.strings[0]);
+ string_list_free(&list);
+ return result;
+}
+
+char* x509_utils_get_upn(const X509* x509)
+{
+ char* result = 0;
+ object_list list;
+ object_list_initialize(&list);
+ list.type_id = OBJ_nid2obj(NID_ms_upn);
+ list.maximum = 1;
+ map_subject_alt_name(x509, GEN_OTHERNAME, extract_othername_object_as_string, &list);
+
+ if (list.count == 0)
+ {
+ object_list_free(&list);
+ return 0;
+ }
+
+ result = list.strings[0];
+ object_list_free(&list);
+ return result;
+}
+
+void x509_utils_dns_names_free(size_t count, size_t* lengths, char** dns_names)
+{
+ free(lengths);
+
+ if (dns_names)
+ {
+ for (size_t i = 0; i < count; i++)
+ {
+ if (dns_names[i])
+ {
+ OPENSSL_free(dns_names[i]);
+ }
+ }
+
+ free(dns_names);
+ }
+}
+
+char** x509_utils_get_dns_names(const X509* x509, size_t* count, size_t** lengths)
+{
+ char** result = 0;
+ string_list list;
+ string_list_initialize(&list);
+ map_subject_alt_name(x509, GEN_DNS, extract_string, &list);
+ (*count) = list.count;
+
+ if (list.count == 0)
+ {
+ string_list_free(&list);
+ return NULL;
+ }
+
+ /* lengths are not useful, since we converted the
+ strings to utf-8, there cannot be nul-bytes in them. */
+ result = calloc(list.count, sizeof(*result));
+ (*lengths) = calloc(list.count, sizeof(**lengths));
+
+ if (!result || !(*lengths))
+ {
+ string_list_free(&list);
+ free(result);
+ free(*lengths);
+ (*lengths) = 0;
+ (*count) = 0;
+ return NULL;
+ }
+
+ for (int i = 0; i < list.count; i++)
+ {
+ result[i] = list.strings[i];
+ (*lengths)[i] = strlen(result[i]);
+ }
+
+ string_list_free(&list);
+ return result;
+}
+
+char* x509_utils_get_issuer(const X509* xcert)
+{
+ char* issuer = NULL;
+ if (!xcert)
+ {
+ WLog_ERR(TAG, "Invalid certificate %p", xcert);
+ return NULL;
+ }
+ issuer = crypto_print_name(X509_get_issuer_name(xcert));
+ if (!issuer)
+ WLog_WARN(TAG, "certificate does not have an issuer!");
+ return issuer;
+}
+
+BOOL x509_utils_check_eku(const X509* xcert, int nid)
+{
+ BOOL ret = FALSE;
+ STACK_OF(ASN1_OBJECT)* oid_stack = NULL;
+ ASN1_OBJECT* oid = NULL;
+
+ if (!xcert)
+ return FALSE;
+
+ oid = OBJ_nid2obj(nid);
+ if (!oid)
+ return FALSE;
+
+ oid_stack = X509_get_ext_d2i(xcert, NID_ext_key_usage, NULL, NULL);
+ if (!oid_stack)
+ return FALSE;
+
+ if (sk_ASN1_OBJECT_find(oid_stack, oid) >= 0)
+ ret = TRUE;
+
+ sk_ASN1_OBJECT_pop_free(oid_stack, ASN1_OBJECT_free);
+ return ret;
+}
+
+void x509_utils_print_info(const X509* xcert)
+{
+ char* fp = NULL;
+ char* issuer = NULL;
+ char* subject = NULL;
+ subject = x509_utils_get_subject(xcert);
+ issuer = x509_utils_get_issuer(xcert);
+ fp = (char*)x509_utils_get_hash(xcert, "sha256", NULL);
+
+ if (!fp)
+ {
+ WLog_ERR(TAG, "error computing fingerprint");
+ goto out_free_issuer;
+ }
+
+ WLog_INFO(TAG, "Certificate details:");
+ WLog_INFO(TAG, "\tSubject: %s", subject);
+ WLog_INFO(TAG, "\tIssuer: %s", issuer);
+ WLog_INFO(TAG, "\tThumbprint: %s", fp);
+ WLog_INFO(TAG,
+ "The above X.509 certificate could not be verified, possibly because you do not have "
+ "the CA certificate in your certificate store, or the certificate has expired. "
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.");
+ free(fp);
+out_free_issuer:
+ free(issuer);
+ free(subject);
+}
+
+static BYTE* x509_utils_get_pem(const X509* xcert, const STACK_OF(X509) * chain, size_t* plength)
+{
+ BIO* bio = NULL;
+ int status = 0;
+ int count = 0;
+ size_t offset = 0;
+ size_t length = 0;
+ BOOL rc = FALSE;
+ BYTE* pemCert = NULL;
+
+ if (!xcert || !plength)
+ return NULL;
+
+ /**
+ * 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 NULL;
+ }
+
+ status = PEM_write_bio_X509(bio, (X509*)xcert);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+
+ if (chain)
+ {
+ count = sk_X509_num(chain);
+ for (int x = 0; x < count; x++)
+ {
+ X509* c = sk_X509_value(chain, x);
+ status = PEM_write_bio_X509(bio, c);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
+ goto fail;
+ }
+ }
+ }
+
+ offset = 0;
+ length = 2048;
+ pemCert = (BYTE*)malloc(length + 1);
+
+ if (!pemCert)
+ {
+ WLog_ERR(TAG, "error allocating pemCert");
+ goto fail;
+ }
+
+ ERR_clear_error();
+ status = BIO_read(bio, pemCert, length);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "failed to read certificate");
+ goto fail;
+ }
+
+ offset += (size_t)status;
+
+ while (offset >= length)
+ {
+ int new_len = 0;
+ BYTE* new_cert = NULL;
+ new_len = length * 2;
+ new_cert = (BYTE*)realloc(pemCert, new_len + 1);
+
+ if (!new_cert)
+ goto fail;
+
+ length = new_len;
+ pemCert = new_cert;
+ ERR_clear_error();
+ status = BIO_read(bio, &pemCert[offset], length - offset);
+
+ if (status < 0)
+ break;
+
+ offset += status;
+ }
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "failed to read certificate");
+ goto fail;
+ }
+
+ length = offset;
+ pemCert[length] = '\0';
+ *plength = length;
+ rc = TRUE;
+fail:
+
+ if (!rc)
+ {
+ WLog_ERR(TAG, "Failed to extract PEM from certificate %p", xcert);
+ free(pemCert);
+ pemCert = NULL;
+ }
+
+ BIO_free_all(bio);
+ return pemCert;
+}
+
+X509* x509_utils_from_pem(const char* data, size_t len, BOOL fromFile)
+{
+ X509* x509 = NULL;
+ BIO* bio = NULL;
+ if (fromFile)
+ bio = BIO_new_file(data, "rb");
+ else
+ bio = BIO_new_mem_buf(data, len);
+
+ if (!bio)
+ {
+ WLog_ERR(TAG, "BIO_new failed for certificate");
+ return NULL;
+ }
+
+ x509 = PEM_read_bio_X509(bio, NULL, NULL, 0);
+ BIO_free_all(bio);
+ if (!x509)
+ WLog_ERR(TAG, "PEM_read_bio_X509 returned NULL [input length %" PRIuz "]", len);
+
+ return x509;
+}
+
+static WINPR_MD_TYPE hash_nid_to_winpr(int hash_nid)
+{
+ switch (hash_nid)
+ {
+ case NID_md2:
+ return WINPR_MD_MD2;
+ case NID_md4:
+ return WINPR_MD_MD4;
+ case NID_md5:
+ return WINPR_MD_MD5;
+ case NID_sha1:
+ return WINPR_MD_SHA1;
+ case NID_sha224:
+ return WINPR_MD_SHA224;
+ case NID_sha256:
+ return WINPR_MD_SHA256;
+ case NID_sha384:
+ return WINPR_MD_SHA384;
+ case NID_sha512:
+ return WINPR_MD_SHA512;
+ case NID_ripemd160:
+ return WINPR_MD_RIPEMD160;
+#if (OPENSSL_VERSION_NUMBER >= 0x1010101fL) && !defined(LIBRESSL_VERSION_NUMBER)
+ case NID_sha3_224:
+ return WINPR_MD_SHA3_224;
+ case NID_sha3_256:
+ return WINPR_MD_SHA3_256;
+ case NID_sha3_384:
+ return WINPR_MD_SHA3_384;
+ case NID_sha3_512:
+ return WINPR_MD_SHA3_512;
+ case NID_shake128:
+ return WINPR_MD_SHAKE128;
+ case NID_shake256:
+ return WINPR_MD_SHAKE256;
+#endif
+ case NID_undef:
+ default:
+ return WINPR_MD_NONE;
+ }
+}
+
+static WINPR_MD_TYPE get_rsa_pss_digest(const X509_ALGOR* alg)
+{
+ WINPR_MD_TYPE ret = WINPR_MD_NONE;
+ WINPR_MD_TYPE message_digest = WINPR_MD_NONE;
+ WINPR_MD_TYPE mgf1_digest = WINPR_MD_NONE;
+ int param_type = 0;
+ const void* param_value = NULL;
+ const ASN1_STRING* sequence = NULL;
+ const unsigned char* inp = NULL;
+ RSA_PSS_PARAMS* params = NULL;
+ X509_ALGOR* mgf1_digest_alg = NULL;
+
+ /* The RSA-PSS digest is encoded in a complex structure, defined in
+ https://www.rfc-editor.org/rfc/rfc4055.html. */
+ X509_ALGOR_get0(NULL, &param_type, &param_value, alg);
+
+ /* param_type and param_value the parameter in ASN1_TYPE form, but split into two parameters. A
+ SEQUENCE is has type V_ASN1_SEQUENCE, and the value is an ASN1_STRING with the encoded
+ structure. */
+ if (param_type != V_ASN1_SEQUENCE)
+ goto end;
+ sequence = param_value;
+
+ /* Decode the structure. */
+ inp = ASN1_STRING_get0_data(sequence);
+ params = d2i_RSA_PSS_PARAMS(NULL, &inp, ASN1_STRING_length(sequence));
+ if (params == NULL)
+ goto end;
+
+ /* RSA-PSS uses two hash algorithms, a message digest and also an MGF function which is, itself,
+ parameterized by a hash function. Both fields default to SHA-1, so we must also check for the
+ value being NULL. */
+ message_digest = WINPR_MD_SHA1;
+ if (params->hashAlgorithm != NULL)
+ {
+ const ASN1_OBJECT* obj = NULL;
+ X509_ALGOR_get0(&obj, NULL, NULL, params->hashAlgorithm);
+ message_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
+ if (message_digest == WINPR_MD_NONE)
+ goto end;
+ }
+
+ mgf1_digest = WINPR_MD_SHA1;
+ if (params->maskGenAlgorithm != NULL)
+ {
+ const ASN1_OBJECT* obj = NULL;
+ int mgf_param_type = 0;
+ const void* mgf_param_value = NULL;
+ const ASN1_STRING* mgf_param_sequence = NULL;
+ /* First, check this is MGF-1, the only one ever defined. */
+ X509_ALGOR_get0(&obj, &mgf_param_type, &mgf_param_value, params->maskGenAlgorithm);
+ if (OBJ_obj2nid(obj) != NID_mgf1)
+ goto end;
+
+ /* MGF-1 is, itself, parameterized by a hash function, encoded as an AlgorithmIdentifier. */
+ if (mgf_param_type != V_ASN1_SEQUENCE)
+ goto end;
+ mgf_param_sequence = mgf_param_value;
+ inp = ASN1_STRING_get0_data(mgf_param_sequence);
+ mgf1_digest_alg = d2i_X509_ALGOR(NULL, &inp, ASN1_STRING_length(mgf_param_sequence));
+ if (mgf1_digest_alg == NULL)
+ goto end;
+
+ /* Finally, extract the digest. */
+ X509_ALGOR_get0(&obj, NULL, NULL, mgf1_digest_alg);
+ mgf1_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
+ if (mgf1_digest == WINPR_MD_NONE)
+ goto end;
+ }
+
+ /* If the two digests do not match, it is ambiguous which to return. tls-server-end-point leaves
+ it undefined, so return none.
+ https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1 */
+ if (message_digest != mgf1_digest)
+ goto end;
+ ret = message_digest;
+
+end:
+ RSA_PSS_PARAMS_free(params);
+ X509_ALGOR_free(mgf1_digest_alg);
+ return ret;
+}
+
+WINPR_MD_TYPE x509_utils_get_signature_alg(const X509* xcert)
+{
+ WINPR_ASSERT(xcert);
+
+ const int nid = X509_get_signature_nid(xcert);
+
+ if (nid == NID_rsassaPss)
+ {
+ const X509_ALGOR* alg = NULL;
+ X509_get0_signature(NULL, &alg, xcert);
+ return get_rsa_pss_digest(alg);
+ }
+
+ int hash_nid = 0;
+ if (OBJ_find_sigid_algs(nid, &hash_nid, NULL) != 1)
+ return WINPR_MD_NONE;
+
+ return hash_nid_to_winpr(hash_nid);
+}
+
+char* x509_utils_get_common_name(const X509* xcert, size_t* plength)
+{
+ X509_NAME* subject_name = X509_get_subject_name(xcert);
+ if (subject_name == NULL)
+ return NULL;
+
+ const int index = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
+ if (index < 0)
+ return NULL;
+
+ const X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject_name, index);
+ if (entry == NULL)
+ return NULL;
+
+ const ASN1_STRING* entry_data = X509_NAME_ENTRY_get_data(entry);
+ if (entry_data == NULL)
+ return NULL;
+
+ BYTE* common_name_raw = NULL;
+ const int length = ASN1_STRING_to_UTF8(&common_name_raw, entry_data);
+ if (length < 0)
+ return NULL;
+
+ if (plength)
+ *plength = (size_t)length;
+
+ char* common_name = _strdup((char*)common_name_raw);
+ OPENSSL_free(common_name_raw);
+ return common_name;
+}
+
+static int verify_cb(int ok, X509_STORE_CTX* csc)
+{
+ if (ok != 1)
+ {
+ WINPR_ASSERT(csc);
+ int err = X509_STORE_CTX_get_error(csc);
+ int derr = X509_STORE_CTX_get_error_depth(csc);
+ X509* where = X509_STORE_CTX_get_current_cert(csc);
+ const char* what = X509_verify_cert_error_string(err);
+ char* name = x509_utils_get_subject(where);
+
+ WLog_WARN(TAG, "Certificate verification failure '%s (%d)' at stack position %d", what, err,
+ derr);
+ WLog_WARN(TAG, "%s", name);
+
+ free(name);
+ }
+ return ok;
+}
+
+BOOL x509_utils_verify(X509* xcert, STACK_OF(X509) * chain, const char* certificate_store_path)
+{
+ const int purposes[3] = { X509_PURPOSE_SSL_SERVER, X509_PURPOSE_SSL_CLIENT, X509_PURPOSE_ANY };
+ X509_STORE_CTX* csc = NULL;
+ BOOL status = FALSE;
+ X509_LOOKUP* lookup = NULL;
+
+ if (!xcert)
+ return FALSE;
+
+ X509_STORE* cert_ctx = X509_STORE_new();
+
+ if (cert_ctx == NULL)
+ goto end;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ OpenSSL_add_all_algorithms();
+#else
+ OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
+ OPENSSL_INIT_LOAD_CONFIG,
+ NULL);
+#endif
+
+ if (X509_STORE_set_default_paths(cert_ctx) != 1)
+ goto end;
+
+ lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_hash_dir());
+
+ if (lookup == NULL)
+ goto end;
+
+ X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
+
+ if (certificate_store_path != NULL)
+ {
+ X509_LOOKUP_add_dir(lookup, certificate_store_path, X509_FILETYPE_PEM);
+ }
+
+ X509_STORE_set_flags(cert_ctx, 0);
+
+ for (size_t i = 0; i < ARRAYSIZE(purposes); i++)
+ {
+ int err = -1;
+ int rc = -1;
+ int purpose = purposes[i];
+ csc = X509_STORE_CTX_new();
+
+ if (csc == NULL)
+ goto skip;
+ if (!X509_STORE_CTX_init(csc, cert_ctx, xcert, chain))
+ goto skip;
+
+ X509_STORE_CTX_set_purpose(csc, purpose);
+ X509_STORE_CTX_set_verify_cb(csc, verify_cb);
+
+ rc = X509_verify_cert(csc);
+ err = X509_STORE_CTX_get_error(csc);
+ skip:
+ X509_STORE_CTX_free(csc);
+ if (rc == 1)
+ {
+ status = TRUE;
+ break;
+ }
+ else if (err != X509_V_ERR_INVALID_PURPOSE)
+ break;
+ }
+
+ X509_STORE_free(cert_ctx);
+end:
+ return status;
+}
diff --git a/libfreerdp/crypto/x509_utils.h b/libfreerdp/crypto/x509_utils.h
new file mode 100644
index 0000000..190c82c
--- /dev/null
+++ b/libfreerdp/crypto/x509_utils.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cryptographic Abstraction Layer
+ *
+ * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_LIB_X509_UTILS_H
+#define FREERDP_LIB_X509_UTILS_H
+
+#include <winpr/custom-crypto.h>
+
+#include <openssl/x509.h>
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL WINPR_MD_TYPE x509_utils_get_signature_alg(const X509* xcert);
+ FREERDP_LOCAL BYTE* x509_utils_get_hash(const X509* xcert, const char* hash, size_t* length);
+
+ FREERDP_LOCAL BYTE* x509_utils_to_pem(const X509* xcert, const STACK_OF(X509) * chain,
+ size_t* length);
+ FREERDP_LOCAL X509* x509_utils_from_pem(const char* data, size_t length, BOOL fromFile);
+
+ FREERDP_LOCAL char* x509_utils_get_subject(const X509* xcert);
+ FREERDP_LOCAL char* x509_utils_get_issuer(const X509* xcert);
+ FREERDP_LOCAL char* x509_utils_get_email(const X509* x509);
+ FREERDP_LOCAL char* x509_utils_get_upn(const X509* x509);
+
+ FREERDP_LOCAL char* x509_utils_get_common_name(const X509* xcert, size_t* plength);
+ FREERDP_LOCAL char** x509_utils_get_dns_names(const X509* xcert, size_t* count,
+ size_t** pplengths);
+
+ FREERDP_LOCAL void x509_utils_dns_names_free(size_t count, size_t* lengths, char** dns_names);
+
+ FREERDP_LOCAL BOOL x509_utils_check_eku(const X509* scert, int nid);
+ FREERDP_LOCAL void x509_utils_print_info(const X509* xcert);
+
+ FREERDP_LOCAL BOOL x509_utils_verify(X509* xcert, STACK_OF(X509) * chain,
+ const char* certificate_store_path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_X509_UTILS_H */