diff options
Diffstat (limited to '')
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, ¶m_type, ¶m_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 */ |