/** * FreeRDP: A Remote Desktop Protocol Implementation * Remote Assistance * * Copyright 2014 Marc-Andre Moreau * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../core/settings.h" #define TAG FREERDP_TAG("common") struct rdp_assistance_file { UINT32 Type; char* Username; char* LHTicket; char* RCTicket; char* PassStub; UINT32 DtStart; UINT32 DtLength; BOOL LowSpeed; BOOL RCTicketEncrypted; char* ConnectionString1; char* ConnectionString2; BYTE* EncryptedPassStub; size_t EncryptedPassStubLength; BYTE* EncryptedLHTicket; size_t EncryptedLHTicketLength; wArrayList* MachineAddresses; wArrayList* MachinePorts; wArrayList* MachineUris; char* RASessionId; char* RASpecificParams; char* RASpecificParams2; char* filename; char* password; }; static const char* strrstr(const char* haystack, size_t len, const char* needle) { if (*needle == '\0') return haystack; char* result = nullptr; for (;;) { char* p = strstr(haystack, needle); if (p == nullptr) break; if (p > haystack + len) return nullptr; result = p; haystack = p + 1; } return result; } static BOOL update_option(char** opt, const char* val, size_t len) { WINPR_ASSERT(opt); free(*opt); *opt = nullptr; if (!val && (len != 0)) return FALSE; else if (!val && (len == 0)) return TRUE; *opt = strndup(val, len); return *opt != nullptr; } static BOOL update_name(rdpAssistanceFile* file, const char* name) { WINPR_ASSERT(file); if (!name) { WLog_ERR(TAG, "ASSISTANCE file %s invalid name", name); return FALSE; } free(file->filename); file->filename = _strdup(name); return file->filename != nullptr; } static BOOL update_password(rdpAssistanceFile* file, const char* password) { WINPR_ASSERT(file); free(file->password); file->password = nullptr; if (!password) return TRUE; file->password = _strdup(password); return file->password != nullptr; } static BOOL update_connectionstring2_nocopy(rdpAssistanceFile* file, char* str) { WINPR_ASSERT(file); free(file->ConnectionString2); file->ConnectionString2 = nullptr; if (!str) return TRUE; file->ConnectionString2 = str; return file->ConnectionString2 != nullptr; } static BOOL update_connectionstring2(rdpAssistanceFile* file, const char* str, size_t len) { char* strc = nullptr; if (!str && (len != 0)) return FALSE; if (str && (len > 0)) { strc = strndup(str, len); if (!strc) return FALSE; } return update_connectionstring2_nocopy(file, strc); } static BOOL update_connectionstring2_wchar(rdpAssistanceFile* file, const WCHAR* str, size_t len) { char* strc = nullptr; if (!str && (len != 0)) return FALSE; if (str && (len > 0)) { strc = ConvertWCharNToUtf8Alloc(str, len, nullptr); if (!strc) return FALSE; } return update_connectionstring2_nocopy(file, strc); } /** * Password encryption in establishing a remote assistance session of type 1: * http://blogs.msdn.com/b/openspecification/archive/2011/10/31/password-encryption-in-establishing-a-remote-assistance-session-of-type-1.aspx * * Creation of PassStub for the Remote Assistance Ticket: * http://social.msdn.microsoft.com/Forums/en-US/6316c3f4-ea09-4343-a4a1-9cca46d70d28/creation-of-passstub-for-the-remote-assistance-ticket?forum=os_windowsprotocols */ /** * CryptDeriveKey Function: * http://msdn.microsoft.com/en-us/library/windows/desktop/aa379916/ * * Let n be the required derived key length, in bytes. * The derived key is the first n bytes of the hash value after the hash computation * has been completed by CryptDeriveKey. If the hash is not a member of the SHA-2 * family and the required key is for either 3DES or AES, the key is derived as follows: * * Form a 64-byte buffer by repeating the constant 0x36 64 times. * Let k be the length of the hash value that is represented by the input parameter hBaseData. * Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes * of the buffer with the hash value that is represented by the input parameter hBaseData. * * Form a 64-byte buffer by repeating the constant 0x5C 64 times. * Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes * of the buffer with the hash value that is represented by the input parameter hBaseData. * * Hash the result of step 1 by using the same hash algorithm as that used to compute the hash * value that is represented by the hBaseData parameter. * * Hash the result of step 2 by using the same hash algorithm as that used to compute the hash * value that is represented by the hBaseData parameter. * * Concatenate the result of step 3 with the result of step 4. * Use the first n bytes of the result of step 5 as the derived key. */ static BOOL freerdp_assistance_crypt_derive_key_sha1(const BYTE* hash, size_t hashLength, BYTE* key, size_t keyLength) { BOOL rc = FALSE; BYTE pad1[64] = WINPR_C_ARRAY_INIT; BYTE pad2[64] = WINPR_C_ARRAY_INIT; if (hashLength == 0) return FALSE; memset(pad1, 0x36, sizeof(pad1)); memset(pad2, 0x5C, sizeof(pad2)); for (size_t i = 0; i < hashLength; i++) { pad1[i] ^= hash[i]; pad2[i] ^= hash[i]; } BYTE* buffer = (BYTE*)calloc(hashLength, 2); if (!buffer) goto fail; if (!winpr_Digest(WINPR_MD_SHA1, pad1, 64, buffer, hashLength)) goto fail; if (!winpr_Digest(WINPR_MD_SHA1, pad2, 64, &buffer[hashLength], hashLength)) goto fail; CopyMemory(key, buffer, keyLength); rc = TRUE; fail: free(buffer); return rc; } static BOOL append_address_to_list(wArrayList* MachineAddresses, const char* str, size_t len) { char* copy = nullptr; if (len > 0) copy = strndup(str, len); if (!copy) return FALSE; const BOOL rc = ArrayList_Append(MachineAddresses, copy); if (!rc) free(copy); // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append takes ownership of copy return rc; } static BOOL append_address(rdpAssistanceFile* file, const char* host, const char* port) { WINPR_ASSERT(file); errno = 0; unsigned long p = strtoul(port, nullptr, 0); if ((errno != 0) || (p == 0) || (p > UINT16_MAX)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid port value %s", port); return FALSE; } if (!append_address_to_list(file->MachineAddresses, host, host ? strlen(host) : 0)) return FALSE; return ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p); } static BOOL freerdp_assistance_parse_address_list(rdpAssistanceFile* file, char* list) { WINPR_ASSERT(file); WLog_DBG(TAG, "freerdp_assistance_parse_address_list list=%s", list); BOOL rc = FALSE; if (!list) return FALSE; char* strp = list; char* s = ";"; // get the first token char* saveptr = nullptr; char* token = strtok_s(strp, s, &saveptr); // walk through other tokens while (token != nullptr) { char* port = strchr(token, ':'); if (!port) goto out; *port = '\0'; port++; if (!append_address(file, token, port)) goto out; token = strtok_s(nullptr, s, &saveptr); } rc = TRUE; out: return rc; } static BOOL freerdp_assistance_parse_connection_string1(rdpAssistanceFile* file) { char* tokens[8] = WINPR_C_ARRAY_INIT; BOOL rc = FALSE; WINPR_ASSERT(file); if (!file->RCTicket) return FALSE; /** * ,,,, * ,,, */ char* str = _strdup(file->RCTicket); if (!str) goto error; { const size_t length = strlen(str); { int count = 1; for (size_t i = 0; i < length; i++) { if (str[i] == ',') count++; } if (count != 8) goto error; } { size_t count = 0; tokens[count++] = str; for (size_t i = 0; i < length; i++) { if (str[i] == ',') { str[i] = '\0'; tokens[count++] = &str[i + 1]; } } } } if (strcmp(tokens[0], "65538") != 0) goto error; if (strcmp(tokens[1], "1") != 0) goto error; if (strcmp(tokens[3], "*") != 0) goto error; if (strcmp(tokens[5], "*") != 0) goto error; if (strcmp(tokens[6], "*") != 0) goto error; file->RASessionId = _strdup(tokens[4]); if (!file->RASessionId) goto error; file->RASpecificParams = _strdup(tokens[7]); if (!file->RASpecificParams) goto error; if (!freerdp_assistance_parse_address_list(file, tokens[2])) goto error; rc = TRUE; error: free(str); return rc; } /** * Decrypted Connection String 2: * * * * * * */ static BOOL freerdp_assistance_parse_attr(const char** opt, size_t* plength, const char* key, const char* tag) { WINPR_ASSERT(opt); WINPR_ASSERT(plength); WINPR_ASSERT(key); *opt = nullptr; *plength = 0; if (!tag) return FALSE; char bkey[128] = WINPR_C_ARRAY_INIT; const int rc = _snprintf(bkey, sizeof(bkey), "%s=\"", key); WINPR_ASSERT(rc > 0); WINPR_ASSERT((size_t)rc < sizeof(bkey)); if ((rc <= 0) || ((size_t)rc >= sizeof(bkey))) return FALSE; char* p = strstr(tag, bkey); if (!p) return TRUE; p += strlen(bkey); char* q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field '%s=%s'", key, p); return FALSE; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field " "order for '%s'", key); return FALSE; } const size_t length = WINPR_ASSERTING_INT_CAST(size_t, q - p); *opt = p; *plength = length; return TRUE; } static BOOL freerdp_assistance_parse_attr_str(char** opt, const char* key, const char* tag) { const char* copt = nullptr; size_t size = 0; if (!freerdp_assistance_parse_attr(&copt, &size, key, tag)) return FALSE; return update_option(opt, copt, size); } static BOOL freerdp_assistance_parse_attr_bool(BOOL* opt, const char* key, const char* tag) { const char* copt = nullptr; size_t size = 0; WINPR_ASSERT(opt); *opt = FALSE; if (!freerdp_assistance_parse_attr(&copt, &size, key, tag)) return FALSE; if (size != 1) return TRUE; *opt = (copt[0] == '1'); return TRUE; } static BOOL freerdp_assistance_parse_attr_uint32(UINT32* opt, const char* key, const char* tag) { const char* copt = nullptr; size_t size = 0; WINPR_ASSERT(opt); *opt = 0; if (!freerdp_assistance_parse_attr(&copt, &size, key, tag)) return FALSE; char buffer[64] = WINPR_C_ARRAY_INIT; if ((!copt && (size > 0)) || (size >= sizeof(buffer))) { WLog_WARN(TAG, "Invalid UINT32 string '%s' [%" PRIuz "]", copt, size); return FALSE; } if (size > 0) strncpy(buffer, copt, size); errno = 0; unsigned long val = strtoul(buffer, nullptr, 0); if ((errno != 0) || (val > UINT32_MAX)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid value %s", buffer); return FALSE; } *opt = (UINT32)val; return TRUE; } static char* freerdp_assistance_contains_element(char* input, size_t ilen, const char* key, size_t* plen, char** pdata, size_t* pdlen) { WINPR_ASSERT(input); WINPR_ASSERT(key); WINPR_ASSERT(plen); char bkey[128] = WINPR_C_ARRAY_INIT; const int rc = _snprintf(bkey, sizeof(bkey), "<%s", key); WINPR_ASSERT(rc > 0); WINPR_ASSERT((size_t)rc < sizeof(bkey)); if ((rc < 0) || ((size_t)rc >= sizeof(bkey))) return nullptr; char* tag = strstr(input, bkey); if (!tag || (tag > input + ilen)) return nullptr; char* data = tag + strnlen(bkey, sizeof(bkey)); /* Ensure there is a valid delimiter following our token */ switch (data[0]) { case '>': case '/': case ' ': case '\t': break; default: WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 missing delimiter after " "field %s", bkey); return nullptr; } char* start = strstr(tag, ">"); if (!start || (start > input + ilen)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 missing field %s", bkey); return nullptr; } const char* end = start; const char* dend = start - 1; if (*dend != '/') { char ekey[128] = WINPR_C_ARRAY_INIT; const int erc = _snprintf(ekey, sizeof(ekey), "", key); WINPR_ASSERT(erc > 0); WINPR_ASSERT((size_t)erc < sizeof(ekey)); if ((erc <= 0) || ((size_t)erc >= sizeof(ekey))) return nullptr; const size_t offset = WINPR_ASSERTING_INT_CAST(size_t, start - tag); dend = end = strrstr(start, ilen - offset, ekey); if (end) end += strnlen(ekey, sizeof(ekey)); } if (!end) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 missing end tag for field %s", key); return nullptr; } if (plen) *plen = WINPR_ASSERTING_INT_CAST(size_t, end - tag); if (pdata) *pdata = data; if (pdlen) *pdlen = WINPR_ASSERTING_INT_CAST(size_t, dend - data); return tag; } /**! \brief this function returns a XML element identified by \b key * The input string will be manipulated, so that the element found is '\0' terminated. * * This function can not find multiple elements on the same level as the input string is changed! */ static BOOL freerdp_assistance_consume_input_and_get_element(char* input, const char* key, char** element, size_t* elen) { WINPR_ASSERT(input); WINPR_ASSERT(key); WINPR_ASSERT(element); WINPR_ASSERT(elen); size_t len = 0; size_t dlen = 0; char* data = nullptr; char* tag = freerdp_assistance_contains_element(input, strlen(input), key, &len, &data, &dlen); if (!tag) return FALSE; char* end = data + dlen; *tag = '\0'; *end = '\0'; *element = data; *elen = dlen + 1; return TRUE; } static BOOL freerdp_assistance_get_element(char* input, size_t ilen, const char* key, char** element, size_t* elen) { WINPR_ASSERT(input); WINPR_ASSERT(key); WINPR_ASSERT(element); WINPR_ASSERT(elen); size_t len = 0; size_t dlen = 0; char* data = nullptr; char* tag = freerdp_assistance_contains_element(input, ilen, key, &len, &data, &dlen); if (!tag) return FALSE; if (tag + len > input + ilen) return FALSE; char* end = tag + len; *element = data; *elen = WINPR_ASSERTING_INT_CAST(size_t, end - data + 1); return TRUE; } static BOOL freerdp_assistance_parse_all_elements_of(rdpAssistanceFile* file, char* data, size_t len, const char* key, BOOL (*fkt)(rdpAssistanceFile* file, char* data, size_t len)) { char* val = nullptr; size_t vlen = 0; while (freerdp_assistance_get_element(data, len, key, &val, &vlen)) { data = val + vlen; len = strnlen(data, len); if (vlen > 0) { val[vlen - 1] = '\0'; if (!fkt(file, val, vlen)) return FALSE; } } return TRUE; } static BOOL freerdp_assistance_parse_all_elements_of_l(rdpAssistanceFile* file, char* data, WINPR_ATTR_UNUSED size_t len) { UINT32 p = 0; const char* n = nullptr; const char* u = nullptr; size_t nlen = 0; size_t ulen = 0; if (!freerdp_assistance_parse_attr_uint32(&p, "P", data)) return FALSE; if (!freerdp_assistance_parse_attr(&n, &nlen, "N", data)) return FALSE; if (!freerdp_assistance_parse_attr(&u, &ulen, "U", data)) return FALSE; if (n && (nlen > 0)) { if (!append_address_to_list(file->MachineAddresses, n, nlen)) return FALSE; if (!ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p)) return FALSE; } if (u && (ulen > 0)) { if (!append_address_to_list(file->MachineAddresses, u, ulen)) return FALSE; if (!ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p)) return FALSE; } return TRUE; } static BOOL freerdp_assistance_parse_all_elements_of_t(rdpAssistanceFile* file, char* data, size_t len) { UINT32 id = 0; UINT32 sid = 0; if (!freerdp_assistance_parse_attr_uint32(&id, "ID", data)) return FALSE; if (!freerdp_assistance_parse_attr_uint32(&sid, "SID", data)) return FALSE; WLog_DBG(TAG, "transport id=%" PRIu32 ", sid=%" PRIu32, id, sid); return freerdp_assistance_parse_all_elements_of(file, data, len, "L", freerdp_assistance_parse_all_elements_of_l); } static BOOL freerdp_assistance_parse_all_elements_of_c(rdpAssistanceFile* file, char* data, size_t len) { return freerdp_assistance_parse_all_elements_of(file, data, len, "T", freerdp_assistance_parse_all_elements_of_t); } static BOOL freerdp_assistance_parse_find_elements_of_c(rdpAssistanceFile* file, char* data, size_t len) { return freerdp_assistance_parse_all_elements_of(file, data, len, "C", freerdp_assistance_parse_all_elements_of_c); } static BOOL freerdp_assistance_parse_connection_string2(rdpAssistanceFile* file) { BOOL rc = FALSE; WINPR_ASSERT(file); if (!file->ConnectionString2) return FALSE; char* str = _strdup(file->ConnectionString2); if (!str) goto out_fail; { char* e = nullptr; size_t elen = 0; if (!freerdp_assistance_consume_input_and_get_element(str, "E", &e, &elen)) goto out_fail; if (!e || (elen == 0)) goto out_fail; { char* a = nullptr; size_t alen = 0; if (!freerdp_assistance_get_element(e, elen, "A", &a, &alen)) goto out_fail; if (!a || (alen == 0)) goto out_fail; if (!freerdp_assistance_parse_find_elements_of_c(file, e, elen)) goto out_fail; /* '\0' terminate the detected XML elements so * the parser can continue with terminated strings */ a[alen] = '\0'; if (!freerdp_assistance_parse_attr_str(&file->RASpecificParams, "KH", a)) goto out_fail; if (!freerdp_assistance_parse_attr_str(&file->RASpecificParams2, "KH2", a)) goto out_fail; if (!freerdp_assistance_parse_attr_str(&file->RASessionId, "ID", a)) goto out_fail; } } rc = TRUE; out_fail: free(str); return rc; } char* freerdp_assistance_construct_expert_blob(const char* name, const char* pass) { if (!name || !pass) return nullptr; const size_t nameLength = strlen(name) + strlen("NAME="); const size_t passLength = strlen(pass) + strlen("PASS="); const size_t size = nameLength + passLength + 64; char* ExpertBlob = (char*)calloc(1, size); if (!ExpertBlob) return nullptr; (void)sprintf_s(ExpertBlob, size, "%" PRIuz ";NAME=%s%" PRIuz ";PASS=%s", nameLength, name, passLength, pass); return ExpertBlob; } char* freerdp_assistance_generate_pass_stub(WINPR_ATTR_UNUSED DWORD flags) { UINT32 nums[14] = WINPR_C_ARRAY_INIT; const char set1[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '_' }; const char set2[12] = { '!', '@', '#', '$', '&', '^', '*', '(', ')', '-', '+', '=' }; const char set3[10] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; const char set4[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; const char set5[26] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; char* passStub = calloc(15, sizeof(char)); if (!passStub) return nullptr; /** * PassStub generation: * * Characters 0 and 5-13 are from the set A-Z a-z 0-9 * _ * Character 1 is from the set !@#$&^*()-+= * Character 2 is from the set 0-9 * Character 3 is from the set A-Z * Character 4 is from the set a-z * * Example: WB^6HsrIaFmEpi */ if (winpr_RAND(nums, sizeof(nums)) < 0) { free(passStub); return nullptr; } passStub[0] = set1[nums[0] % sizeof(set1)]; /* character 0 */ passStub[1] = set2[nums[1] % sizeof(set2)]; /* character 1 */ passStub[2] = set3[nums[2] % sizeof(set3)]; /* character 2 */ passStub[3] = set4[nums[3] % sizeof(set4)]; /* character 3 */ passStub[4] = set5[nums[4] % sizeof(set5)]; /* character 4 */ for (size_t x = 5; x < ARRAYSIZE(nums); x++) passStub[x] = set1[nums[x] % sizeof(set1)]; /* character 5 - 13 */ return passStub; } BYTE* freerdp_assistance_encrypt_pass_stub(const char* password, const char* passStub, size_t* pEncryptedSize) { BOOL rc = 0; size_t cbPasswordW = 0; size_t cbPassStubW = 0; BYTE PasswordHash[WINPR_MD5_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT; WINPR_RC4_CTX* rc4Ctx = nullptr; BYTE* pbIn = nullptr; BYTE* pbOut = nullptr; BYTE* res = nullptr; WCHAR* PasswordW = ConvertUtf8ToWCharAlloc(password, &cbPasswordW); WCHAR* PassStubW = ConvertUtf8ToWCharAlloc(passStub, &cbPassStubW); cbPasswordW = cbPasswordW * sizeof(WCHAR); cbPassStubW = cbPassStubW * sizeof(WCHAR); const size_t EncryptedSize = cbPassStubW + 4; if (!PasswordW || !PassStubW) goto fail; if (!winpr_Digest(WINPR_MD_MD5, (BYTE*)PasswordW, cbPasswordW, (BYTE*)PasswordHash, sizeof(PasswordHash))) goto fail; pbIn = (BYTE*)calloc(1, EncryptedSize); pbOut = (BYTE*)calloc(1, EncryptedSize); if (!pbIn || !pbOut) goto fail; WINPR_ASSERT(cbPasswordW <= UINT32_MAX); winpr_Data_Write_UINT32(pbIn, (UINT32)cbPassStubW); CopyMemory(&pbIn[4], PassStubW, cbPassStubW); rc4Ctx = winpr_RC4_New(PasswordHash, sizeof(PasswordHash)); if (!rc4Ctx) { WLog_ERR(TAG, "winpr_Cipher_New failure"); goto fail; } rc = winpr_RC4_Update(rc4Ctx, EncryptedSize, pbIn, pbOut); if (!rc) { WLog_ERR(TAG, "winpr_Cipher_Update failure"); goto fail; } res = pbOut; fail: winpr_RC4_Free(rc4Ctx); free(PasswordW); free(PassStubW); free(pbIn); if (!res) free(pbOut); else *pEncryptedSize = EncryptedSize; return res; } static BOOL freerdp_assistance_decrypt2(rdpAssistanceFile* file) { BOOL rc = FALSE; int status = 0; size_t cbPasswordW = 0; size_t cchOutW = 0; WINPR_CIPHER_CTX* aesDec = nullptr; WCHAR* PasswordW = nullptr; BYTE* pbIn = nullptr; BYTE* pbOut = nullptr; size_t cbOut = 0; size_t cbIn = 0; size_t cbFinal = 0; BYTE DerivedKey[WINPR_AES_BLOCK_SIZE] = WINPR_C_ARRAY_INIT; BYTE InitializationVector[WINPR_AES_BLOCK_SIZE] = WINPR_C_ARRAY_INIT; BYTE PasswordHash[WINPR_SHA1_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT; WINPR_ASSERT(file); if (!file->password) return FALSE; PasswordW = ConvertUtf8ToWCharAlloc(file->password, &cbPasswordW); if (!PasswordW) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Conversion from UCS2 to UTF8 failed"); return FALSE; } cbPasswordW = cbPasswordW * sizeof(WCHAR); if (!winpr_Digest(WINPR_MD_SHA1, (BYTE*)PasswordW, cbPasswordW, PasswordHash, sizeof(PasswordHash))) goto fail; if (!freerdp_assistance_crypt_derive_key_sha1(PasswordHash, sizeof(PasswordHash), DerivedKey, sizeof(DerivedKey))) goto fail; aesDec = winpr_Cipher_NewEx(WINPR_CIPHER_AES_128_CBC, WINPR_DECRYPT, DerivedKey, sizeof(DerivedKey), InitializationVector, sizeof(InitializationVector)); if (!aesDec) goto fail; cbOut = cbFinal = 0; cbIn = file->EncryptedLHTicketLength; pbIn = file->EncryptedLHTicket; pbOut = (BYTE*)calloc(1, cbIn + WINPR_AES_BLOCK_SIZE + 2); if (!pbOut) goto fail; if (!winpr_Cipher_Update(aesDec, pbIn, cbIn, pbOut, &cbOut)) goto fail; if (!winpr_Cipher_Final(aesDec, pbOut + cbOut, &cbFinal)) { WLog_ERR(TAG, "winpr_Cipher_Final failure"); goto fail; } cbOut += cbFinal; cbFinal = 0; union { const WCHAR* wc; const BYTE* b; } cnv; cnv.b = pbOut; cchOutW = cbOut / sizeof(WCHAR); if (!update_connectionstring2_wchar(file, cnv.wc, cchOutW)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Conversion from UCS2 to UTF8 failed"); goto fail; } if (!freerdp_assistance_parse_connection_string2(file)) goto fail; rc = TRUE; fail: winpr_Cipher_Free(aesDec); free(PasswordW); free(pbOut); WLog_DBG(TAG, "freerdp_assistance_parse_connection_string2: %d", status); return rc; } BYTE* freerdp_assistance_hex_string_to_bin(const void* str, size_t* size) { BYTE* buffer = nullptr; if (!str || !size) return nullptr; *size = 0; const size_t length = strlen(str); buffer = calloc(length, sizeof(BYTE)); if (!buffer) return nullptr; const size_t rc = winpr_HexStringToBinBuffer(str, length, buffer, length); if (rc == 0) { free(buffer); return nullptr; } *size = rc; return buffer; } char* freerdp_assistance_bin_to_hex_string(const void* data, size_t size) { return winpr_BinToHexString(data, size, FALSE); } static int freerdp_assistance_parse_uploadinfo(rdpAssistanceFile* file, char* uploadinfo, size_t uploadinfosize) { const char escalated[9] = { 'E', 's', 'c', 'a', 'l', 'a', 't', 'e', 'd' }; const size_t esclen = sizeof(escalated); const char* typestr = nullptr; size_t typelen = 0; if (!uploadinfo || (uploadinfosize == 0)) return -1; if (strnlen(uploadinfo, uploadinfosize) == uploadinfosize) { WLog_WARN(TAG, "UPLOADINFOR string is not '\\0' terminated"); return -1; } if (!freerdp_assistance_parse_attr(&typestr, &typelen, "TYPE", uploadinfo)) return -1; if ((typelen != esclen) || (strncmp(typestr, escalated, esclen) != 0)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Missing or invalid UPLOADINFO TYPE '%s' [%" PRIuz "]", typestr, typelen); return -1; } char* uploaddata = nullptr; size_t uploaddatasize = 0; if (!freerdp_assistance_consume_input_and_get_element(uploadinfo, "UPLOADDATA", &uploaddata, &uploaddatasize)) return -1; /* Parse USERNAME */ if (!freerdp_assistance_parse_attr_str(&file->Username, "USERNAME", uploaddata)) return -1; /* Parse LHTICKET */ if (!freerdp_assistance_parse_attr_str(&file->LHTicket, "LHTICKET", uploaddata)) return -1; /* Parse RCTICKET */ if (!freerdp_assistance_parse_attr_str(&file->RCTicket, "RCTICKET", uploaddata)) return -1; /* Parse RCTICKETENCRYPTED */ if (!freerdp_assistance_parse_attr_bool(&file->RCTicketEncrypted, "RCTICKETENCRYPTED", uploaddata)) return -1; /* Parse PassStub */ if (!freerdp_assistance_parse_attr_str(&file->PassStub, "PassStub", uploaddata)) return -1; if (file->PassStub) { const char* amp = "&"; char* passtub = strstr(file->PassStub, amp); while (passtub) { const char* end = passtub + 5; const size_t len = strlen(end); memmove(&passtub[1], end, len + 1); passtub = strstr(passtub, amp); } } /* Parse DtStart */ if (!freerdp_assistance_parse_attr_uint32(&file->DtStart, "DtStart", uploaddata)) return -1; /* Parse DtLength */ if (!freerdp_assistance_parse_attr_uint32(&file->DtLength, "DtLength", uploaddata)) return -1; /* Parse L (LowSpeed) */ if (!freerdp_assistance_parse_attr_bool(&file->LowSpeed, "L", uploaddata)) return -1; file->Type = (file->LHTicket) ? 2 : 1; int status = 0; switch (file->Type) { case 2: { file->EncryptedLHTicket = freerdp_assistance_hex_string_to_bin( file->LHTicket, &file->EncryptedLHTicketLength); if (!freerdp_assistance_decrypt2(file)) status = -1; } break; case 1: { if (!freerdp_assistance_parse_connection_string1(file)) status = -1; } break; default: return -1; } if (status < 0) { WLog_ERR(TAG, "freerdp_assistance_parse_connection_string1 failure: %d", status); return -1; } file->EncryptedPassStub = freerdp_assistance_encrypt_pass_stub(file->password, file->PassStub, &file->EncryptedPassStubLength); if (!file->EncryptedPassStub) return -1; return 1; } static int freerdp_assistance_parse_file_buffer_int(rdpAssistanceFile* file, char* buffer, size_t size, const char* password) { WINPR_ASSERT(file); WINPR_ASSERT(buffer); WINPR_ASSERT(size > 0); if (!update_password(file, password)) return -1; char* uploadinfo = nullptr; size_t uploadinfosize = 0; if (freerdp_assistance_consume_input_and_get_element(buffer, "UPLOADINFO", &uploadinfo, &uploadinfosize)) return freerdp_assistance_parse_uploadinfo(file, uploadinfo, uploadinfosize); size_t elen = 0; const char* estr = freerdp_assistance_contains_element(buffer, size, "E", &elen, nullptr, nullptr); if (!estr || (elen == 0)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Neither UPLOADINFO nor found"); return -1; } if (!update_connectionstring2(file, estr, elen)) return -1; if (!freerdp_assistance_parse_connection_string2(file)) return -1; return 1; } int freerdp_assistance_parse_file_buffer(rdpAssistanceFile* file, const char* cbuffer, size_t size, const char* password) { WINPR_ASSERT(file); if (!password) { WLog_WARN(TAG, "empty password supplied"); } if (!cbuffer || (size == 0)) { WLog_WARN(TAG, "no data supplied [%p, %" PRIuz "]", WINPR_CXX_COMPAT_CAST(const void*, cbuffer), size); return -1; } char* abuffer = strndup(cbuffer, size); const size_t len = strnlen(cbuffer, size); if (len == size) WLog_WARN(TAG, "Input data not '\\0' terminated"); if (!abuffer) return -1; const int rc = freerdp_assistance_parse_file_buffer_int(file, abuffer, len + 1, password); free(abuffer); return rc; } int freerdp_assistance_parse_file(rdpAssistanceFile* file, const char* name, const char* password) { int status = 0; BYTE* buffer = nullptr; FILE* fp = nullptr; size_t readSize = 0; union { INT64 i64; size_t s; } fileSize; if (!update_name(file, name)) return -1; fp = winpr_fopen(name, "r"); if (!fp) { WLog_ERR(TAG, "Failed to open ASSISTANCE file %s ", name); return -1; } (void)_fseeki64(fp, 0, SEEK_END); fileSize.i64 = _ftelli64(fp); (void)_fseeki64(fp, 0, SEEK_SET); if (fileSize.i64 < 1) { WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name); (void)fclose(fp); return -1; } buffer = (BYTE*)malloc(fileSize.s + 2); if (!buffer) { (void)fclose(fp); return -1; } readSize = fread(buffer, fileSize.s, 1, fp); if (!readSize) { if (!ferror(fp)) readSize = fileSize.s; } (void)fclose(fp); if (readSize < 1) { WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name); free(buffer); buffer = nullptr; return -1; } buffer[fileSize.s] = '\0'; buffer[fileSize.s + 1] = '\0'; status = freerdp_assistance_parse_file_buffer(file, (char*)buffer, fileSize.s, password); free(buffer); return status; } BOOL freerdp_assistance_populate_settings_from_assistance_file(rdpAssistanceFile* file, rdpSettings* settings) { if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceMode, TRUE)) return FALSE; if (!file->RASessionId || !file->MachineAddresses) return FALSE; if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceSessionId, file->RASessionId)) return FALSE; if (file->RCTicket) { if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceRCTicket, file->RCTicket)) return FALSE; } else { if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceRCTicket, file->ConnectionString2)) return FALSE; } if (file->PassStub) { if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistancePassStub, file->PassStub)) return FALSE; } if (ArrayList_Count(file->MachineAddresses) < 1) return FALSE; const char* addr = ArrayList_GetItem(file->MachineAddresses, 0); if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, addr)) return FALSE; if (!freerdp_settings_set_string(settings, FreeRDP_AssistanceFile, file->filename)) return FALSE; if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistancePassword, file->password)) return FALSE; if (file->Username) { if (!freerdp_settings_set_string(settings, FreeRDP_Username, file->Username)) return FALSE; } if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceMode, TRUE)) return FALSE; const size_t ports = ArrayList_Count(file->MachinePorts); const size_t addresses = ArrayList_Count(file->MachineAddresses); if (ports < 1) return FALSE; if (ports != addresses) return FALSE; union { uintptr_t port; void* data; } cnv; cnv.data = ArrayList_GetItem(file->MachinePorts, 0); WINPR_ASSERT(cnv.port <= UINT32_MAX); if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)cnv.port)) return FALSE; if (!freerdp_target_net_adresses_reset(settings, ports)) return FALSE; for (size_t x = 0; x < ports; x++) { cnv.data = ArrayList_GetItem(file->MachinePorts, x); WINPR_ASSERT(cnv.port <= UINT32_MAX); const UINT32 port = (UINT32)cnv.port; if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetPorts, x, &port)) return FALSE; } for (size_t i = 0; i < addresses; i++) { const char* maddr = ArrayList_GetItem(file->MachineAddresses, i); if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses, i, maddr)) return FALSE; } return TRUE; } static BOOL setup_string(wArrayList* list) { WINPR_ASSERT(list); wObject* obj = ArrayList_Object(list); if (!obj) return FALSE; obj->fnObjectFree = free; // obj->fnObjectNew = wwinpr_ObjectStringClone; return TRUE; } rdpAssistanceFile* freerdp_assistance_file_new(void) { winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT); rdpAssistanceFile* file = calloc(1, sizeof(rdpAssistanceFile)); if (!file) return nullptr; file->MachineAddresses = ArrayList_New(FALSE); file->MachinePorts = ArrayList_New(FALSE); file->MachineUris = ArrayList_New(FALSE); if (!file->MachineAddresses || !file->MachinePorts || !file->MachineUris) goto fail; if (!setup_string(file->MachineAddresses) || !setup_string(file->MachineUris)) goto fail; return file; fail: WINPR_PRAGMA_DIAG_PUSH WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC freerdp_assistance_file_free(file); WINPR_PRAGMA_DIAG_POP return nullptr; } void freerdp_assistance_file_free(rdpAssistanceFile* file) { if (!file) return; update_password(file, nullptr); update_connectionstring2(file, nullptr, 0); free(file->filename); free(file->Username); free(file->LHTicket); free(file->RCTicket); free(file->PassStub); free(file->ConnectionString1); free(file->EncryptedLHTicket); free(file->RASessionId); free(file->RASpecificParams); free(file->RASpecificParams2); free(file->EncryptedPassStub); ArrayList_Free(file->MachineAddresses); ArrayList_Free(file->MachinePorts); ArrayList_Free(file->MachineUris); free(file); } void freerdp_assistance_print_file(rdpAssistanceFile* file, wLog* log, DWORD level) { WINPR_ASSERT(file); WLog_Print(log, level, "Username: %s", file->Username); WLog_Print(log, level, "LHTicket: %s", file->LHTicket); WLog_Print(log, level, "RCTicket: %s", file->RCTicket); WLog_Print(log, level, "RCTicketEncrypted: %" PRId32, file->RCTicketEncrypted); WLog_Print(log, level, "PassStub: %s", file->PassStub); WLog_Print(log, level, "DtStart: %" PRIu32, file->DtStart); WLog_Print(log, level, "DtLength: %" PRIu32, file->DtLength); WLog_Print(log, level, "LowSpeed: %" PRId32, file->LowSpeed); WLog_Print(log, level, "RASessionId: %s", file->RASessionId); WLog_Print(log, level, "RASpecificParams: %s", file->RASpecificParams); WLog_Print(log, level, "RASpecificParams2: %s", file->RASpecificParams2); for (size_t x = 0; x < ArrayList_Count(file->MachineAddresses); x++) { UINT32 port = 0; const char* uri = nullptr; const char* addr = ArrayList_GetItem(file->MachineAddresses, x); if (x < ArrayList_Count(file->MachinePorts)) { union { uintptr_t port; void* data; } cnv; cnv.data = ArrayList_GetItem(file->MachinePorts, x); WINPR_ASSERT(cnv.port <= UINT32_MAX); port = (UINT32)cnv.port; } if (x < ArrayList_Count(file->MachineUris)) uri = ArrayList_GetItem(file->MachineUris, x); WLog_Print(log, level, "MachineAddress [%" PRIuz ": %s", x, addr); WLog_Print(log, level, "MachinePort [%" PRIuz ": %" PRIu32, x, port); WLog_Print(log, level, "MachineURI [%" PRIuz ": %s", x, uri); } } BOOL freerdp_assistance_get_encrypted_pass_stub(rdpAssistanceFile* file, const char** pwd, size_t* size) { if (!file || !pwd || !size) return FALSE; *pwd = (const char*)file->EncryptedPassStub; *size = file->EncryptedPassStubLength; return TRUE; } int freerdp_assistance_set_connection_string2(rdpAssistanceFile* file, const char* string, const char* password) { if (!file || !string || !password) return -1; char* str = _strdup(string); if (!str) return -1; if (!update_connectionstring2_nocopy(file, str)) return -1; if (!update_password(file, password)) return -1; return freerdp_assistance_parse_connection_string2(file); }