mirror of
https://github.com/moonlight-stream/moonlight-common-c.git
synced 2025-07-01 15:25:43 +00:00
Implement RTSP encryption support
This commit is contained in:
parent
35f730fedd
commit
955f13a18d
@ -525,5 +525,7 @@ Cleanup:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const char* LiGetLaunchUrlQueryParameters() {
|
const char* LiGetLaunchUrlQueryParameters() {
|
||||||
return "&corever=0";
|
// v0 = Video encryption and control stream encryption v2
|
||||||
|
// v1 = RTSP encryption
|
||||||
|
return "&corever=1";
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,11 @@ static int rtspClientVersion;
|
|||||||
static char urlAddr[URLSAFESTRING_LEN];
|
static char urlAddr[URLSAFESTRING_LEN];
|
||||||
static bool useEnet;
|
static bool useEnet;
|
||||||
static char* controlStreamId;
|
static char* controlStreamId;
|
||||||
|
static bool encryptedRtspEnabled;
|
||||||
|
|
||||||
|
static PPLT_CRYPTO_CONTEXT encryptionCtx;
|
||||||
|
static PPLT_CRYPTO_CONTEXT decryptionCtx;
|
||||||
|
static uint32_t encryptionSequenceNumber;
|
||||||
|
|
||||||
static SOCKET sock = INVALID_SOCKET;
|
static SOCKET sock = INVALID_SOCKET;
|
||||||
static ENetHost* client;
|
static ENetHost* client;
|
||||||
@ -84,6 +89,134 @@ static bool initializeRtspRequest(PRTSP_MESSAGE msg, char* command, char* target
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define ENCRYPTED_RTSP_BIT 0x80000000
|
||||||
|
|
||||||
|
typedef struct _ENC_RTSP_HEADER {
|
||||||
|
uint32_t typeAndLength; // BE
|
||||||
|
uint32_t sequenceNumber; // BE
|
||||||
|
uint8_t tag[16];
|
||||||
|
} ENC_RTSP_HEADER, *PENC_RTSP_HEADER;
|
||||||
|
|
||||||
|
static char* sealRtspMessage(PRTSP_MESSAGE request, int* messageLen) {
|
||||||
|
char* serializedMessage;
|
||||||
|
PENC_RTSP_HEADER encryptedMessage;
|
||||||
|
int plaintextLen;
|
||||||
|
bool success;
|
||||||
|
uint8_t iv[12] = { 0 };
|
||||||
|
|
||||||
|
serializedMessage = serializeRtspMessage(request, &plaintextLen);
|
||||||
|
if (serializedMessage == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else if (!encryptedRtspEnabled) {
|
||||||
|
*messageLen = plaintextLen;
|
||||||
|
return serializedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedMessage = (PENC_RTSP_HEADER)malloc(sizeof(ENC_RTSP_HEADER) + plaintextLen);
|
||||||
|
if (encryptedMessage == NULL) {
|
||||||
|
free(serializedMessage);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the IV in little endian byte order
|
||||||
|
encryptionSequenceNumber++;
|
||||||
|
iv[3] = (uint8_t)(encryptionSequenceNumber >> 24);
|
||||||
|
iv[2] = (uint8_t)(encryptionSequenceNumber >> 16);
|
||||||
|
iv[1] = (uint8_t)(encryptionSequenceNumber >> 8);
|
||||||
|
iv[0] = (uint8_t)(encryptionSequenceNumber >> 0);
|
||||||
|
|
||||||
|
// Set high bytes to something unique to ensure no IV collisions
|
||||||
|
iv[10] = (uint8_t)'C'; // Client originated
|
||||||
|
iv[11] = (uint8_t)'R'; // RTSP stream
|
||||||
|
|
||||||
|
encryptedMessage->typeAndLength = BE32(ENCRYPTED_RTSP_BIT | plaintextLen);
|
||||||
|
encryptedMessage->sequenceNumber = BE32(encryptionSequenceNumber);
|
||||||
|
|
||||||
|
success = PltEncryptMessage(encryptionCtx, ALGORITHM_AES_GCM, 0,
|
||||||
|
(uint8_t*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey),
|
||||||
|
iv, sizeof(iv),
|
||||||
|
encryptedMessage->tag, sizeof(encryptedMessage->tag),
|
||||||
|
(uint8_t*)serializedMessage, plaintextLen,
|
||||||
|
(uint8_t*)(encryptedMessage + 1), messageLen);
|
||||||
|
free(serializedMessage);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
free(encryptedMessage);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The size returned from PltEncryptMessage() is the payload only
|
||||||
|
*messageLen += sizeof(ENC_RTSP_HEADER);
|
||||||
|
|
||||||
|
return (char*)encryptedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool unsealRtspMessage(char* rawMessage, int rawMessageLen, PRTSP_MESSAGE response) {
|
||||||
|
char* decryptedMessage;
|
||||||
|
int decryptedMessageLen;
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
if (encryptedRtspEnabled) {
|
||||||
|
PENC_RTSP_HEADER encryptedMessage;
|
||||||
|
uint32_t seq;
|
||||||
|
uint8_t iv[12] = { 0 };
|
||||||
|
|
||||||
|
if (rawMessageLen <= (int)sizeof(ENC_RTSP_HEADER)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedMessage = (PENC_RTSP_HEADER)rawMessage;
|
||||||
|
seq = BE32(encryptedMessage->sequenceNumber);
|
||||||
|
|
||||||
|
// Populate the IV in little endian byte order
|
||||||
|
iv[3] = (uint8_t)(seq >> 24);
|
||||||
|
iv[2] = (uint8_t)(seq >> 16);
|
||||||
|
iv[1] = (uint8_t)(seq >> 8);
|
||||||
|
iv[0] = (uint8_t)(seq >> 0);
|
||||||
|
|
||||||
|
// Set high bytes to something unique to ensure no IV collisions
|
||||||
|
iv[10] = (uint8_t)'H'; // Host originated
|
||||||
|
iv[11] = (uint8_t)'R'; // RTSP stream
|
||||||
|
|
||||||
|
decryptedMessageLen = rawMessageLen - sizeof(ENC_RTSP_HEADER);
|
||||||
|
decryptedMessage = (char*)malloc(decryptedMessageLen);
|
||||||
|
if (decryptedMessage == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
success = PltDecryptMessage(decryptionCtx, ALGORITHM_AES_GCM, 0,
|
||||||
|
(uint8_t*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey),
|
||||||
|
iv, sizeof(iv),
|
||||||
|
encryptedMessage->tag, sizeof(encryptedMessage->tag),
|
||||||
|
(uint8_t*)(encryptedMessage + 1), decryptedMessageLen,
|
||||||
|
(uint8_t*)decryptedMessage, &decryptedMessageLen);
|
||||||
|
if (!success) {
|
||||||
|
Limelog("Failed to decrypt RTSP response\n");
|
||||||
|
free(decryptedMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
decryptedMessage = rawMessage;
|
||||||
|
decryptedMessageLen = rawMessageLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseRtspMessage(response, decryptedMessage, decryptedMessageLen) == RTSP_ERROR_SUCCESS) {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Limelog("Failed to parse RTSP response\n");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decryptedMessage != rawMessage) {
|
||||||
|
free(decryptedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
// Send RTSP message and get response over ENet
|
// Send RTSP message and get response over ENet
|
||||||
static bool transactRtspMessageEnet(PRTSP_MESSAGE request, PRTSP_MESSAGE response, bool expectingPayload, int* error) {
|
static bool transactRtspMessageEnet(PRTSP_MESSAGE request, PRTSP_MESSAGE response, bool expectingPayload, int* error) {
|
||||||
ENetEvent event;
|
ENetEvent event;
|
||||||
@ -96,6 +229,10 @@ static bool transactRtspMessageEnet(PRTSP_MESSAGE request, PRTSP_MESSAGE respons
|
|||||||
bool ret;
|
bool ret;
|
||||||
char* responseBuffer;
|
char* responseBuffer;
|
||||||
|
|
||||||
|
// RTSP encryption is not supported using ENet due to our special handling
|
||||||
|
// of the payload below. Modern versions of Sunshine use TCP for RTSP.
|
||||||
|
LC_ASSERT(!encryptedRtspEnabled);
|
||||||
|
|
||||||
*error = -1;
|
*error = -1;
|
||||||
ret = false;
|
ret = false;
|
||||||
responseBuffer = NULL;
|
responseBuffer = NULL;
|
||||||
@ -250,7 +387,7 @@ static bool transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedMessage = serializeRtspMessage(request, &messageLen);
|
serializedMessage = sealRtspMessage(request, &messageLen);
|
||||||
if (serializedMessage == NULL) {
|
if (serializedMessage == NULL) {
|
||||||
closeSocket(sock);
|
closeSocket(sock);
|
||||||
sock = INVALID_SOCKET;
|
sock = INVALID_SOCKET;
|
||||||
@ -312,13 +449,8 @@ static bool transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parseRtspMessage(response, responseBuffer, offset) == RTSP_ERROR_SUCCESS) {
|
// Decrypt (if necessary) and deserialize the RTSP response
|
||||||
// Successfully parsed response
|
ret = unsealRtspMessage(responseBuffer, offset, response);
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Limelog("Failed to parse RTSP response\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the local address for this socket if it's not populated yet
|
// Fetch the local address for this socket if it's not populated yet
|
||||||
if (LocalAddr.ss_family == 0) {
|
if (LocalAddr.ss_family == 0) {
|
||||||
@ -774,6 +906,9 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) {
|
|||||||
hasSessionId = false;
|
hasSessionId = false;
|
||||||
controlStreamId = APP_VERSION_AT_LEAST(7, 1, 431) ? "streamid=control/13/0" : "streamid=control/1/0";
|
controlStreamId = APP_VERSION_AT_LEAST(7, 1, 431) ? "streamid=control/13/0" : "streamid=control/1/0";
|
||||||
AudioEncryptionEnabled = false;
|
AudioEncryptionEnabled = false;
|
||||||
|
encryptedRtspEnabled = serverInfo->rtspSessionUrl && strstr(serverInfo->rtspSessionUrl, "rtspenc://");
|
||||||
|
encryptionCtx = PltCreateCryptoContext();
|
||||||
|
decryptionCtx = PltCreateCryptoContext();
|
||||||
|
|
||||||
// HACK: In order to get GFE to respect our request for a lower audio bitrate, we must
|
// HACK: In order to get GFE to respect our request for a lower audio bitrate, we must
|
||||||
// fake our target address so it doesn't match any of the PC's local interfaces. It seems
|
// fake our target address so it doesn't match any of the PC's local interfaces. It seems
|
||||||
@ -1221,5 +1356,9 @@ Exit:
|
|||||||
sessionIdString = NULL;
|
sessionIdString = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PltDestroyCryptoContext(encryptionCtx);
|
||||||
|
PltDestroyCryptoContext(decryptionCtx);
|
||||||
|
decryptionCtx = encryptionCtx = NULL;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user