From ecad4aa2766dc1601b7a2de71a6476d023f49bb0 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 24 Dec 2018 01:03:32 -0800 Subject: [PATCH] Sync pairing code with moonlight-embedded to fix MITM check during pairing --- libgamestream/pairing.c | 329 +++++++++++++++++++++++++++------------- 1 file changed, 223 insertions(+), 106 deletions(-) diff --git a/libgamestream/pairing.c b/libgamestream/pairing.c index 7ecb31a..5dc5742 100644 --- a/libgamestream/pairing.c +++ b/libgamestream/pairing.c @@ -126,115 +126,232 @@ cleanup: return result; } -int gs_pair(int serverMajorVersion, const char* address, const char* pin) { - int ret = GS_OK; - char url[4096]; - - unsigned char salt_data[16]; - char salt_hex[33]; - RAND_bytes(salt_data, 16); - bytes_to_hex(salt_data, salt_hex, 16); - - sprintf(url, "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&phrase=getservercert&salt=%s&clientcert=%s", address, g_UniqueId, salt_hex, g_CertHex); - PHTTP_DATA data = http_create_data(); - if (data == NULL) - return GS_OUT_OF_MEMORY; - else if ((ret = http_request(url, data)) != GS_OK) - goto cleanup; - - unsigned char salt_pin[20]; - unsigned char aes_key_hash[32]; - AES_KEY enc_key, dec_key; - memcpy(salt_pin, salt_data, 16); - memcpy(salt_pin+16, pin, 4); - - int hash_length = serverMajorVersion >= 7 ? 32 : 20; - if (serverMajorVersion >= 7) - SHA256(salt_pin, 20, aes_key_hash); - else - SHA1(salt_pin, 20, aes_key_hash); - - AES_set_encrypt_key((unsigned char *)aes_key_hash, 128, &enc_key); - AES_set_decrypt_key((unsigned char *)aes_key_hash, 128, &dec_key); - - unsigned char challenge_data[16]; - unsigned char challenge_enc[16]; - char challenge_hex[33]; - RAND_bytes(challenge_data, 16); - AES_encrypt(challenge_data, challenge_enc, &enc_key); - bytes_to_hex(challenge_enc, challenge_hex, 16); - - sprintf(url, "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&clientchallenge=%s", address, g_UniqueId, challenge_hex); - if ((ret = http_request(url, data)) != GS_OK) - goto cleanup; +static bool verifySignature(const unsigned char *data, int dataLength, unsigned char *signature, int signatureLength, X509 *cert) { + EVP_PKEY* pubKey = X509_get_pubkey(cert); + EVP_MD_CTX *mdctx = NULL; + mdctx = EVP_MD_CTX_create(); + EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pubKey); + EVP_DigestVerifyUpdate(mdctx, data, dataLength); + int result = EVP_DigestVerifyFinal(mdctx, signature, signatureLength); + + EVP_PKEY_free(pubKey); + EVP_MD_CTX_destroy(mdctx); + return result > 0; +} + +X509* get_cert(PHTTP_DATA data) { char *result; - if (xml_search(data->memory, data->size, "challengeresponse", &result) != GS_OK) { - ret = GS_INVALID; - goto cleanup; - } - - unsigned char challenge_response_data_enc[48]; - unsigned char challenge_response_data[48]; - for (int count = 0; count < strlen(result); count += 2) { - sscanf(&result[count], "%2hhx", &challenge_response_data_enc[count / 2]); - } + + if (xml_search(data->memory, data->size, "plaincert", &result) != GS_OK) + return NULL; + + BIO* bio = BIO_new_mem_buf(result, -1); free(result); - - for (int i = 0; i < 48; i += 16) { - AES_decrypt(&challenge_response_data_enc[i], &challenge_response_data[i], &dec_key); + + if (bio) { + X509* cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + BIO_free_all(bio); + return cert; } - - unsigned char client_secret_data[16]; - RAND_bytes(client_secret_data, 16); - - unsigned char challenge_response[16 + 256 + 16]; - unsigned char challenge_response_hash[32]; - unsigned char challenge_response_hash_enc[32]; - char challenge_response_hex[65]; - memcpy(challenge_response, challenge_response_data + hash_length, 16); - memcpy(challenge_response + 16, g_Cert->signature->data, 256); - memcpy(challenge_response + 16 + 256, client_secret_data, 16); - - if (serverMajorVersion >= 7) - SHA256(challenge_response, 16 + 256 + 16, challenge_response_hash); - else - SHA1(challenge_response, 16 + 256 + 16, challenge_response_hash); - - for (int i = 0; i < 32; i += 16) { - AES_encrypt(&challenge_response_hash[i], &challenge_response_hash_enc[i], &enc_key); + else { + return NULL; } - bytes_to_hex(challenge_response_hash_enc, challenge_response_hex, 32); - - sprintf(url, "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&serverchallengeresp=%s", address, g_UniqueId, challenge_response_hex); - if ((ret = http_request(url, data)) != GS_OK) - goto cleanup; - - if (xml_search(data->memory, data->size, "pairingsecret", &result) != GS_OK) { - ret = GS_INVALID; - goto cleanup; - } - - unsigned char *signature = NULL; - size_t s_len; - if (sign_it(client_secret_data, 16, &signature, &s_len, g_PrivateKey) != GS_OK) { - gs_error = "Failed to sign data"; - ret = GS_FAILED; - goto cleanup; - } - - unsigned char client_pairing_secret[16 + 256]; - char client_pairing_secret_hex[(16 + 256) * 2 + 1]; - memcpy(client_pairing_secret, client_secret_data, 16); - memcpy(client_pairing_secret + 16, signature, 256); - bytes_to_hex(client_pairing_secret, client_pairing_secret_hex, 16 + 256); - - sprintf(url, "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&clientpairingsecret=%s", address, g_UniqueId, client_pairing_secret_hex); - if ((ret = http_request(url, data)) != GS_OK) - goto cleanup; - -cleanup: - http_free_data(data); - - return ret; +} + +int gs_unpair(const char* address) { + int ret = GS_OK; + char url[4096]; + PHTTP_DATA data = http_create_data(); + if (data == NULL) + return GS_OUT_OF_MEMORY; + + snprintf(url, sizeof(url), "http://%s:47989/unpair?uniqueid=%s", address, g_UniqueId); + ret = http_request(url, data); + + http_free_data(data); + return ret; +} + +int gs_pair(int serverMajorVersion, const char* address, const char* pin) { + int ret = GS_OK; + char* result = NULL; + X509* server_cert = NULL; + char url[4096]; + + unsigned char salt_data[16]; + char salt_hex[33]; + RAND_bytes(salt_data, 16); + bytes_to_hex(salt_data, salt_hex, 16); + + snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&phrase=getservercert&salt=%s&clientcert=%s", address, g_UniqueId, salt_hex, g_CertHex); + PHTTP_DATA data = http_create_data(); + if (data == NULL) + return GS_OUT_OF_MEMORY; + else if ((ret = http_request(url, data)) != GS_OK) + goto cleanup; + + if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK) + goto cleanup; + + if (strcmp(result, "1") != 0) { + ret = GS_FAILED; + goto cleanup; + } + + free(result); + result = NULL; + server_cert = get_cert(data); + if (server_cert == NULL) { + ret = GS_FAILED; + goto cleanup; + } + + unsigned char salt_pin[20]; + unsigned char aes_key_hash[32]; + AES_KEY enc_key, dec_key; + memcpy(salt_pin, salt_data, 16); + memcpy(salt_pin+16, pin, 4); + + int hash_length = serverMajorVersion >= 7 ? 32 : 20; + if (serverMajorVersion >= 7) + SHA256(salt_pin, 20, aes_key_hash); + else + SHA1(salt_pin, 20, aes_key_hash); + + AES_set_encrypt_key((unsigned char *)aes_key_hash, 128, &enc_key); + AES_set_decrypt_key((unsigned char *)aes_key_hash, 128, &dec_key); + + unsigned char challenge_data[16]; + unsigned char challenge_enc[16]; + char challenge_hex[33]; + RAND_bytes(challenge_data, 16); + AES_encrypt(challenge_data, challenge_enc, &enc_key); + bytes_to_hex(challenge_enc, challenge_hex, 16); + + snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&clientchallenge=%s", address, g_UniqueId, challenge_hex); + if ((ret = http_request(url, data)) != GS_OK) + goto cleanup; + + free(result); + result = NULL; + if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK) + goto cleanup; + + if (strcmp(result, "1") != 0) { + ret = GS_FAILED; + goto cleanup; + } + + free(result); + result = NULL; + if (xml_search(data->memory, data->size, "challengeresponse", &result) != GS_OK) { + ret = GS_INVALID; + goto cleanup; + } + + unsigned char challenge_response_data_enc[48]; + unsigned char challenge_response_data[48]; + for (int count = 0; count < strlen(result); count += 2) { + sscanf(&result[count], "%2hhx", &challenge_response_data_enc[count / 2]); + } + + for (int i = 0; i < 48; i += 16) { + AES_decrypt(&challenge_response_data_enc[i], &challenge_response_data[i], &dec_key); + } + + unsigned char client_secret_data[16]; + RAND_bytes(client_secret_data, 16); + + ASN1_BIT_STRING *asnSignature; + X509_get0_signature(&asnSignature, NULL, g_Cert); + + unsigned char challenge_response[16 + 256 + 16]; + unsigned char challenge_response_hash[32]; + unsigned char challenge_response_hash_enc[32]; + char challenge_response_hex[65]; + memcpy(challenge_response, challenge_response_data + hash_length, 16); + memcpy(challenge_response + 16, asnSignature->data, 256); + memcpy(challenge_response + 16 + 256, client_secret_data, 16); + if (serverMajorVersion >= 7) + SHA256(challenge_response, 16 + 256 + 16, challenge_response_hash); + else + SHA1(challenge_response, 16 + 256 + 16, challenge_response_hash); + + for (int i = 0; i < 32; i += 16) { + AES_encrypt(&challenge_response_hash[i], &challenge_response_hash_enc[i], &enc_key); + } + bytes_to_hex(challenge_response_hash_enc, challenge_response_hex, 32); + + snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&serverchallengeresp=%s", address, g_UniqueId, challenge_response_hex); + if ((ret = http_request(url, data)) != GS_OK) + goto cleanup; + + free(result); + result = NULL; + if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK) + goto cleanup; + + if (strcmp(result, "1") != 0) { + ret = GS_FAILED; + goto cleanup; + } + + free(result); + result = NULL; + if (xml_search(data->memory, data->size, "pairingsecret", &result) != GS_OK) { + ret = GS_INVALID; + goto cleanup; + } + + unsigned char pairing_secret[16 + 256]; + for (int count = 0; count < strlen(result); count += 2) { + sscanf(&result[count], "%2hhx", &pairing_secret[count / 2]); + } + + if (!verifySignature(pairing_secret, 16, pairing_secret+16, 256, server_cert)) { + ret = GS_FAILED; + goto cleanup; + } + + unsigned char *signature = NULL; + size_t s_len; + if (sign_it(client_secret_data, 16, &signature, &s_len, g_PrivateKey) != GS_OK) { + ret = GS_FAILED; + goto cleanup; + } + + unsigned char client_pairing_secret[16 + 256]; + char client_pairing_secret_hex[(16 + 256) * 2 + 1]; + memcpy(client_pairing_secret, client_secret_data, 16); + memcpy(client_pairing_secret + 16, signature, 256); + bytes_to_hex(client_pairing_secret, client_pairing_secret_hex, 16 + 256); + + snprintf(url, sizeof(url), "http://%s:47989/pair?uniqueid=%s&devicename=roth&updateState=1&clientpairingsecret=%s", address, g_UniqueId, client_pairing_secret_hex); + if ((ret = http_request(url, data)) != GS_OK) + goto cleanup; + + free(result); + result = NULL; + if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK) + goto cleanup; + + if (strcmp(result, "1") != 0) { + ret = GS_FAILED; + goto cleanup; + } + + cleanup: + if (ret != GS_OK) + gs_unpair(address); + + if (result != NULL) + free(result); + + if (server_cert != NULL) + X509_free(server_cert); + + http_free_data(data); + + return ret; } \ No newline at end of file