From b494a193db5bba20dfc476d08690655210a6a7fb Mon Sep 17 00:00:00 2001 From: akallabeth Date: Tue, 13 Apr 2021 11:01:43 +0200 Subject: [PATCH] Refactored certificate API: * Proper encapsulation * known_hosts2 backend extended (storing PEM) * New backend storing each host certificate in a file --- CMakeLists.txt | 1 + channels/printer/client/printer_main.c | 6 +- client/common/client.c | 37 +- client/common/cmdline.c | 7 +- include/freerdp/crypto/certificate.h | 63 +- include/freerdp/crypto/crypto.h | 8 +- include/freerdp/freerdp.h | 15 +- include/freerdp/settings.h | 6 +- libfreerdp/common/settings_getters.c | 14 + libfreerdp/common/settings_str.c | 2 + libfreerdp/core/gateway/ncacn_http.c | 4 +- libfreerdp/core/gateway/rdg.c | 13 +- libfreerdp/core/settings.c | 4 +- .../core/test/settings_property_lists.h | 2 + libfreerdp/crypto/base64.c | 14 +- libfreerdp/crypto/certificate.c | 1125 ++++++++++++----- libfreerdp/crypto/crypto.c | 209 ++- libfreerdp/crypto/test/TestBase64.c | 4 +- libfreerdp/crypto/test/TestKnownHosts.c | 728 +++++++++-- libfreerdp/crypto/tls.c | 255 ++-- winpr/libwinpr/path/shell.c | 2 +- 21 files changed, 1776 insertions(+), 743 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e53e89b1..8e92d359d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,7 @@ endif() if(BUILD_TESTING) set(EXPORT_ALL_SYMBOLS TRUE) + set(CTEST_OUTPUT_ON_FAILURE TRUE) elseif(NOT DEFINED EXPORT_ALL_SYMBOLS) set(EXPORT_ALL_SYMBOLS FALSE) endif() diff --git a/channels/printer/client/printer_main.c b/channels/printer/client/printer_main.c index a6af9d8b2..b971ff86f 100644 --- a/channels/printer/client/printer_main.c +++ b/channels/printer/client/printer_main.c @@ -80,7 +80,7 @@ static char* get_printer_config_path(const rdpSettings* settings, const WCHAR* n { const char* path = settings->ConfigPath; char* dir = GetCombinedPath(path, "printers"); - char* bname = crypto_base64_encode((const BYTE*)name, (int)length); + char* bname = crypto_base64_encode((const BYTE*)name, length); char* config = GetCombinedPath(dir, bname); if (config && !PathFileExistsA(config)) @@ -197,8 +197,8 @@ fail: if (rc && (lowSize <= INT_MAX)) { - int blen = 0; - crypto_base64_decode(fdata, (int)lowSize, (BYTE**)data, &blen); + size_t blen = 0; + crypto_base64_decode(fdata, lowSize, (BYTE**)data, &blen); if (*data && (blen > 0)) *length = (UINT32)blen; diff --git a/client/common/client.c b/client/common/client.c index c811b0489..e8e92718b 100644 --- a/client/common/client.c +++ b/client/common/client.c @@ -540,7 +540,6 @@ DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT const char* issuer, const char* fingerprint, DWORD flags) { const char* type = "RDP-Server"; - if (flags & VERIFY_CERT_FLAG_GATEWAY) type = "RDP-Gateway"; @@ -551,7 +550,17 @@ DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT printf("\tCommon Name: %s\n", common_name); printf("\tSubject: %s\n", subject); printf("\tIssuer: %s\n", issuer); - printf("\tThumbprint: %s\n", fingerprint); + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + { + printf("\t----------- Certificate --------------\n"); + printf("%s\n", fingerprint); + printf("\t--------------------------------------\n"); + } + else + printf("\tThumbprint: %s\n", fingerprint); printf("The above X.509 certificate could not be verified, possibly because you do not have\n" "the CA certificate in your certificate store, or the certificate has expired.\n" @@ -641,12 +650,32 @@ DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* ho printf("\tCommon Name: %s\n", common_name); printf("\tSubject: %s\n", subject); printf("\tIssuer: %s\n", issuer); - printf("\tThumbprint: %s\n", fingerprint); + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + { + printf("\t----------- Certificate --------------\n"); + printf("%s\n", fingerprint); + printf("\t--------------------------------------\n"); + } + else + printf("\tThumbprint: %s\n", fingerprint); printf("\n"); printf("Old Certificate details:\n"); printf("\tSubject: %s\n", old_subject); printf("\tIssuer: %s\n", old_issuer); - printf("\tThumbprint: %s\n", old_fingerprint); + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + { + printf("\t----------- Certificate --------------\n"); + printf("%s\n", old_fingerprint); + printf("\t--------------------------------------\n"); + } + else + printf("\tThumbprint: %s\n", old_fingerprint); printf("\n"); if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1) { diff --git a/client/common/cmdline.c b/client/common/cmdline.c index 5aca273db..9114446cd 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -3116,16 +3116,15 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, CommandLineSwitchCase(arg, "reconnect-cookie") { BYTE* base64 = NULL; - int length; + size_t length; if (!arg->Value) return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; - crypto_base64_decode((const char*)(arg->Value), (int)strlen(arg->Value), &base64, - &length); + crypto_base64_decode((const char*)(arg->Value), strlen(arg->Value), &base64, &length); if ((base64 != NULL) && (length == sizeof(ARC_SC_PRIVATE_PACKET))) { - memcpy(settings->ServerAutoReconnectCookie, base64, (size_t)length); + memcpy(settings->ServerAutoReconnectCookie, base64, length); } else { diff --git a/include/freerdp/crypto/certificate.h b/include/freerdp/crypto/certificate.h index 30190771d..bed48b17c 100644 --- a/include/freerdp/crypto/certificate.h +++ b/include/freerdp/crypto/certificate.h @@ -32,43 +32,46 @@ typedef struct rdp_certificate_store rdpCertificateStore; #include #include -struct rdp_certificate_data -{ - char* hostname; - UINT16 port; - char* subject; - char* issuer; - char* fingerprint; -}; - -struct rdp_certificate_store -{ - char* path; - char* file; - rdpSettings* settings; - rdpCertificateData* certificate_data; -}; - #ifdef __cplusplus extern "C" { #endif - FREERDP_API rdpCertificateData* certificate_data_new(const char* hostname, UINT16 port, - const char* subject, const char* issuer, - const char* fingerprint); + FREERDP_API rdpCertificateData* certificate_data_new(const char* hostname, UINT16 port); FREERDP_API void certificate_data_free(rdpCertificateData* certificate_data); - FREERDP_API rdpCertificateStore* certificate_store_new(rdpSettings* settings); - FREERDP_API BOOL certificate_data_replace(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data); + + FREERDP_API const char* certificate_data_get_host(const rdpCertificateData* cert); + FREERDP_API UINT16 certificate_data_get_port(const rdpCertificateData* cert); + + FREERDP_API BOOL certificate_data_set_pem(rdpCertificateData* cert, const char* pem); + FREERDP_API BOOL certificate_data_set_subject(rdpCertificateData* cert, const char* subject); + FREERDP_API BOOL certificate_data_set_issuer(rdpCertificateData* cert, const char* issuer); + FREERDP_API BOOL certificate_data_set_fingerprint(rdpCertificateData* cert, + const char* fingerprint); + FREERDP_API const char* certificate_data_get_pem(const rdpCertificateData* cert); + FREERDP_API const char* certificate_data_get_subject(const rdpCertificateData* cert); + FREERDP_API const char* certificate_data_get_issuer(const rdpCertificateData* cert); + FREERDP_API const char* certificate_data_get_fingerprint(const rdpCertificateData* cert); + + FREERDP_API rdpCertificateStore* certificate_store_new(const rdpSettings* settings); FREERDP_API void certificate_store_free(rdpCertificateStore* certificate_store); - FREERDP_API int certificate_data_match(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data); - FREERDP_API BOOL certificate_data_print(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data); - FREERDP_API BOOL certificate_get_stored_data(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data, - char** subject, char** issuer, char** fingerprint); + + FREERDP_API int certificate_store_contains_data(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data); + FREERDP_API rdpCertificateData* + certificate_store_load_data(rdpCertificateStore* certificate_store, const char* host, + UINT16 port); + FREERDP_API BOOL certificate_store_save_data(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data); + FREERDP_API BOOL certificate_store_remove_data(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data); + + FREERDP_API const char* + certificate_store_get_hosts_file(const rdpCertificateStore* certificate_store); + FREERDP_API const char* + certificate_store_get_certs_path(const rdpCertificateStore* certificate_store); + FREERDP_API const char* + certificate_store_get_hosts_path(const rdpCertificateStore* certificate_store); #ifdef __cplusplus } diff --git a/include/freerdp/crypto/crypto.h b/include/freerdp/crypto/crypto.h index 9c855a782..a99643111 100644 --- a/include/freerdp/crypto/crypto.h +++ b/include/freerdp/crypto/crypto.h @@ -57,6 +57,8 @@ extern "C" FREERDP_API BYTE* crypto_cert_hash(X509* xcert, const char* hash, UINT32* length); FREERDP_API char* crypto_cert_fingerprint_by_hash(X509* xcert, const char* hash); FREERDP_API char* crypto_cert_fingerprint(X509* xcert); + FREERDP_API BYTE* crypto_cert_pem(X509* xcert, STACK_OF(X509) * chain, size_t* length); + FREERDP_API X509* crypto_cert_from_pem(const char* data, size_t length, BOOL fromFile); FREERDP_API char* crypto_cert_subject(X509* xcert); FREERDP_API char* crypto_cert_subject_common_name(X509* xcert, int* length); FREERDP_API char** crypto_cert_get_dns_names(X509* xcert, int* count, int** lengths); @@ -102,9 +104,9 @@ extern "C" BYTE* output); FREERDP_API void crypto_reverse(BYTE* data, int length); - FREERDP_API char* crypto_base64_encode(const BYTE* data, int length); - FREERDP_API void crypto_base64_decode(const char* enc_data, int length, BYTE** dec_data, - int* res_length); + FREERDP_API char* crypto_base64_encode(const BYTE* data, size_t length); + FREERDP_API void crypto_base64_decode(const char* enc_data, size_t length, BYTE** dec_data, + size_t* res_length); #ifdef __cplusplus } diff --git a/include/freerdp/freerdp.h b/include/freerdp/freerdp.h index 0602dd35f..6f8845899 100644 --- a/include/freerdp/freerdp.h +++ b/include/freerdp/freerdp.h @@ -70,6 +70,7 @@ extern "C" #define VERIFY_CERT_FLAG_CHANGED 0x40 #define VERIFY_CERT_FLAG_MISMATCH 0x80 #define VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1 0x100 +#define VERIFY_CERT_FLAG_FP_IS_PEM 0x200 /* Message types used by gateway messaging callback */ #define GATEWAY_MESSAGE_CONSENT 1 @@ -91,7 +92,8 @@ extern "C" * @param common_name The certificate registered hostname. * @param subject The common name of the certificate. * @param issuer The issuer of the certificate. - * @param fingerprint The fingerprint of the certificate. + * @param fingerprint The fingerprint of the certificate (old) or the certificate in PEM + * format * @param host_mismatch A flag indicating the certificate * subject does not match the host connecting to. * @@ -110,7 +112,8 @@ extern "C" * @param common_name The certificate registered hostname. * @param subject The common name of the certificate. * @param issuer The issuer of the certificate. - * @param fingerprint The fingerprint of the certificate. + * @param fingerprint The fingerprint of the certificate (old) or the certificate in PEM + * format (VERIFY_CERT_FLAG_FP_IS_PEM set) * @param flags Flags of type VERIFY_CERT_FLAG* * * @return 1 to accept and store a certificate, 2 to accept @@ -149,10 +152,12 @@ extern "C" * @param common_name The certificate registered hostname. * @param subject The common name of the new certificate. * @param issuer The issuer of the new certificate. - * @param fingerprint The fingerprint of the new certificate. + * @param fingerprint The fingerprint of the new certificate (old) or the certificate in + * PEM format (VERIFY_CERT_FLAG_FP_IS_PEM set) * @param old_subject The common name of the old certificate. * @param old_issuer The issuer of the new certificate. - * @param old_fingerprint The fingerprint of the old certificate. + * @param old_fingerprint The fingerprint of the old certificate (old) or the certificate in + * PEM format (VERIFY_CERT_FLAG_FP_IS_PEM set) * @param flags Flags of type VERIFY_CERT_FLAG* * * @return 1 to accept and store a certificate, 2 to accept @@ -169,7 +174,7 @@ extern "C" * a certificate. * * @param instance Pointer to the freerdp instance. - * @param data Pointer to certificate data in PEM format. + * @param data Pointer to certificate data (full chain) in PEM format. * @param length The length of the certificate data. * @param hostname The hostname connecting to. * @param port The port connecting to. diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h index 08e175585..9a746562a 100644 --- a/include/freerdp/settings.h +++ b/include/freerdp/settings.h @@ -700,6 +700,8 @@ typedef struct _RDPDR_PARALLEL RDPDR_PARALLEL; #define FreeRDP_AutoAcceptCertificate (1419) #define FreeRDP_AutoDenyCertificate (1420) #define FreeRDP_CertificateAcceptedFingerprints (1421) +#define FreeRDP_CertificateUseKnownHosts (1422) +#define FreeRDP_CertificateCallbackPreferPEM (1423) #define FreeRDP_Workarea (1536) #define FreeRDP_Fullscreen (1537) #define FreeRDP_PercentScreen (1538) @@ -1176,7 +1178,9 @@ struct rdp_settings ALIGN64 BOOL AutoAcceptCertificate; /* 1419 */ ALIGN64 BOOL AutoDenyCertificate; /* 1420 */ ALIGN64 char* CertificateAcceptedFingerprints; /* 1421 */ - UINT64 padding1472[1472 - 1422]; /* 1422 */ + ALIGN64 BOOL CertificateUseKnownHosts; /* 1422 */ + ALIGN64 BOOL CertificateCallbackPreferPEM; /* 1423 */ + UINT64 padding1472[1472 - 1424]; /* 1424 */ UINT64 padding1536[1536 - 1472]; /* 1472 */ /** diff --git a/libfreerdp/common/settings_getters.c b/libfreerdp/common/settings_getters.c index 828048a73..d3aa98767 100644 --- a/libfreerdp/common/settings_getters.c +++ b/libfreerdp/common/settings_getters.c @@ -74,6 +74,12 @@ BOOL freerdp_settings_get_bool(const rdpSettings* settings, size_t id) case FreeRDP_BitmapCompressionDisabled: return settings->BitmapCompressionDisabled; + case FreeRDP_CertificateCallbackPreferPEM: + return settings->CertificateCallbackPreferPEM; + + case FreeRDP_CertificateUseKnownHosts: + return settings->CertificateUseKnownHosts; + case FreeRDP_ColorPointerFlag: return settings->ColorPointerFlag; @@ -602,6 +608,14 @@ BOOL freerdp_settings_set_bool(rdpSettings* settings, size_t id, BOOL val) settings->BitmapCompressionDisabled = val; break; + case FreeRDP_CertificateCallbackPreferPEM: + settings->CertificateCallbackPreferPEM = val; + break; + + case FreeRDP_CertificateUseKnownHosts: + settings->CertificateUseKnownHosts = val; + break; + case FreeRDP_ColorPointerFlag: settings->ColorPointerFlag = val; break; diff --git a/libfreerdp/common/settings_str.c b/libfreerdp/common/settings_str.c index a8d0e4d2d..560c131a0 100644 --- a/libfreerdp/common/settings_str.c +++ b/libfreerdp/common/settings_str.c @@ -34,6 +34,8 @@ static const struct settings_str_entry settings_map[] = { { FreeRDP_BitmapCachePersistEnabled, 0, "FreeRDP_BitmapCachePersistEnabled" }, { FreeRDP_BitmapCacheV3Enabled, 0, "FreeRDP_BitmapCacheV3Enabled" }, { FreeRDP_BitmapCompressionDisabled, 0, "FreeRDP_BitmapCompressionDisabled" }, + { FreeRDP_CertificateCallbackPreferPEM, 0, "FreeRDP_CertificateCallbackPreferPEM" }, + { FreeRDP_CertificateUseKnownHosts, 0, "FreeRDP_CertificateUseKnownHosts" }, { FreeRDP_ColorPointerFlag, 0, "FreeRDP_ColorPointerFlag" }, { FreeRDP_CompressionEnabled, 0, "FreeRDP_CompressionEnabled" }, { FreeRDP_ConsoleSession, 0, "FreeRDP_ConsoleSession" }, diff --git a/libfreerdp/core/gateway/ncacn_http.c b/libfreerdp/core/gateway/ncacn_http.c index 75da83d62..f288a0f3c 100644 --- a/libfreerdp/core/gateway/ncacn_http.c +++ b/libfreerdp/core/gateway/ncacn_http.c @@ -105,7 +105,7 @@ BOOL rpc_ncacn_http_send_in_channel_request(RpcChannel* inChannel) BOOL rpc_ncacn_http_recv_in_channel_response(RpcChannel* inChannel, HttpResponse* response) { const char* token64 = NULL; - int ntlmTokenLength = 0; + size_t ntlmTokenLength = 0; BYTE* ntlmTokenData = NULL; rdpNtlm* ntlm; @@ -259,7 +259,7 @@ BOOL rpc_ncacn_http_send_out_channel_request(RpcChannel* outChannel, BOOL replac BOOL rpc_ncacn_http_recv_out_channel_response(RpcChannel* outChannel, HttpResponse* response) { const char* token64 = NULL; - int ntlmTokenLength = 0; + size_t ntlmTokenLength = 0; BYTE* ntlmTokenData = NULL; rdpNtlm* ntlm; diff --git a/libfreerdp/core/gateway/rdg.c b/libfreerdp/core/gateway/rdg.c index 01c863eb7..45854a7b6 100644 --- a/libfreerdp/core/gateway/rdg.c +++ b/libfreerdp/core/gateway/rdg.c @@ -1181,7 +1181,7 @@ static BOOL rdg_handle_ntlm_challenge(rdpNtlm* ntlm, HttpResponse* response) BOOL continueNeeded = FALSE; size_t len; const char* token64 = NULL; - int ntlmTokenLength = 0; + size_t ntlmTokenLength = 0; BYTE* ntlmTokenData = NULL; long StatusCode; @@ -1203,16 +1203,7 @@ static BOOL rdg_handle_ntlm_challenge(rdpNtlm* ntlm, HttpResponse* response) len = strlen(token64); - if (len > INT_MAX) - return FALSE; - - crypto_base64_decode(token64, (int)len, &ntlmTokenData, &ntlmTokenLength); - - if (ntlmTokenLength < 0) - { - free(ntlmTokenData); - return FALSE; - } + crypto_base64_decode(token64, len, &ntlmTokenData, &ntlmTokenLength); if (ntlmTokenData && ntlmTokenLength) { diff --git a/libfreerdp/core/settings.c b/libfreerdp/core/settings.c index b1703fec5..83e236655 100644 --- a/libfreerdp/core/settings.c +++ b/libfreerdp/core/settings.c @@ -381,7 +381,9 @@ rdpSettings* freerdp_settings_new(DWORD flags) !freerdp_settings_set_bool(settings, FreeRDP_DisableCredentialsDelegation, FALSE) || !freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel, 2) || !freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, 0) || - !freerdp_settings_set_uint32(settings, FreeRDP_ChannelDefArraySize, 32)) + !freerdp_settings_set_uint32(settings, FreeRDP_ChannelDefArraySize, 32) || + !freerdp_settings_set_bool(settings, FreeRDP_CertificateUseKnownHosts, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, FALSE)) goto out_fail; settings->ChannelDefArray = (CHANNEL_DEF*)calloc( diff --git a/libfreerdp/core/test/settings_property_lists.h b/libfreerdp/core/test/settings_property_lists.h index c3aaeddcb..26034cf15 100644 --- a/libfreerdp/core/test/settings_property_lists.h +++ b/libfreerdp/core/test/settings_property_lists.h @@ -23,6 +23,8 @@ static const size_t bool_list_indices[] = { FreeRDP_BitmapCachePersistEnabled, FreeRDP_BitmapCacheV3Enabled, FreeRDP_BitmapCompressionDisabled, + FreeRDP_CertificateCallbackPreferPEM, + FreeRDP_CertificateUseKnownHosts, FreeRDP_ColorPointerFlag, FreeRDP_CompressionEnabled, FreeRDP_ConsoleSession, diff --git a/libfreerdp/crypto/base64.c b/libfreerdp/crypto/base64.c index 5a5da89a4..7b0638220 100644 --- a/libfreerdp/crypto/base64.c +++ b/libfreerdp/crypto/base64.c @@ -27,7 +27,7 @@ static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -char* crypto_base64_encode(const BYTE* data, int length) +char* crypto_base64_encode(const BYTE* data, size_t length) { int c; const BYTE* q; @@ -114,12 +114,12 @@ static int base64_decode_char(char c) return -1; } -static void* base64_decode(const char* s, int length, int* data_len) +static void* base64_decode(const char* s, size_t length, size_t* data_len) { int n[4]; BYTE* q; BYTE* data; - int nBlocks, i, outputLen; + size_t nBlocks, i, outputLen; if (length % 4) return NULL; @@ -132,6 +132,12 @@ static void* base64_decode(const char* s, int length, int* data_len) nBlocks = (length / 4); outputLen = 0; + if (nBlocks < 1) + { + free(data); + return NULL; + } + for (i = 0; i < nBlocks - 1; i++, q += 3) { n[0] = base64_decode_char(*s++); @@ -192,7 +198,7 @@ out_free: return NULL; } -void crypto_base64_decode(const char* enc_data, int length, BYTE** dec_data, int* res_length) +void crypto_base64_decode(const char* enc_data, size_t length, BYTE** dec_data, size_t* res_length) { *dec_data = base64_decode(enc_data, length, res_length); } diff --git a/libfreerdp/crypto/certificate.c b/libfreerdp/crypto/certificate.c index dc7e9b5f7..08a79bfcd 100644 --- a/libfreerdp/crypto/certificate.c +++ b/libfreerdp/crypto/certificate.c @@ -35,6 +35,28 @@ #include #include +#include + +#include + +struct rdp_certificate_store +{ + char* file; + char* certs_path; + char* server_path; + const rdpSettings* settings; +}; + +struct rdp_certificate_data +{ + char* hostname; + UINT16 port; + char* subject; + char* issuer; + char* fingerprint; + char* pem; +}; + static const char certificate_store_dir[] = "certs"; static const char certificate_server_dir[] = "server"; static const char certificate_known_hosts_file[] = "known_hosts2"; @@ -44,8 +66,24 @@ static const char certificate_known_hosts_file[] = "known_hosts2"; #define TAG FREERDP_TAG("crypto") -static BOOL certificate_split_line(char* line, char** host, UINT16* port, char** subject, - char** issuer, char** fingerprint); +static BOOL certificate_get_file_data(rdpCertificateStore* store, rdpCertificateData* data); +static BOOL duplicate(char** data, const char* value); + +static HANDLE open_file(const char* name, DWORD dwDesiredAccess, DWORD dwShareMode, + DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes) +{ + HANDLE fp; + WCHAR* wfile = NULL; + int rc = ConvertToUnicode(CP_UTF8, 0, name, -1, &wfile, 0); + if (rc <= 0) + return INVALID_HANDLE_VALUE; + + fp = CreateFileW(wfile, dwDesiredAccess, 0, NULL, dwCreationDisposition, dwFlagsAndAttributes, + NULL); + free(wfile); + return fp; +} +static rdpCertificateData* certificate_split_line(char* line); static BOOL certificate_line_is_comment(const char* line, size_t length) { while (length > 0) @@ -73,16 +111,40 @@ static void certificate_store_uninit(rdpCertificateStore* certificate_store) { if (certificate_store) { - free(certificate_store->path); + free(certificate_store->certs_path); free(certificate_store->file); - certificate_store->path = NULL; - certificate_store->file = NULL; + free(certificate_store->server_path); } } + +static BOOL ensure_path_exists(const char* path) +{ + BOOL res = FALSE; + WCHAR* wpath = NULL; + /* Use wide character functions to allow proper unicode handling on windows */ + int rc = ConvertToUnicode(CP_UTF8, 0, path, -1, &wpath, 0); + if (rc <= 0) + return FALSE; + + if (!PathFileExistsW(wpath)) + { + if (!PathMakePathW(wpath, 0)) + { + WLog_ERR(TAG, "error creating directory '%s'", path); + goto fail; + } + + WLog_INFO(TAG, "creating directory %s", path); + } + res = TRUE; +fail: + free(wpath); + return res; +} + static BOOL certificate_store_init(rdpCertificateStore* certificate_store) { - char* server_path = NULL; - rdpSettings* settings; + const rdpSettings* settings; const char* ConfigPath; if (!certificate_store) return FALSE; @@ -93,81 +155,75 @@ static BOOL certificate_store_init(rdpCertificateStore* certificate_store) ConfigPath = settings->ConfigPath; if (!ConfigPath) return FALSE; - if (!PathFileExistsA(ConfigPath)) - { - if (!PathMakePathA(ConfigPath, 0)) - { - WLog_ERR(TAG, "error creating directory '%s'", ConfigPath); - goto fail; - } - - WLog_INFO(TAG, "creating directory %s", ConfigPath); - } - - if (!(certificate_store->path = GetCombinedPath(ConfigPath, (char*)certificate_store_dir))) + if (!(certificate_store->certs_path = + GetCombinedPath(ConfigPath, (char*)certificate_store_dir))) goto fail; - - if (!PathFileExistsA(certificate_store->path)) - { - if (!PathMakePathA(certificate_store->path, 0)) - { - WLog_ERR(TAG, "error creating directory [%s]", certificate_store->path); - goto fail; - } - - WLog_INFO(TAG, "creating directory [%s]", certificate_store->path); - } - - if (!(server_path = GetCombinedPath(ConfigPath, (char*)certificate_server_dir))) + certificate_store->server_path = GetCombinedPath(ConfigPath, (char*)certificate_server_dir); + if (!certificate_store->server_path) goto fail; - - if (!PathFileExistsA(server_path)) - { - if (!PathMakePathA(server_path, 0)) - { - WLog_ERR(TAG, "error creating directory [%s]", server_path); - goto fail; - } - - WLog_INFO(TAG, "created directory [%s]", server_path); - } - if (!(certificate_store->file = GetCombinedPath(ConfigPath, (char*)certificate_known_hosts_file))) goto fail; + PathCchConvertStyleA(certificate_store->file, strlen(certificate_store->file), PATH_STYLE_UNIX); + + if (!ensure_path_exists(ConfigPath) || !ensure_path_exists(certificate_store->certs_path) || + !ensure_path_exists(certificate_store->server_path)) + goto fail; - free(server_path); return TRUE; fail: WLog_ERR(TAG, "certificate store initialization failed"); - free(server_path); certificate_store_uninit(certificate_store); return FALSE; } +static int compare_pem(const char* current, const char* stored) +{ + int rc = 1; + X509* xcur = NULL; + X509* xstore = NULL; + char* fpcur = NULL; + char* fpstore = NULL; + if (!current || !stored) + goto fail; + + xcur = crypto_cert_from_pem(current, strlen(current), FALSE); + xstore = crypto_cert_from_pem(stored, strlen(stored), FALSE); + if (!xcur || !xstore) + goto fail; + + fpcur = crypto_cert_fingerprint(xcur); + fpstore = crypto_cert_fingerprint(xstore); + if (!fpcur || !fpstore) + goto fail; + + rc = strcmp(fpcur, fpstore); + +fail: + free(fpcur); + free(fpstore); + X509_free(xcur); + X509_free(xstore); + return rc; +} + static int certificate_data_match_raw(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data, char** psubject, - char** pissuer, char** fprint) + const rdpCertificateData* certificate_data, char** psubject, + char** pissuer, char** fprint, char** ppem) { BOOL found = FALSE; HANDLE fp; - size_t length; - char* data; - char* mdata; - char* pline; + char* data = NULL; + char* mdata = NULL; + char* pline = NULL; int match = 1; DWORD lowSize, highSize; UINT64 size; - char* hostname = NULL; - char* subject = NULL; - char* issuer = NULL; - char* fingerprint = NULL; - unsigned short port = 0; + DWORD read; - /* Assure POSIX style paths, CreateFile expects either '/' or '\\' */ - PathCchConvertStyleA(certificate_store->file, strlen(certificate_store->file), PATH_STYLE_UNIX); - fp = CreateFileA(certificate_store->file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, - FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NORMAL, NULL); + + fp = open_file(certificate_store->file, GENERIC_READ, FILE_SHARE_READ, OPEN_ALWAYS, + FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NORMAL); if (fp == INVALID_HANDLE_VALUE) return match; @@ -176,173 +232,451 @@ static int certificate_data_match_raw(rdpCertificateStore* certificate_store, { WLog_ERR(TAG, "GetFileSize(%s) returned %s [0x%08" PRIX32 "]", certificate_store->file, strerror(errno), GetLastError()); - CloseHandle(fp); - return match; + goto fail; } size = (UINT64)lowSize | ((UINT64)highSize << 32); if (size < 1) - { - CloseHandle(fp); - return match; - } + goto fail; mdata = (char*)malloc(size + 2); if (!mdata) - { - CloseHandle(fp); - return match; - } + goto fail; data = mdata; if (!ReadFile(fp, data, size, &read, NULL) || (read != size)) - { - free(data); - CloseHandle(fp); - return match; - } + goto fail; - CloseHandle(fp); data[size] = '\n'; data[size + 1] = '\0'; pline = StrSep(&data, "\r\n"); - while (pline != NULL) + while ((pline != NULL) && !found) { - length = strlen(pline); + size_t length = strlen(pline); + rdpCertificateData* cert = NULL; - if (length > 0) + if (length == 0) + goto next; + + if (certificate_line_is_comment(pline, length)) + goto next; + + cert = certificate_split_line(pline); + if (!cert) { - if (certificate_line_is_comment(pline, length)) - { - } - else if (!certificate_split_line(pline, &hostname, &port, &subject, &issuer, - &fingerprint)) - WLog_WARN(TAG, "Invalid %s entry %s!", certificate_known_hosts_file, pline); - else if (strcmp(pline, certificate_data->hostname) == 0) - { - int outLen; - - if (port == certificate_data->port) - { - found = TRUE; - - if (fingerprint) - { - match = (strcmp(certificate_data->fingerprint, fingerprint) == 0) ? 0 : -1; - - if (fprint) - *fprint = _strdup(fingerprint); - } - - if (subject && psubject) - crypto_base64_decode(subject, strlen(subject), (BYTE**)psubject, &outLen); - - if (issuer && pissuer) - crypto_base64_decode(issuer, strlen(issuer), (BYTE**)pissuer, &outLen); - - break; - } - } + WLog_WARN(TAG, "Invalid %s entry %s!", certificate_known_hosts_file, pline); + goto next; } + { + const char* old = certificate_data_get_host(cert); + const char* cur = certificate_data_get_host(certificate_data); + + if (strcmp(old, cur) != 0) + goto next; + } + + if (certificate_data_get_port(cert) != certificate_data_get_port(certificate_data)) + goto next; + + { + const char* fingerprint = certificate_data_get_fingerprint(cert); + const char* subject = certificate_data_get_subject(cert); + const char* issuer = certificate_data_get_issuer(cert); + const char* pem = certificate_data_get_pem(cert); + const char* cur_fp = certificate_data_get_fingerprint(certificate_data); + const char* cur_pem = certificate_data_get_pem(certificate_data); + + duplicate(psubject, subject); + duplicate(pissuer, issuer); + duplicate(fprint, fingerprint); + duplicate(ppem, pem); + + match = -1; + if (fingerprint && cur_fp) + match = (strcmp(cur_fp, fingerprint) == 0) ? 0 : -1; + + if (cur_pem && pem) + match = compare_pem(cur_pem, pem); + + found = TRUE; + } + + next: + certificate_data_free(cert); pline = StrSep(&data, "\r\n"); } +fail: free(mdata); + CloseHandle(fp); return match; } -BOOL certificate_get_stored_data(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data, char** subject, - char** issuer, char** fingerprint) +static WCHAR* certificate_get_cert_file_name(rdpCertificateStore* store, + const rdpCertificateData* data) { - int rc = certificate_data_match_raw(certificate_store, certificate_data, subject, issuer, - fingerprint); + size_t x, offset = 0; + char* pem = NULL; + WCHAR* wpem = NULL; + WINPR_DIGEST_CTX* ctx; + BYTE hash[WINPR_SHA3_256_DIGEST_LENGTH] = { 0 }; + char fname[WINPR_SHA3_256_DIGEST_LENGTH * 2 + 6] = { 0 }; - if ((rc == 0) || (rc == -1)) + if (!store || !store->server_path || !data || !data->hostname) + goto fail; + + ctx = winpr_Digest_New(); + if (!ctx) + goto fail; + if (!winpr_Digest_Init(ctx, WINPR_MD_SHA256)) + goto fail; + if (!winpr_Digest_Update(ctx, (const BYTE*)data->hostname, strlen(data->hostname))) + goto fail; + if (!winpr_Digest_Update(ctx, (BYTE*)&data->port, sizeof(data->port))) + goto fail; + if (!winpr_Digest_Final(ctx, hash, sizeof(hash))) + goto fail; + + for (x = 0; x < sizeof(hash); x++) + { + int rc = _snprintf(&fname[offset], sizeof(fname) - offset, "%02x", hash[x]); + if (rc != 2) + goto fail; + offset += (size_t)rc; + } + _snprintf(&fname[offset], sizeof(fname) - offset, ".pem"); + pem = GetCombinedPath(store->server_path, fname); + if (!pem) + goto fail; + + ConvertToUnicode(CP_UTF8, 0, pem, -1, &wpem, 0); +fail: + free(pem); + winpr_Digest_Free(ctx); + return wpem; +} + +static BOOL duplicate(char** data, const char* value) +{ + char* tmp = NULL; + if (!data) + return FALSE; + if (value) + tmp = _strdup(value); + + free(*data); + *data = tmp; + if ((value == tmp) && (tmp == NULL)) return TRUE; - - return FALSE; + return tmp != NULL; } -int certificate_data_match(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data) +static rdpCertificateData* load_from_file(rdpCertificateStore* store, const char* hostname, + UINT16 port) { - return certificate_data_match_raw(certificate_store, certificate_data, NULL, NULL, NULL); + rdpCertificateData* loaded; + if (!store || !hostname) + return NULL; + + loaded = certificate_data_new(hostname, port); + if (!loaded) + return NULL; + if (!certificate_get_file_data(store, loaded)) + { + certificate_data_free(loaded); + return NULL; + } + return loaded; } -BOOL certificate_data_replace(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data) +static BOOL certificate_get_file_data(rdpCertificateStore* store, rdpCertificateData* data) { - HANDLE fp; + DWORD read; + BOOL rc = FALSE; + HANDLE fp = INVALID_HANDLE_VALUE; + + X509* certificate = NULL; + LARGE_INTEGER position = { 0 }; + const LARGE_INTEGER offset = { 0 }; + WCHAR* fname = certificate_get_cert_file_name(store, data); + if (!fname) + goto fail; + + fp = CreateFileW(fname, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fp == INVALID_HANDLE_VALUE) + goto fail; + + if (!SetFilePointerEx(fp, offset, &position, FILE_END)) + goto fail; + if (!SetFilePointerEx(fp, offset, NULL, FILE_BEGIN)) + goto fail; + + { + char* pem = realloc(data->pem, position.QuadPart + 1); + if (!pem) + goto fail; + + data->pem = pem; + } + + if (!ReadFile(fp, data->pem, position.QuadPart, &read, NULL)) + goto fail; + data->pem[read] = '\0'; + + certificate = crypto_cert_from_pem(data->pem, read, FALSE); + if (!certificate) + goto fail; + + free(data->fingerprint); + data->fingerprint = crypto_cert_fingerprint(certificate); + if (!data->fingerprint) + goto fail; + free(data->issuer); + data->issuer = crypto_cert_issuer(certificate); + if (!data->issuer) + goto fail; + free(data->subject); + data->subject = crypto_cert_subject(certificate); + if (!data->subject) + goto fail; + + rc = TRUE; + +fail: + CloseHandle(fp); + X509_free(certificate); + free(fname); + return rc; +} + +static int certificate_match_data_file(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data) +{ + int rc = 1; + const char* pem; + const char* loaded_pem; + + rdpCertificateData* loaded = + load_from_file(certificate_store, certificate_data->hostname, certificate_data->port); + if (!loaded) + return 1; + + pem = certificate_data_get_pem(certificate_data); + loaded_pem = certificate_data_get_pem(loaded); + if (!pem) + { + const char* fp = certificate_data_get_fingerprint(certificate_data); + const char* load_fp = certificate_data_get_fingerprint(loaded); + if (fp) + { + if (strcmp(fp, load_fp) == 0) + rc = 0; + else + rc = -1; + } + } + else + { + rc = compare_pem(pem, loaded_pem); + } + + certificate_data_free(loaded); + return rc; +} + +int certificate_store_contains_data(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data) +{ + if (freerdp_settings_get_bool(certificate_store->settings, FreeRDP_CertificateUseKnownHosts)) + { + char* pem = NULL; + int rc = + certificate_data_match_raw(certificate_store, certificate_data, NULL, NULL, NULL, &pem); + + /* Upgrade entry, append PEM */ + if ((rc == 0) && !pem && certificate_data->pem) + { + certificate_store_save_data(certificate_store, certificate_data); + } + free(pem); + return rc; + } + else + return certificate_match_data_file(certificate_store, certificate_data); +} + +static char* decode(const char* value) +{ + size_t len, length; + char* converted = NULL; + if (!value) + return NULL; + len = strlen(value); + crypto_base64_decode(value, len, (BYTE**)&converted, &length); + return converted; +} + +static char* encode(const char* value) +{ + size_t len; + if (!value) + return NULL; + len = strlen(value); + return (char*)crypto_base64_encode((BYTE*)value, len); +} + +static char* allocated_printf(const char* fmt, ...) +{ + int rc1, rc2; + size_t size; + va_list ap; + char* buffer = NULL; + + va_start(ap, fmt); + rc1 = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + if (rc1 <= 0) + return NULL; + + size = (size_t)rc1; + buffer = calloc(size + 2, sizeof(char)); + if (!buffer) + return NULL; + + va_start(ap, fmt); + rc2 = vsnprintf(buffer, size + 1, fmt, ap); + va_end(ap); + if (rc2 != rc1) + { + free(buffer); + return NULL; + } + return buffer; +} + +static char* certificate_data_get_host_file_entry(const rdpCertificateData* data) +{ + char* buffer = NULL; + const char* hostname = certificate_data_get_host(data); + const UINT16 port = certificate_data_get_port(data); + char* subject = encode(certificate_data_get_subject(data)); + char* issuer = encode(certificate_data_get_issuer(data)); + const char* fingerprint = certificate_data_get_fingerprint(data); + char* pem = encode(certificate_data_get_pem(data)); + + if (!data || !hostname || !fingerprint || !subject || !issuer) + goto fail; + + if (pem) + buffer = allocated_printf("%s %" PRIu16 " %s %s %s %s\n", hostname, port, fingerprint, + subject, issuer, pem); + else + buffer = allocated_printf("%s %" PRIu16 " %s %s %s\n", hostname, port, fingerprint, subject, + issuer); +fail: + free(subject); + free(issuer); + free(pem); + return buffer; +} + +static BOOL write_line_and_free(const char* filename, HANDLE fp, char* line) +{ + BOOL rc = FALSE; + DWORD size, written; + if ((fp == INVALID_HANDLE_VALUE) || !line) + return FALSE; + size = strlen(line); + rc = WriteFile(fp, line, size, &written, NULL); + + if (!rc || (written != size)) + { + WLog_ERR(TAG, "WriteFile(%s) returned %s [0x%08X]", filename, strerror(errno), errno); + rc = FALSE; + } + + free(line); + return rc; +} + +static BOOL certificate_data_replace_hosts_file(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data, + BOOL remove, BOOL append) +{ + HANDLE fp = INVALID_HANDLE_VALUE; BOOL rc = FALSE; size_t length; - char* data; - char* sdata; - char* pline; + char* data = NULL; + char* sdata = NULL; + char* pline = NULL; + UINT64 size; - DWORD read, written; + DWORD read; DWORD lowSize, highSize; - /* Assure POSIX style paths, CreateFile expects either '/' or '\\' */ - PathCchConvertStyleA(certificate_store->file, strlen(certificate_store->file), PATH_STYLE_UNIX); - fp = CreateFileA(certificate_store->file, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); + rdpCertificateData* copy = NULL; + + fp = open_file(certificate_store->file, GENERIC_READ | GENERIC_WRITE, 0, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL); if (fp == INVALID_HANDLE_VALUE) return FALSE; + /* Create a copy, if a PEM was provided it will replace subject, issuer, fingerprint */ + copy = certificate_data_new(certificate_data->hostname, certificate_data->port); + if (!copy) + goto fail; + + if (certificate_data->pem) + { + if (!certificate_data_set_pem(copy, certificate_data->pem)) + goto fail; + } + else + { + if (!certificate_data_set_subject(copy, certificate_data->subject) || + !certificate_data_set_issuer(copy, certificate_data->issuer) || + !certificate_data_set_fingerprint(copy, certificate_data->fingerprint)) + goto fail; + } + if ((lowSize = GetFileSize(fp, &highSize)) == INVALID_FILE_SIZE) { WLog_ERR(TAG, "GetFileSize(%s) returned %s [0x%08" PRIX32 "]", certificate_store->file, strerror(errno), GetLastError()); - CloseHandle(fp); - return FALSE; + goto fail; } size = (UINT64)lowSize | ((UINT64)highSize << 32); if (size < 1) - { - CloseHandle(fp); - return FALSE; - } + goto fail; data = (char*)malloc(size + 2); if (!data) - { - CloseHandle(fp); - return FALSE; - } + goto fail; if (!ReadFile(fp, data, size, &read, NULL) || (read != size)) - { - free(data); - CloseHandle(fp); - return FALSE; - } + goto fail; if (SetFilePointer(fp, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { WLog_ERR(TAG, "SetFilePointer(%s) returned %s [0x%08" PRIX32 "]", certificate_store->file, strerror(errno), GetLastError()); - free(data); - CloseHandle(fp); - return FALSE; + goto fail; } if (!SetEndOfFile(fp)) { WLog_ERR(TAG, "SetEndOfFile(%s) returned %s [0x%08" PRIX32 "]", certificate_store->file, strerror(errno), GetLastError()); - free(data); - CloseHandle(fp); - return FALSE; + goto fail; } /* Write the file back out, with appropriate fingerprint substitutions */ @@ -353,257 +687,290 @@ BOOL certificate_data_replace(rdpCertificateStore* certificate_store, while (pline != NULL) { + rdpCertificateData* cert = NULL; length = strlen(pline); if (length > 0) { - UINT16 port = 0; - char* hostname = NULL; - char* fingerprint = NULL; - char* subject = NULL; - char* issuer = NULL; - char* tdata; - if (certificate_line_is_comment(pline, length)) { } - else if (!certificate_split_line(pline, &hostname, &port, &subject, &issuer, - &fingerprint)) + else if (!(cert = certificate_split_line(pline))) WLog_WARN(TAG, "Skipping invalid %s entry %s!", certificate_known_hosts_file, pline); else { - int res; + char* line; /* If this is the replaced hostname, use the updated fingerprint. */ - if ((strcmp(hostname, certificate_data->hostname) == 0) && - (port == certificate_data->port)) + if ((strcmp(certificate_data_get_host(cert), certificate_data_get_host(copy)) == + 0) && + (certificate_data_get_port(cert) == certificate_data_get_port(copy))) { - fingerprint = certificate_data->fingerprint; rc = TRUE; + if (remove && !append) + goto next; + + line = certificate_data_get_host_file_entry(copy); } + else + line = certificate_data_get_host_file_entry(cert); - res = _snprintf(NULL, 0, "%s %" PRIu16 " %s %s %s\n", hostname, port, fingerprint, - subject, issuer); - if (res < 0) - { - free(data); - CloseHandle(fp); - return FALSE; - } - size = (size_t)res; + if (!line) + goto next; - tdata = malloc(size + 1); - - if (!tdata) - { - WLog_ERR(TAG, "malloc(%s) returned %s [0x%08X]", certificate_store->file, - strerror(errno), errno); - free(data); - CloseHandle(fp); - return FALSE; - } - - res = _snprintf(tdata, size + 1, "%s %" PRIu16 " %s %s %s\n", hostname, port, - fingerprint, subject, issuer); - if (res < 0) - { - free(tdata); - free(data); - CloseHandle(fp); - return FALSE; - } - - if ((size_t)res != size) - { - WLog_ERR(TAG, "_snprintf(%s) returned %s [0x%08X]", certificate_store->file, - strerror(errno), errno); - free(tdata); - free(data); - CloseHandle(fp); - return FALSE; - } - - if (!WriteFile(fp, tdata, size, &written, NULL) || (written != size)) - { - WLog_ERR(TAG, "WriteFile(%s) returned %s [0x%08X]", certificate_store->file, - strerror(errno), errno); - free(tdata); - free(data); - CloseHandle(fp); - return FALSE; - } - - free(tdata); + if (!write_line_and_free(certificate_store->file, fp, line)) + goto fail; } } - + next: + certificate_data_free(cert); pline = StrSep(&sdata, "\r\n"); } +fail: + if (!rc && append) + { + char* line = certificate_data_get_host_file_entry(copy); + rc = write_line_and_free(certificate_store->file, fp, line); + } + CloseHandle(fp); free(data); + certificate_data_free(copy); return rc; } -BOOL certificate_split_line(char* line, char** host, UINT16* port, char** subject, char** issuer, - char** fingerprint) +static BOOL certificate_data_write_to_file(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data) +{ + BOOL rc = FALSE; + size_t len; + HANDLE handle; + DWORD numberOfBytesWritten; + WCHAR* fname = certificate_get_cert_file_name(certificate_store, certificate_data); + if (!fname) + return FALSE; + + handle = CreateFileW(fname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle == INVALID_HANDLE_VALUE) + goto fail; + len = strlen(certificate_data->pem); + rc = WriteFile(handle, certificate_data->pem, len, &numberOfBytesWritten, NULL); + CloseHandle(handle); +fail: + free(fname); + return rc; +} + +static BOOL certificate_data_remove_file(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data) +{ + BOOL rc = FALSE; + WCHAR* fname = certificate_get_cert_file_name(certificate_store, certificate_data); + if (!fname) + return FALSE; + if (PathFileExistsW(fname)) + rc = DeleteFileW(fname); + else + rc = TRUE; + free(fname); + return rc; +} + +BOOL certificate_store_remove_data(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data) +{ + if (freerdp_settings_get_bool(certificate_store->settings, FreeRDP_CertificateUseKnownHosts)) + { + /* Ignore return, if the entry was invalid just continue */ + certificate_data_replace_hosts_file(certificate_store, certificate_data, TRUE, FALSE); + return TRUE; + } + else + return certificate_data_remove_file(certificate_store, certificate_data); +} + +rdpCertificateData* certificate_split_line(char* line) { char* cur; + char* host = NULL; + char* subject = NULL; + char* issuer = NULL; + char* fingerprint = NULL; + char* pem = NULL; + UINT16 port = 0; + rdpCertificateData* data = NULL; + size_t length = strlen(line); if (length <= 0) - return FALSE; + goto fail; cur = StrSep(&line, " \t"); if (!cur) - return FALSE; + goto fail; - *host = cur; + host = cur; cur = StrSep(&line, " \t"); if (!cur) - return FALSE; + goto fail; - if (sscanf(cur, "%hu", port) != 1) - return FALSE; + if (sscanf(cur, "%hu", &port) != 1) + goto fail; cur = StrSep(&line, " \t"); if (!cur) - return FALSE; + goto fail; - *fingerprint = cur; + fingerprint = cur; cur = StrSep(&line, " \t"); if (!cur) - return FALSE; + goto fail; - *subject = cur; + subject = cur; cur = StrSep(&line, " \t"); if (!cur) - return FALSE; + goto fail; - *issuer = cur; - return TRUE; + issuer = cur; + + /* Optional field */ + cur = StrSep(&line, " \t"); + if (cur) + pem = cur; + + data = certificate_data_new(host, port); + if (!data) + goto fail; + if (pem) + { + BOOL rc; + char* dpem = NULL; + size_t length; + crypto_base64_decode(pem, strlen(pem), (BYTE**)&dpem, &length); + rc = certificate_data_set_pem(data, dpem); + free(dpem); + if (!rc) + goto fail; + } + else + { + BOOL rc; + size_t length; + char* dsubject = NULL; + char* dissuer = NULL; + crypto_base64_decode(subject, strlen(subject), (BYTE**)&dsubject, &length); + crypto_base64_decode(issuer, strlen(issuer), (BYTE**)&dissuer, &length); + + rc = certificate_data_set_subject(data, dsubject) && + certificate_data_set_issuer(data, dissuer) && + certificate_data_set_fingerprint(data, fingerprint); + free(dsubject); + free(dissuer); + if (!rc) + goto fail; + } + return data; +fail: + certificate_data_free(data); + return NULL; } -BOOL certificate_data_print(rdpCertificateStore* certificate_store, - rdpCertificateData* certificate_data) +static BOOL update_from_pem(rdpCertificateData* data) { - int rc; - HANDLE fp; - char* tdata; - size_t size; - DWORD written; - /* reopen in append mode */ - /* Assure POSIX style paths, CreateFile expects either '/' or '\\' */ - PathCchConvertStyleA(certificate_store->file, strlen(certificate_store->file), PATH_STYLE_UNIX); - fp = CreateFileA(certificate_store->file, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); + BOOL rc = FALSE; + char* subject = NULL; + char* issuer = NULL; + char* fingerprint = NULL; + X509* x1 = NULL; - if (fp == INVALID_HANDLE_VALUE) + if (!data || !data->pem) return FALSE; - - if (SetFilePointer(fp, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) - { - WLog_ERR(TAG, "SetFilePointer(%s) returned %s [0x%08" PRIX32 "]", certificate_store->file, - strerror(errno), GetLastError()); - CloseHandle(fp); - return FALSE; - } - - rc = _snprintf(NULL, 0, "%s %" PRIu16 " %s %s %s\n", certificate_data->hostname, - certificate_data->port, certificate_data->fingerprint, certificate_data->subject, - certificate_data->issuer); - if (rc < 0) - return FALSE; - size = (size_t)rc; - - tdata = malloc(size + 1); - - if (!tdata) - { - WLog_ERR(TAG, "malloc(%s) returned %s [0x%08X]", certificate_store->file, strerror(errno), - errno); - CloseHandle(fp); - return FALSE; - } - - rc = _snprintf(tdata, size + 1, "%s %" PRIu16 " %s %s %s\n", certificate_data->hostname, - certificate_data->port, certificate_data->fingerprint, certificate_data->subject, - certificate_data->issuer); - - if ((rc < 0) || ((size_t)rc != size)) - { - WLog_ERR(TAG, "_snprintf(%s) returned %s [0x%08X]", certificate_store->file, - strerror(errno), errno); - free(tdata); - CloseHandle(fp); - return FALSE; - } - - if (!WriteFile(fp, tdata, size, &written, NULL) || (written != size)) - { - WLog_ERR(TAG, "WriteFile(%s) returned %s [0x%08X]", certificate_store->file, - strerror(errno), errno); - free(tdata); - CloseHandle(fp); - return FALSE; - } - - free(tdata); - CloseHandle(fp); - return TRUE; + x1 = crypto_cert_from_pem(data->pem, strlen(data->pem), FALSE); + if (!x1) + goto fail; + subject = crypto_cert_subject(x1); + if (!subject) + goto fail; + issuer = crypto_cert_issuer(x1); + if (!issuer) + goto fail; + fingerprint = crypto_cert_fingerprint(x1); + if (!fingerprint) + goto fail; + duplicate(&data->subject, subject); + duplicate(&data->issuer, issuer); + duplicate(&data->fingerprint, fingerprint); + rc = TRUE; +fail: + free(subject); + free(issuer); + free(fingerprint); + X509_free(x1); + return rc; } -rdpCertificateData* certificate_data_new(const char* hostname, UINT16 port, const char* subject, - const char* issuer, const char* fingerprint) +BOOL certificate_store_save_data(rdpCertificateStore* certificate_store, + const rdpCertificateData* certificate_data) +{ + if (freerdp_settings_get_bool(certificate_store->settings, FreeRDP_CertificateUseKnownHosts)) + return certificate_data_replace_hosts_file(certificate_store, certificate_data, TRUE, TRUE); + else + return certificate_data_write_to_file(certificate_store, certificate_data); +} + +rdpCertificateData* certificate_store_load_data(rdpCertificateStore* certificate_store, + const char* host, UINT16 port) +{ + if (freerdp_settings_get_bool(certificate_store->settings, FreeRDP_CertificateUseKnownHosts)) + { + int rc; + rdpCertificateData* data = certificate_data_new(host, port); + if (!data) + return NULL; + + rc = certificate_data_match_raw(certificate_store, data, &data->subject, &data->issuer, + &data->fingerprint, &data->pem); + if ((rc == 0) || (rc == -1)) + return data; + certificate_data_free(data); + return NULL; + } + else + { + return load_from_file(certificate_store, host, port); + } +} + +rdpCertificateData* certificate_data_new(const char* hostname, UINT16 port) { size_t i; - rdpCertificateData* certdata; + rdpCertificateData* certdata = NULL; if (!hostname) - return NULL; - - if (!fingerprint) - return NULL; + goto fail; certdata = (rdpCertificateData*)calloc(1, sizeof(rdpCertificateData)); if (!certdata) - return NULL; + goto fail; certdata->port = port; certdata->hostname = _strdup(hostname); - - if (subject) - certdata->subject = crypto_base64_encode((const BYTE*)subject, strlen(subject)); - else - certdata->subject = crypto_base64_encode((const BYTE*)"", 0); - - if (issuer) - certdata->issuer = crypto_base64_encode((const BYTE*)issuer, strlen(issuer)); - else - certdata->issuer = crypto_base64_encode((const BYTE*)"", 0); - - certdata->fingerprint = _strdup(fingerprint); - - if (!certdata->hostname || !certdata->subject || !certdata->issuer || !certdata->fingerprint) + if (!certdata->hostname) goto fail; - for (i = 0; i < strlen(hostname); i++) certdata->hostname[i] = tolower(certdata->hostname[i]); return certdata; fail: - free(certdata->hostname); - free(certdata->subject); - free(certdata->issuer); - free(certdata->fingerprint); - free(certdata); + certificate_data_free(certdata); return NULL; } @@ -615,14 +982,85 @@ void certificate_data_free(rdpCertificateData* certificate_data) free(certificate_data->subject); free(certificate_data->issuer); free(certificate_data->fingerprint); + free(certificate_data->pem); free(certificate_data); } } -rdpCertificateStore* certificate_store_new(rdpSettings* settings) +const char* certificate_data_get_host(const rdpCertificateData* cert) { - rdpCertificateStore* certificate_store; - certificate_store = (rdpCertificateStore*)calloc(1, sizeof(rdpCertificateStore)); + if (!cert) + return NULL; + return cert->hostname; +} + +UINT16 certificate_data_get_port(const rdpCertificateData* cert) +{ + if (!cert) + return 0; + return cert->port; +} + +BOOL certificate_data_set_pem(rdpCertificateData* cert, const char* pem) +{ + if (!cert) + return FALSE; + if (!duplicate(&cert->pem, pem)) + return FALSE; + if (!pem) + return TRUE; + + return update_from_pem(cert); +} + +BOOL certificate_data_set_subject(rdpCertificateData* cert, const char* subject) +{ + if (!cert) + return FALSE; + return duplicate(&cert->subject, subject); +} +BOOL certificate_data_set_issuer(rdpCertificateData* cert, const char* issuer) +{ + if (!cert) + return FALSE; + return duplicate(&cert->issuer, issuer); +} +BOOL certificate_data_set_fingerprint(rdpCertificateData* cert, const char* fingerprint) +{ + if (!cert) + return FALSE; + return duplicate(&cert->fingerprint, fingerprint); +} +const char* certificate_data_get_pem(const rdpCertificateData* cert) +{ + if (!cert) + return NULL; + return cert->pem; +} + +const char* certificate_data_get_subject(const rdpCertificateData* cert) +{ + if (!cert) + return NULL; + return cert->subject; +} +const char* certificate_data_get_issuer(const rdpCertificateData* cert) +{ + if (!cert) + return NULL; + return cert->issuer; +} +const char* certificate_data_get_fingerprint(const rdpCertificateData* cert) +{ + if (!cert) + return NULL; + return cert->fingerprint; +} + +rdpCertificateStore* certificate_store_new(const rdpSettings* settings) +{ + rdpCertificateStore* certificate_store = + (rdpCertificateStore*)calloc(1, sizeof(rdpCertificateStore)); if (!certificate_store) return NULL; @@ -631,7 +1069,7 @@ rdpCertificateStore* certificate_store_new(rdpSettings* settings) if (!certificate_store_init(certificate_store)) { - free(certificate_store); + certificate_data_free(certificate_store); return NULL; } @@ -641,8 +1079,27 @@ rdpCertificateStore* certificate_store_new(rdpSettings* settings) void certificate_store_free(rdpCertificateStore* certstore) { certificate_store_uninit(certstore); - if (certstore != NULL) - { - free(certstore); - } + free(certstore); +} + +const char* certificate_store_get_hosts_file(const rdpCertificateStore* certificate_store) +{ + if (!certificate_store) + return NULL; + return certificate_store->file; +} + +const char* certificate_store_get_certs_path(const rdpCertificateStore* certificate_store) +{ + if (!certificate_store) + return NULL; + return certificate_store->certs_path; +} + +const char* certificate_store_get_hosts_path(const rdpCertificateStore* certificate_store) +{ + + if (!certificate_store) + return NULL; + return certificate_store->server_path; } diff --git a/libfreerdp/crypto/crypto.c b/libfreerdp/crypto/crypto.c index 8f7c48255..d02cbed73 100644 --- a/libfreerdp/crypto/crypto.c +++ b/libfreerdp/crypto/crypto.c @@ -250,19 +250,27 @@ BYTE* crypto_cert_hash(X509* xcert, const char* hash, UINT32* length) BYTE* fp; const EVP_MD* md = EVP_get_digestbyname(hash); if (!md) + { + WLog_ERR(TAG, "System does not support %s hash!", hash); return NULL; - if (!length) - return NULL; - if (!xcert) + } + if (!xcert || !length) + { + WLog_ERR(TAG, "[%s] Invalid arugments: xcert=%p, length=%p", __FUNCTION__, xcert, length); return NULL; + } fp = calloc(fp_len, sizeof(BYTE)); if (!fp) + { + WLog_ERR(TAG, "[%s] could not allocate %" PRIuz " bytes", __FUNCTION__, 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; } @@ -276,7 +284,16 @@ char* crypto_cert_fingerprint_by_hash(X509* xcert, const char* hash) BYTE* fp; char* p; char* fp_buffer; - + if (!xcert) + { + WLog_ERR(TAG, "Invalid certificate %p", xcert); + return NULL; + } + if (!hash) + { + WLog_ERR(TAG, "Invalid certificate hash %p", hash); + return NULL; + } fp = crypto_cert_hash(xcert, hash, &fp_len); if (!fp) return NULL; @@ -322,7 +339,16 @@ static char* crypto_print_name(X509_NAME* name) char* crypto_cert_subject(X509* xcert) { - return crypto_print_name(X509_get_subject_name(xcert)); + char* subject; + if (!xcert) + { + WLog_ERR(TAG, "Invalid certificate %p", xcert); + return NULL; + } + subject = crypto_print_name(X509_get_subject_name(xcert)); + if (!subject) + WLog_ERR(TAG, "certificate does not have a subject!"); + return subject; } char* crypto_cert_subject_common_name(X509* xcert, int* length) @@ -799,7 +825,16 @@ char** crypto_cert_get_dns_names(X509* x509, int* count, int** lengths) char* crypto_cert_issuer(X509* xcert) { - return crypto_print_name(X509_get_issuer_name(xcert)); + char* issuer; + if (!xcert) + { + WLog_ERR(TAG, "Invalid certificate %p", xcert); + return NULL; + } + issuer = crypto_print_name(X509_get_issuer_name(xcert)); + if (!issuer) + WLog_ERR(TAG, "certificate does not have an issuer!"); + return issuer; } static int verify_cb(int ok, X509_STORE_CTX* csc) @@ -893,22 +928,24 @@ end: rdpCertificateData* crypto_get_certificate_data(X509* xcert, const char* hostname, UINT16 port) { - char* issuer; - char* subject; - char* fp; - rdpCertificateData* certdata; - fp = crypto_cert_fingerprint(xcert); + char* pem = NULL; + size_t length; + rdpCertificateData* certdata = NULL; - if (!fp) - return NULL; - - issuer = crypto_cert_issuer(xcert); - subject = crypto_cert_subject(xcert); - certdata = certificate_data_new(hostname, port, issuer, subject, fp); - free(subject); - free(issuer); - free(fp); + pem = (char*)crypto_cert_pem(xcert, NULL, &length); + if (!pem) + goto fail; + certdata = certificate_data_new(hostname, port); + if (!certdata) + goto fail; + if (!certificate_data_set_pem(certdata, pem)) + goto fail; + free(pem); return certdata; +fail: + certificate_data_free(certdata); + free(pem); + return NULL; } void crypto_cert_print_info(X509* xcert) @@ -939,3 +976,135 @@ out_free_issuer: free(issuer); free(subject); } + +BYTE* crypto_cert_pem(X509* xcert, STACK_OF(X509) * chain, size_t* plength) +{ + BIO* bio; + int status, count, x; + size_t offset; + 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, xcert); + + if (status < 0) + { + WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status); + goto fail; + } + + if (chain) + { + count = sk_X509_num(chain); + for (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; + } + + 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; + BYTE* new_cert; + new_len = length * 2; + new_cert = (BYTE*)realloc(pemCert, new_len + 1); + + if (!new_cert) + goto fail; + + length = new_len; + pemCert = new_cert; + 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) + { + free(pemCert); + pemCert = NULL; + } + + BIO_free_all(bio); + return pemCert; +} + +X509* crypto_cert_from_pem(const char* data, size_t len, BOOL fromFile) +{ + X509* x509 = NULL; + BIO* bio; + 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; +} diff --git a/libfreerdp/crypto/test/TestBase64.c b/libfreerdp/crypto/test/TestBase64.c index c4309868f..ee9553121 100644 --- a/libfreerdp/crypto/test/TestBase64.c +++ b/libfreerdp/crypto/test/TestBase64.c @@ -22,7 +22,7 @@ struct Encode64test { const char* input; - int len; + size_t len; const char* output; }; @@ -41,7 +41,7 @@ static const struct Encode64test encodeTests[] = { int TestBase64(int argc, char* argv[]) { int i, testNb = 0; - int outLen; + size_t outLen; BYTE* decoded; WINPR_UNUSED(argc); WINPR_UNUSED(argv); diff --git a/libfreerdp/crypto/test/TestKnownHosts.c b/libfreerdp/crypto/test/TestKnownHosts.c index 62976c7df..5f6dea3e3 100644 --- a/libfreerdp/crypto/test/TestKnownHosts.c +++ b/libfreerdp/crypto/test/TestKnownHosts.c @@ -23,11 +23,104 @@ #include +/* Some certificates copied from /usr/share/ca-certificates */ +static const char pem1[] = "-----BEGIN CERTIFICATE-----\n" + "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\n" + "-----END CERTIFICATE-----"; + +static const char pem2[] = "-----BEGIN CERTIFICATE-----\n" + "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\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 subject issuer\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; @@ -52,21 +145,19 @@ finish: return rc; } -int TestKnownHosts(int argc, char* argv[]) +static BOOL setup_config(rdpSettings** settings) { - int rc = -1; - rdpSettings current; - rdpCertificateData* data = NULL; - rdpCertificateStore* store = NULL; - char* currentFileV2 = NULL; - char* subject = NULL; - char* issuer = NULL; - char* fp = NULL; + BOOL rc = FALSE; + char* path = NULL; char sname[8192]; - SYSTEMTIME systemTime; - WINPR_UNUSED(argc); - WINPR_UNUSED(argv); + + 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 @@ -74,18 +165,237 @@ int TestKnownHosts(int argc, char* argv[]) systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds); - current.ConfigPath = GetKnownSubPath(KNOWN_PATH_TEMP, sname); - - if (!PathFileExistsA(current.ConfigPath)) + path = GetKnownSubPath(KNOWN_PATH_TEMP, sname); + if (!path) + goto fail; + if (!PathFileExistsA(path)) { - if (!CreateDirectoryA(current.ConfigPath, NULL)) + if (!CreateDirectoryA(path, NULL)) { - fprintf(stderr, "Could not create %s!\n", current.ConfigPath); - goto finish; + fprintf(stderr, "Could not create %s!\n", path); + goto fail; } } - currentFileV2 = GetCombinedPath(current.ConfigPath, "known_hosts2"); + rc = freerdp_settings_set_string(*settings, FreeRDP_ConfigPath, path); +fail: + free(path); + return rc; +} + +/* Test if host is found in current file. */ +static BOOL test_known_hosts_host_found(rdpCertificateStore* store) +{ + BOOL rc = FALSE; + rdpCertificateData* stored_data = NULL; + rdpCertificateData* data; + + printf("%s\n", __FUNCTION__); + data = certificate_data_new("someurl", 3389); + if (!data) + { + fprintf(stderr, "Could not create certificate data!\n"); + goto finish; + } + if (!certificate_data_set_subject(data, "subject") || + !certificate_data_set_issuer(data, "issuer") || + !certificate_data_set_fingerprint(data, "ff:11:22:dd")) + goto finish; + + if (0 != certificate_store_contains_data(store, data)) + { + fprintf(stderr, "Could not find data in v2 file!\n"); + goto finish; + } + + /* Test if we can read out the old fingerprint. */ + stored_data = certificate_store_load_data(store, certificate_data_get_host(data), + certificate_data_get_port(data)); + if (!stored_data) + { + fprintf(stderr, "Could not read old fingerprint!\n"); + goto finish; + } + + printf("Got %s, %s '%s'\n", certificate_data_get_subject(stored_data), + certificate_data_get_issuer(stored_data), certificate_data_get_fingerprint(stored_data)); + + rc = TRUE; +finish: + printf("certificate_data_free %d\n", rc); + certificate_data_free(data); + certificate_data_free(stored_data); + return rc; +} + +/* Test if host not found in current file. */ +static BOOL test_known_hosts_host_not_found(rdpCertificateStore* store) +{ + BOOL rc = FALSE; + rdpCertificateData* stored_data = NULL; + rdpCertificateData* data; + + printf("%s\n", __FUNCTION__); + data = certificate_data_new("somehost", 1234); + + if (!data) + { + fprintf(stderr, "Could not create certificate data!\n"); + goto finish; + } + + if (!certificate_data_set_fingerprint(data, "ff:aa:bb:cc")) + goto finish; + + if (0 == certificate_store_contains_data(store, data)) + { + fprintf(stderr, "Invalid host found in v2 file!\n"); + goto finish; + } + + /* Test if we read out the old fingerprint fails. */ + stored_data = certificate_store_load_data(store, certificate_data_get_host(data), + certificate_data_get_port(data)); + if (stored_data) + { + fprintf(stderr, "Read out not existing old fingerprint succeeded?!\n"); + goto finish; + } + + rc = TRUE; +finish: + printf("certificate_data_free %d\n", rc); + certificate_data_free(data); + certificate_data_free(stored_data); + return rc; +} + +/* Test host add current file. */ +static BOOL test_known_hosts_host_add(rdpCertificateStore* store) +{ + BOOL rc = FALSE; + rdpCertificateData* data; + + printf("%s\n", __FUNCTION__); + + data = certificate_data_new("somehost", 1234); + + if (!data) + { + fprintf(stderr, "Could not create certificate data!\n"); + goto finish; + } + if (!certificate_data_set_subject(data, "ff:aa:bb:cc") || + !certificate_data_set_issuer(data, "ff:aa:bb:cc") || + !certificate_data_set_fingerprint(data, "ff:aa:bb:cc")) + goto finish; + + if (!certificate_store_save_data(store, data)) + { + fprintf(stderr, "Could not add host to file!\n"); + goto finish; + } + + if (0 != certificate_store_contains_data(store, data)) + { + fprintf(stderr, "Could not find host written in v2 file!\n"); + goto finish; + } + rc = TRUE; +finish: + printf("certificate_data_free %d\n", rc); + certificate_data_free(data); + return rc; +} + +/* Test host replace current file. */ +static BOOL test_known_hosts_host_replace(rdpCertificateStore* store) +{ + BOOL rc = FALSE; + rdpCertificateData* data; + + printf("%s\n", __FUNCTION__); + data = certificate_data_new("somehost", 1234); + + if (!data) + { + fprintf(stderr, "Could not create certificate data!\n"); + goto finish; + } + if (!certificate_data_set_subject(data, "ff:aa:xx:cc") || + !certificate_data_set_issuer(data, "ff:aa:bb:ee") || + !certificate_data_set_fingerprint(data, "ff:aa:bb:dd:ee")) + goto finish; + + if (!certificate_store_save_data(store, data)) + { + fprintf(stderr, "Could not replace data!\n"); + goto finish; + } + + if (0 != certificate_store_contains_data(store, data)) + { + fprintf(stderr, "Invalid host found in v2 file!\n"); + goto finish; + } + + rc = TRUE; +finish: + printf("certificate_data_free %d\n", rc); + certificate_data_free(data); + return rc; +} + +/* Test host replace invalid entry in current file. */ +static BOOL test_known_hosts_host_replace_invalid(rdpCertificateStore* store) +{ + BOOL rc = FALSE; + rdpCertificateData* data; + + printf("%s\n", __FUNCTION__); + data = certificate_data_new("somehostXXXX", 1234); + + if (!data) + { + fprintf(stderr, "Could not create certificate data!\n"); + goto finish; + } + if (!certificate_data_set_fingerprint(data, "ff:aa:bb:dd:ee")) + goto finish; + + if (certificate_store_save_data(store, data)) + { + fprintf(stderr, "Invalid return for replace invalid entry!\n"); + goto finish; + } + + if (0 == certificate_store_contains_data(store, data)) + { + fprintf(stderr, "Invalid host found in v2 file!\n"); + goto finish; + } + rc = TRUE; +finish: + printf("certificate_data_free %d\n", rc); + certificate_data_free(data); + return rc; +} + +static BOOL test_known_hosts_file(void) +{ + BOOL rc = FALSE; + rdpSettings* settings = NULL; + rdpCertificateStore* store = NULL; + char* currentFileV2 = NULL; + + printf("%s", __FUNCTION__); + if (!setup_config(&settings)) + goto finish; + if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateUseKnownHosts, TRUE)) + goto finish; + + currentFileV2 = + GetCombinedPath(freerdp_settings_get_string(settings, FreeRDP_ConfigPath), "known_hosts2"); if (!currentFileV2) { @@ -93,7 +403,8 @@ int TestKnownHosts(int argc, char* argv[]) goto finish; } - store = certificate_store_new(¤t); + printf("certificate_store_new\n"); + store = certificate_store_new(settings); if (!store) { @@ -104,139 +415,276 @@ int TestKnownHosts(int argc, char* argv[]) if (prepare(currentFileV2)) goto finish; - /* Test if host is found in current file. */ - data = certificate_data_new("someurl", 3389, "subject", "issuer", "ff:11:22:dd"); - - if (!data) - { - fprintf(stderr, "Could not create certificate data!\n"); + if (!test_known_hosts_host_found(store)) goto finish; - } - if (0 != certificate_data_match(store, data)) - { - fprintf(stderr, "Could not find data in v2 file!\n"); + if (!test_known_hosts_host_not_found(store)) goto finish; - } - /* Test if we can read out the old fingerprint. */ - if (!certificate_get_stored_data(store, data, &subject, &issuer, &fp)) - { - fprintf(stderr, "Could not read old fingerprint!\n"); + if (!test_known_hosts_host_add(store)) goto finish; - } - printf("Got %s, %s '%s'\n", subject, issuer, fp); - free(subject); - free(issuer); - free(fp); - subject = NULL; - issuer = NULL; - fp = NULL; - certificate_data_free(data); - /* Test if host not found in current file. */ - data = certificate_data_new("somehost", 1234, "", "", "ff:aa:bb:cc"); - - if (!data) - { - fprintf(stderr, "Could not create certificate data!\n"); + if (!test_known_hosts_host_replace(store)) goto finish; - } - if (0 == certificate_data_match(store, data)) - { - fprintf(stderr, "Invalid host found in v2 file!\n"); + if (!test_known_hosts_host_replace_invalid(store)) goto finish; - } - /* Test if we read out the old fingerprint fails. */ - if (certificate_get_stored_data(store, data, &subject, &issuer, &fp)) - { - fprintf(stderr, "Read out not existing old fingerprint succeeded?!\n"); - goto finish; - } - - certificate_data_free(data); - /* Test host add current file. */ - data = certificate_data_new("somehost", 1234, "", "", "ff:aa:bb:cc"); - - if (!data) - { - fprintf(stderr, "Could not create certificate data!\n"); - goto finish; - } - - if (!certificate_data_print(store, data)) - { - fprintf(stderr, "Could not add host to file!\n"); - goto finish; - } - - if (0 != certificate_data_match(store, data)) - { - fprintf(stderr, "Could not find host written in v2 file!\n"); - goto finish; - } - - certificate_data_free(data); - /* Test host replace current file. */ - data = certificate_data_new("somehost", 1234, "", "", "ff:aa:bb:dd:ee"); - - if (!data) - { - fprintf(stderr, "Could not create certificate data!\n"); - goto finish; - } - - if (!certificate_data_replace(store, data)) - { - fprintf(stderr, "Could not replace data!\n"); - goto finish; - } - - if (0 != certificate_data_match(store, data)) - { - fprintf(stderr, "Invalid host found in v2 file!\n"); - goto finish; - } - - certificate_data_free(data); - /* Test host replace invalid entry in current file. */ - data = certificate_data_new("somehostXXXX", 1234, "", "", "ff:aa:bb:dd:ee"); - - if (!data) - { - fprintf(stderr, "Could not create certificate data!\n"); - goto finish; - } - - if (certificate_data_replace(store, data)) - { - fprintf(stderr, "Invalid return for replace invalid entry!\n"); - goto finish; - } - - if (0 == certificate_data_match(store, data)) - { - fprintf(stderr, "Invalid host found in v2 file!\n"); - goto finish; - } - - rc = 0; + rc = TRUE; finish: - free(current.ConfigPath); + freerdp_settings_free(settings); - if (store) - certificate_store_free(store); - - if (data) - certificate_data_free(data); + printf("certificate_store_free\n"); + certificate_store_free(store); DeleteFileA(currentFileV2); - // RemoveDirectoryA(current.ConfigPath); free(currentFileV2); - free(subject); - free(issuer); - free(fp); + 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(certificate_data_get_subject(data), certificate_data_get_subject(stored))) + return FALSE; + if (!equal(certificate_data_get_issuer(data), certificate_data_get_issuer(stored))) + return FALSE; + if (!equal(certificate_data_get_fingerprint(data), certificate_data_get_fingerprint(stored))) + return FALSE; + return TRUE; +} + +static BOOL pem_equal(const char* a, const char* b) +{ + BOOL rc = FALSE; + size_t sa = strlen(a); + size_t sb = strlen(b); + X509* x1 = crypto_cert_from_pem(a, sa, FALSE); + X509* x2 = crypto_cert_from_pem(b, sb, FALSE); + char* f1 = NULL; + char* f2 = NULL; + if (!x1 || !x2) + goto fail; + f1 = crypto_cert_fingerprint(x1); + f2 = crypto_cert_fingerprint(x1); + if (!f1 || !f2) + goto fail; + + rc = strcmp(f1, f2) == 0; + +fail: + free(f1); + free(f2); + X509_free(x1); + X509_free(x2); + return rc; +} + +static BOOL compare_ex(const rdpCertificateData* data, const rdpCertificateData* stored) +{ + if (!compare(data, stored)) + return FALSE; + if (!pem_equal(certificate_data_get_pem(data), certificate_data_get_pem(stored))) + return FALSE; + + return TRUE; +} + +static BOOL test_get_data(rdpCertificateStore* store, const rdpCertificateData* data) +{ + BOOL res; + rdpCertificateData* stored = certificate_store_load_data(store, certificate_data_get_host(data), + certificate_data_get_port(data)); + if (!stored) + return FALSE; + + res = compare(data, stored); + certificate_data_free(stored); + return res; +} + +static BOOL test_get_data_ex(rdpCertificateStore* store, const rdpCertificateData* data) +{ + BOOL res; + rdpCertificateData* stored = certificate_store_load_data(store, certificate_data_get_host(data), + certificate_data_get_port(data)); + if (!stored) + return FALSE; + + res = compare_ex(data, stored); + certificate_data_free(stored); + return res; +} + +static BOOL test_certs_dir(BOOL useHostsFile) +{ + BOOL rc = FALSE; + rdpSettings* settings = NULL; + rdpCertificateStore* store = NULL; + rdpCertificateData* data1 = NULL; + rdpCertificateData* data2 = NULL; + rdpCertificateData* data3 = NULL; + rdpCertificateData* data4 = NULL; + + printf("%s %d\n", __FUNCTION__, useHostsFile); + if (!setup_config(&settings)) + goto fail; + /* Initialize certificate folder backend */ + if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateUseKnownHosts, useHostsFile)) + goto fail; + printf("certificate_store_new()\n"); + store = certificate_store_new(settings); + if (!store) + goto fail; + + { + printf("certificate_data_new()\n"); + data1 = certificate_data_new("somehost", 1234); + data2 = certificate_data_new("otherhost", 4321); + data3 = certificate_data_new("otherhost4", 444); + data4 = certificate_data_new("otherhost", 4321); + if (!data1 || !data2 || !data3 || !data4) + goto fail; + + printf("certificate_data_set_pem(1 [%" PRIuz "])\n", strlen(pem1)); + if (!certificate_data_set_pem(data1, pem1)) + goto fail; + printf("certificate_data_set_pem(2 [%" PRIuz "])\n", strlen(pem2)); + if (!certificate_data_set_pem(data2, pem2)) + goto fail; + printf("certificate_data_set_pem(3 [%" PRIuz "])\n", strlen(pem3)); + if (!certificate_data_set_pem(data3, pem3)) + goto fail; + printf("certificate_data_set_pem(4 [%" PRIuz "])\n", strlen(pem4)); + if (!certificate_data_set_pem(data4, pem4)) + goto fail; + + /* Find non existing in empty store */ + printf("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("certificate_store_save_data\n"); + if (!certificate_store_save_data(store, data1)) + goto fail; + if (!certificate_store_save_data(store, data2)) + goto fail; + + /* Find non existing in non empty store */ + printf("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("certificate_store_save_data\n"); + if (!certificate_store_save_data(store, data3)) + goto fail; + + /* Check existing can all be found */ + printf("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("certificate_store_save_data modify data\n"); + if (!certificate_store_save_data(store, data4)) + goto fail; + + /* Check new data is in store */ + printf("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("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("certificate_store_remove_data\n"); + if (!certificate_store_remove_data(store, data3)) + goto fail; + /* Delete non existing, should succeed */ + printf("certificate_store_remove_data missing value\n"); + if (!certificate_store_remove_data(store, data3)) + goto fail; + + printf("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("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("certificate_data_free %d\n", rc); + certificate_data_free(data1); + certificate_data_free(data2); + certificate_data_free(data3); + certificate_data_free(data4); + certificate_store_free(store); + freerdp_settings_free(settings); + return rc; +} + +int TestKnownHosts(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + if (!test_known_hosts_file()) + return -1; + if (!test_certs_dir(FALSE)) + return -1; + if (!test_certs_dir(TRUE)) + return -1; + return 0; +} diff --git a/libfreerdp/crypto/tls.c b/libfreerdp/crypto/tls.c index 4677bb5b4..dae38c87b 100644 --- a/libfreerdp/crypto/tls.c +++ b/libfreerdp/crypto/tls.c @@ -970,34 +970,17 @@ BOOL tls_accept(rdpTls* tls, BIO* underlying, rdpSettings* settings) } if (settings->CertificateFile) - { - bio = BIO_new_file(settings->CertificateFile, "rb"); - - if (!bio) - { - WLog_ERR(TAG, "BIO_new_file failed for certificate %s", settings->CertificateFile); - return FALSE; - } - } + x509 = crypto_cert_from_pem(settings->CertificateFile, strlen(settings->CertificateFile), + TRUE); else if (settings->CertificateContent) - { - bio = BIO_new_mem_buf(settings->CertificateContent, strlen(settings->CertificateContent)); - - if (!bio) - { - WLog_ERR(TAG, "BIO_new_mem_buf failed for certificate"); - return FALSE; - } - } + x509 = crypto_cert_from_pem(settings->CertificateContent, + strlen(settings->CertificateContent), FALSE); else { WLog_ERR(TAG, "no certificate defined"); return FALSE; } - x509 = PEM_read_bio_X509(bio, NULL, NULL, 0); - BIO_free_all(bio); - if (!x509) { WLog_ERR(TAG, "invalid certificate"); @@ -1260,120 +1243,19 @@ static BOOL accept_cert(rdpTls* tls, const BYTE* pem, UINT32 length) return TRUE; } -static BOOL tls_extract_pem(CryptoCert cert, BYTE** PublicKey, DWORD* PublicKeyLength) +static BOOL tls_extract_pem(CryptoCert cert, BYTE** PublicKey, size_t* PublicKeyLength) { - BIO* bio; - int status, count, x; - size_t offset; - size_t length = 0; - BOOL rc = FALSE; - BYTE* pemCert = NULL; - - if (!PublicKey || !PublicKeyLength) + if (!cert || !PublicKey) return FALSE; - - *PublicKey = NULL; - *PublicKeyLength = 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 FALSE; - } - - status = PEM_write_bio_X509(bio, cert->px509); - - if (status < 0) - { - WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status); - goto fail; - } - - if (cert->px509chain) - { - count = sk_X509_num(cert->px509chain); - for (x = 0; x < count; x++) - { - X509* c = sk_X509_value(cert->px509chain, 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; - } - - 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; - BYTE* new_cert; - new_len = length * 2; - new_cert = (BYTE*)realloc(pemCert, new_len + 1); - - if (!new_cert) - goto fail; - - length = new_len; - pemCert = new_cert; - 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'; - *PublicKey = pemCert; - *PublicKeyLength = length; - rc = TRUE; -fail: - - if (!rc) - free(pemCert); - - BIO_free_all(bio); - return rc; + *PublicKey = crypto_cert_pem(cert->px509, cert->px509chain, PublicKeyLength); + return *PublicKey != NULL; } int tls_verify_certificate(rdpTls* tls, CryptoCert cert, const char* hostname, UINT16 port) { int match; int index; - DWORD length; + size_t length; BOOL certificate_status; char* common_name = NULL; int common_name_length = 0; @@ -1445,7 +1327,8 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, const char* hostname, U hostname = tls->settings->CertificateName; /* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */ - certificate_status = x509_verify_certificate(cert, tls->certificate_store->path); + certificate_status = + x509_verify_certificate(cert, certificate_store_get_certs_path(tls->certificate_store)); /* verify certificate name match */ certificate_data = crypto_get_certificate_data(cert->px509, hostname, port); /* extra common name and alternative names */ @@ -1486,31 +1369,17 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, const char* hostname, U * manual verification */ if (!certificate_status || !hostname_match) { - char* issuer; - char* subject; - char* fingerprint; DWORD accept_certificate = 0; - issuer = crypto_cert_issuer(cert->px509); - subject = crypto_cert_subject(cert->px509); - fingerprint = crypto_cert_fingerprint(cert->px509); + size_t length = 0; + char* issuer = crypto_cert_issuer(cert->px509); + char* subject = crypto_cert_subject(cert->px509); + char* pem = crypto_cert_pem(cert->px509, NULL, &length); + + if (!pem) + goto end; + /* search for matching entry in known_hosts file */ - match = certificate_data_match(tls->certificate_store, certificate_data); - { - int match_old = -1; - char* sha1 = crypto_cert_fingerprint_by_hash(cert->px509, "sha1"); - rdpCertificateData* certificate_data_sha1 = - certificate_data_new(hostname, port, subject, issuer, sha1); - - if (sha1 && certificate_data_sha1) - match_old = - certificate_data_match(tls->certificate_store, certificate_data_sha1); - - if (match_old == 0) - flags |= VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1; - - certificate_data_free(certificate_data_sha1); - free(sha1); - } + match = certificate_store_contains_data(tls->certificate_store, certificate_data); if (match == 1) { @@ -1545,29 +1414,42 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, const char* hostname, U } else if (instance->VerifyCertificateEx) { + const BOOL use_pem = freerdp_settings_get_bool( + instance->settings, FreeRDP_CertificateCallbackPreferPEM); + char* fp = NULL; + DWORD cflags = flags; + if (use_pem) + { + cflags |= VERIFY_CERT_FLAG_FP_IS_PEM; + fp = pem; + } + else + fp = crypto_cert_fingerprint(cert->px509); accept_certificate = instance->VerifyCertificateEx( - instance, hostname, port, common_name, subject, issuer, fingerprint, flags); + instance, hostname, port, common_name, subject, issuer, fp, cflags); + if (!use_pem) + free(fp); } else if (instance->VerifyCertificate) { + char* fp = crypto_cert_fingerprint(cert->px509); WLog_WARN(TAG, "The VerifyCertificate callback is deprecated, migrate your " "application to VerifyCertificateEx"); - accept_certificate = instance->VerifyCertificate( - instance, common_name, subject, issuer, fingerprint, !hostname_match); + accept_certificate = instance->VerifyCertificate(instance, common_name, subject, + issuer, fp, !hostname_match); + free(fp); } } else if (match == -1) { - char* old_subject = NULL; - char* old_issuer = NULL; - char* old_fingerprint = NULL; + rdpCertificateData* stored_data = + 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 */ - tls_print_certificate_error(hostname, port, fingerprint, - tls->certificate_store->file); + tls_print_certificate_error( + hostname, port, pem, certificate_store_get_hosts_file(tls->certificate_store)); - if (!certificate_get_stored_data(tls->certificate_store, certificate_data, - &old_subject, &old_issuer, &old_fingerprint)) + if (!stored_data) WLog_WARN(TAG, "Failed to get certificate entry for %s:%d", hostname, port); if (tls->settings->AutoDenyCertificate) @@ -1590,22 +1472,45 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, const char* hostname, U } else if (instance->VerifyChangedCertificateEx) { + DWORD cflags = flags | VERIFY_CERT_FLAG_CHANGED; + const char* old_subject = certificate_data_get_subject(stored_data); + const char* old_issuer = certificate_data_get_issuer(stored_data); + const char* old_fp = certificate_data_get_fingerprint(stored_data); + const char* old_pem = certificate_data_get_pem(stored_data); + char* fp; + if (old_pem && freerdp_settings_get_bool(instance->settings, + FreeRDP_CertificateCallbackPreferPEM)) + { + cflags |= VERIFY_CERT_FLAG_FP_IS_PEM; + fp = pem; + old_fp = old_pem; + } + else + { + fp = crypto_cert_fingerprint(cert->px509); + } accept_certificate = instance->VerifyChangedCertificateEx( - instance, hostname, port, common_name, subject, issuer, fingerprint, - old_subject, old_issuer, old_fingerprint, flags | VERIFY_CERT_FLAG_CHANGED); + instance, hostname, port, common_name, subject, issuer, pem, old_subject, + old_issuer, old_fp, cflags); + if (!old_pem) + free(fp); } else if (instance->VerifyChangedCertificate) { + char* fp = crypto_cert_fingerprint(cert->px509); + const char* old_subject = certificate_data_get_subject(stored_data); + const char* old_issuer = certificate_data_get_issuer(stored_data); + const char* old_fingerprint = 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, fingerprint, old_subject, - old_issuer, old_fingerprint); + instance, common_name, subject, issuer, fp, old_subject, old_issuer, + old_fingerprint); + free(fp); } - free(old_subject); - free(old_issuer); - free(old_fingerprint); + certificate_data_free(stored_data); } else if (match == 0) accept_certificate = 2; /* success! */ @@ -1616,15 +1521,9 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, const char* hostname, U case 1: /* user accepted certificate, add entry in known_hosts file */ - if (match < 0) - verification_status = - certificate_data_replace(tls->certificate_store, certificate_data) ? 1 - : -1; - else - verification_status = - certificate_data_print(tls->certificate_store, certificate_data) ? 1 - : -1; - + verification_status = + certificate_store_save_data(tls->certificate_store, certificate_data) ? 1 + : -1; break; case 2: @@ -1640,7 +1539,7 @@ int tls_verify_certificate(rdpTls* tls, CryptoCert cert, const char* hostname, U free(issuer); free(subject); - free(fingerprint); + free(pem); } if (verification_status > 0) diff --git a/winpr/libwinpr/path/shell.c b/winpr/libwinpr/path/shell.c index fc536c6ba..02f9483f7 100644 --- a/winpr/libwinpr/path/shell.c +++ b/winpr/libwinpr/path/shell.c @@ -550,7 +550,7 @@ BOOL PathMakePathW(LPCWSTR path, LPSECURITY_ATTRIBUTES lpAttributes) #endif - if (ConvertFromUnicode(CP_UTF8, 0, path, -1, &dup, 0, NULL, NULL)) + if (ConvertFromUnicode(CP_UTF8, 0, path, -1, &dup, 0, NULL, NULL) <= 0) return FALSE; #ifdef __OS2__