/** * 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 #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; UINT32 MachineCount; char** MachineAddresses; UINT32* MachinePorts; char* RASessionId; char* RASpecificParams; char* filename; char* password; }; /** * 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(BYTE* hash, size_t hashLength, BYTE* key, size_t keyLength) { BOOL rc = FALSE; size_t i; BYTE* buffer; BYTE pad1[64]; BYTE pad2[64]; memset(pad1, 0x36, 64); memset(pad2, 0x5C, 64); for (i = 0; i < hashLength; i++) { pad1[i] ^= hash[i]; pad2[i] ^= hash[i]; } 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 reallocate(rdpAssistanceFile* file, const char* host, UINT32 port) { void *tmp1, *tmp2; file->MachineCount++; tmp1 = realloc(file->MachinePorts, sizeof(UINT32) * file->MachineCount); tmp2 = realloc(file->MachineAddresses, sizeof(char*) * file->MachineCount); if (!tmp1 || !tmp2) { free(tmp1); free(tmp2); return FALSE; } file->MachinePorts = tmp1; file->MachineAddresses = tmp2; file->MachinePorts[file->MachineCount - 1] = port; file->MachineAddresses[file->MachineCount - 1] = _strdup(host); return TRUE; } static BOOL append_address(rdpAssistanceFile* file, const char* host, const char* port) { unsigned long p; errno = 0; p = strtoul(port, NULL, 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; } return reallocate(file, host, (UINT16)p); } static BOOL freerdp_assistance_parse_address_list(rdpAssistanceFile* file, char* list) { WLog_DBG(TAG, "freerdp_assistance_parse_address_list list=%s", list); BOOL rc = FALSE; if (!file || !list) return FALSE; char* strp = list; char* s = ";"; char* token; // get the first token token = strtok(strp, s); // walk through other tokens while (token != NULL) { char* port = strchr(token, ':'); *port = '\0'; port++; if (!append_address(file, token, port)) goto out; token = strtok(NULL, s); } rc = TRUE; out: return rc; } static BOOL freerdp_assistance_parse_connection_string1(rdpAssistanceFile* file) { size_t i; char* str; int count; size_t length; char* tokens[8]; BOOL rc = FALSE; if (!file || !file->RCTicket) return FALSE; /** * ,,,, * ,,, */ count = 1; str = _strdup(file->RCTicket); if (!str) goto error; length = strlen(str); for (i = 0; i < length; i++) { if (str[i] == ',') count++; } if (count != 8) goto error; count = 0; tokens[count++] = str; for (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_connection_string2(rdpAssistanceFile* file) { char* str; char* tag; char* end; char* p; BOOL rc = FALSE; if (!file || !file->ConnectionString2) return FALSE; str = file->ConnectionString2; if (!strstr(str, "")) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 missing field "); return FALSE; } if (!strstr(str, "")) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 missing field "); return FALSE; } str = _strdup(file->ConnectionString2); if (!str) goto out_fail; if (!(tag = strstr(str, ") */ end = strstr(tag, "/>"); if (!end) goto out_fail; *end = '\0'; p = strstr(tag, "KH=\""); if (p) { char* q; size_t length; p += sizeof("KH=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field KH=%s", q); goto out_fail; } if (p > q) { WLog_ERR( TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field order for KH"); goto out_fail; } length = q - p; free(file->RASpecificParams); file->RASpecificParams = (char*)malloc(length + 1); if (!file->RASpecificParams) goto out_fail; CopyMemory(file->RASpecificParams, p, length); file->RASpecificParams[length] = '\0'; } p = strstr(tag, "ID=\""); if (p) { char* q; size_t length; p += sizeof("ID=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field ID=%s", q); goto out_fail; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field " "order for ID"); goto out_fail; } length = q - p; free(file->RASessionId); file->RASessionId = (char*)malloc(length + 1); if (!file->RASessionId) goto out_fail; CopyMemory(file->RASessionId, p, length); file->RASessionId[length] = '\0'; } *end = '/'; /* Parse 6) { if (!append_address(file, p, port)) goto out_fail; } p = strstr(q, "EncryptedLHTicketLength; pbIn = (BYTE*)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; pbOutW = (WCHAR*)pbOut; if (cbOut > INT_MAX / 2) goto fail; cchOutW = (int)cbOut / 2; file->ConnectionString2 = NULL; status = ConvertFromUnicode(CP_UTF8, 0, pbOutW, cchOutW, &file->ConnectionString2, 0, NULL, NULL); if (status <= 0) { 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* raw, size_t* size) { BYTE* buffer = NULL; size_t length, rc; if (!raw || !size) return NULL; *size = 0; length = strlen(raw); buffer = calloc(length, sizeof(BYTE)); if (!buffer) return NULL; rc = winpr_HexStringToBinBuffer(raw, length, buffer, length); if (rc == 0) { free(buffer); return NULL; } *size = rc; return buffer; } char* freerdp_assistance_bin_to_hex_string(const void* raw, size_t size) { return winpr_BinToHexString(raw, size, FALSE); } int freerdp_assistance_parse_file_buffer(rdpAssistanceFile* file, const char* buffer, size_t size, const char* password) { char* p; char* q; char* r; char* amp; int status; size_t length; free(file->password); file->password = _strdup(password); p = strstr(buffer, "UPLOADINFO"); if (p) { p = strstr(p + sizeof("UPLOADINFO") - 1, "TYPE=\""); if (!p) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Missing UPLOADINFO TYPE"); return -1; } p = strstr(buffer, "UPLOADDATA"); if (!p) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Missing UPLOADDATA"); return -1; } /* Parse USERNAME */ p = strstr(buffer, "USERNAME=\""); if (p) { p += sizeof("USERNAME=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid USERNAME=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for USERNAME"); return -1; } length = q - p; file->Username = (char*)malloc(length + 1); if (!file->Username) return -1; CopyMemory(file->Username, p, length); file->Username[length] = '\0'; } /* Parse LHTICKET */ p = strstr(buffer, "LHTICKET=\""); if (p) { p += sizeof("LHTICKET=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid LHTICKET=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for LHTICKET"); return -1; } length = q - p; file->LHTicket = (char*)malloc(length + 1); if (!file->LHTicket) return -1; CopyMemory(file->LHTicket, p, length); file->LHTicket[length] = '\0'; } /* Parse RCTICKET */ p = strstr(buffer, "RCTICKET=\""); if (p) { p += sizeof("RCTICKET=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid RCTICKET=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for RCTICKET"); return -1; } length = q - p; file->RCTicket = (char*)malloc(length + 1); if (!file->RCTicket) return -1; CopyMemory(file->RCTicket, p, length); file->RCTicket[length] = '\0'; } /* Parse RCTICKETENCRYPTED */ p = strstr(buffer, "RCTICKETENCRYPTED=\""); if (p) { p += sizeof("RCTICKETENCRYPTED=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid RCTICKETENCRYPTED=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for RCTICKETENCRYPTED"); return -1; } length = q - p; if ((length == 1) && (p[0] == '1')) file->RCTicketEncrypted = TRUE; } /* Parse PassStub */ p = strstr(buffer, "PassStub=\""); if (p) { p += sizeof("PassStub=\"") - 1; // needs to be unescaped (& => &) amp = strstr(p, "&"); q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid PassStub=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for PassStub"); return -1; } if (amp) { length = q - p - 4; } else { length = q - p; } file->PassStub = (char*)malloc(length + 1); if (!file->PassStub) return -1; if (amp) { // just skip over "amp;" leaving "&" CopyMemory(file->PassStub, p, amp - p + 1); CopyMemory(file->PassStub + (amp - p + 1), amp + 5, q - amp + 5); } else { CopyMemory(file->PassStub, p, length); } file->PassStub[length] = '\0'; } /* Parse DtStart */ p = strstr(buffer, "DtStart=\""); if (p) { p += sizeof("DtStart=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid DtStart=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for DtStart"); return -1; } length = q - p; r = (char*)malloc(length + 1); if (!r) return -1; CopyMemory(r, p, length); r[length] = '\0'; errno = 0; { unsigned long val = strtoul(r, NULL, 0); if ((errno != 0) || (val > UINT32_MAX)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid DtStart value %s", r); free(r); return -1; } free(r); file->DtStart = val; } } /* Parse DtLength */ p = strstr(buffer, "DtLength=\""); if (p) { p += sizeof("DtLength=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid DtLength=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for DtLength"); return -1; } length = q - p; r = (char*)malloc(length + 1); if (!r) return -1; CopyMemory(r, p, length); r[length] = '\0'; errno = 0; { unsigned long val = strtoul(r, NULL, 0); if ((errno != 0) || (val > UINT32_MAX)) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid DtLength value %s", r); free(r); return -1; } free(r); file->DtLength = val; } } /* Parse L (LowSpeed) */ p = strstr(buffer, " L=\""); if (p) { p += sizeof(" L=\"") - 1; q = strchr(p, '"'); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid L=%s", p); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field " "order for L"); return -1; } length = q - p; if ((length == 1) && (p[0] == '1')) file->LowSpeed = TRUE; } file->Type = (file->LHTicket) ? 2 : 1; status = 0; switch (file->Type) { case 2: { file->EncryptedLHTicket = freerdp_assistance_hex_string_to_bin( file->LHTicket, &file->EncryptedLHTicketLength); if (!freerdp_assistance_decrypt2(file, password)) 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( password, file->PassStub, &file->EncryptedPassStubLength); if (!file->EncryptedPassStub) return -1; return 1; } p = strstr(buffer, ""); if (p) { q = strstr(buffer, ""); if (!q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Missing tag"); return -1; } if (p > q) { WLog_ERR(TAG, "Failed to parse ASSISTANCE file: invalid field order for "); return -1; } q += sizeof("") - 1; length = q - p; file->ConnectionString2 = (char*)malloc(length + 1); if (!file->ConnectionString2) return -1; CopyMemory(file->ConnectionString2, p, length); file->ConnectionString2[length] = '\0'; if (!freerdp_assistance_parse_connection_string2(file)) return -1; return 1; } WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Neither UPLOADINFO nor found"); return -1; } int freerdp_assistance_parse_file(rdpAssistanceFile* file, const char* name, const char* password) { int status; BYTE* buffer; FILE* fp = NULL; size_t readSize; union { INT64 i64; size_t s; } fileSize; if (!name) { WLog_ERR(TAG, "ASSISTANCE file %s invalid name", name); return -1; } free(file->filename); file->filename = _strdup(name); fp = winpr_fopen(name, "r"); if (!fp) { WLog_ERR(TAG, "Failed to open ASSISTANCE file %s ", name); return -1; } _fseeki64(fp, 0, SEEK_END); fileSize.i64 = _ftelli64(fp); _fseeki64(fp, 0, SEEK_SET); if (fileSize.i64 < 1) { WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name); fclose(fp); return -1; } buffer = (BYTE*)malloc(fileSize.s + 2); if (!buffer) { fclose(fp); return -1; } readSize = fread(buffer, fileSize.s, 1, fp); if (!readSize) { if (!ferror(fp)) readSize = fileSize.s; } fclose(fp); if (readSize < 1) { WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name); free(buffer); buffer = NULL; 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) { UINT32 i; 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 (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, file->MachineAddresses[0])) 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; if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, file->MachinePorts[0])) return FALSE; if (!freerdp_settings_set_pointer_len(settings, FreeRDP_TargetNetAddresses, NULL, file->MachineCount)) return FALSE; if (!freerdp_settings_set_pointer_len(settings, FreeRDP_TargetNetPorts, file->MachinePorts, file->MachineCount)) return FALSE; for (i = 0; i < file->MachineCount; i++) { if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses, i, file->MachineAddresses[i])) return FALSE; } return TRUE; } rdpAssistanceFile* freerdp_assistance_file_new(void) { winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT); return (rdpAssistanceFile*)calloc(1, sizeof(rdpAssistanceFile)); } void freerdp_assistance_file_free(rdpAssistanceFile* file) { UINT32 i; if (!file) return; free(file->filename); free(file->password); free(file->Username); free(file->LHTicket); free(file->RCTicket); free(file->PassStub); free(file->ConnectionString1); free(file->ConnectionString2); free(file->EncryptedLHTicket); free(file->RASessionId); free(file->RASpecificParams); free(file->EncryptedPassStub); for (i = 0; i < file->MachineCount; i++) { free(file->MachineAddresses[i]); } free(file->MachineAddresses); free(file->MachinePorts); free(file); } void freerdp_assistance_print_file(rdpAssistanceFile* file, wLog* log, DWORD level) { size_t x; 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); for (x = 0; x < file->MachineCount; x++) { WLog_Print(log, level, "MachineAddress [%" PRIdz ": %s", x, file->MachineAddresses[x]); WLog_Print(log, level, "MachinePort [%" PRIdz ": %" PRIu32, x, file->MachinePorts[x]); } } 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; free(file->ConnectionString2); free(file->password); file->ConnectionString2 = _strdup(string); file->password = _strdup(password); return freerdp_assistance_parse_connection_string2(file); }