From 955f13a18d1d87a1289fb9b0696bed97faf6501c Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 2 Feb 2024 22:55:53 -0600 Subject: [PATCH] Implement RTSP encryption support --- src/Connection.c | 4 +- src/RtspConnection.c | 155 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 150 insertions(+), 9 deletions(-) diff --git a/src/Connection.c b/src/Connection.c index ff0b9b4..2b82eda 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -525,5 +525,7 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre } const char* LiGetLaunchUrlQueryParameters() { - return "&corever=0"; + // v0 = Video encryption and control stream encryption v2 + // v1 = RTSP encryption + return "&corever=1"; } diff --git a/src/RtspConnection.c b/src/RtspConnection.c index 78789ac..d917a72 100644 --- a/src/RtspConnection.c +++ b/src/RtspConnection.c @@ -13,6 +13,11 @@ static int rtspClientVersion; static char urlAddr[URLSAFESTRING_LEN]; static bool useEnet; 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 ENetHost* client; @@ -84,6 +89,134 @@ static bool initializeRtspRequest(PRTSP_MESSAGE msg, char* command, char* target 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 static bool transactRtspMessageEnet(PRTSP_MESSAGE request, PRTSP_MESSAGE response, bool expectingPayload, int* error) { ENetEvent event; @@ -96,6 +229,10 @@ static bool transactRtspMessageEnet(PRTSP_MESSAGE request, PRTSP_MESSAGE respons bool ret; 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; ret = false; responseBuffer = NULL; @@ -250,7 +387,7 @@ static bool transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response return ret; } - serializedMessage = serializeRtspMessage(request, &messageLen); + serializedMessage = sealRtspMessage(request, &messageLen); if (serializedMessage == NULL) { closeSocket(sock); sock = INVALID_SOCKET; @@ -312,13 +449,8 @@ static bool transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response } } - if (parseRtspMessage(response, responseBuffer, offset) == RTSP_ERROR_SUCCESS) { - // Successfully parsed response - ret = true; - } - else { - Limelog("Failed to parse RTSP response\n"); - } + // Decrypt (if necessary) and deserialize the RTSP response + ret = unsealRtspMessage(responseBuffer, offset, response); // Fetch the local address for this socket if it's not populated yet if (LocalAddr.ss_family == 0) { @@ -774,6 +906,9 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) { hasSessionId = false; controlStreamId = APP_VERSION_AT_LEAST(7, 1, 431) ? "streamid=control/13/0" : "streamid=control/1/0"; 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 // fake our target address so it doesn't match any of the PC's local interfaces. It seems @@ -1221,5 +1356,9 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) { sessionIdString = NULL; } + PltDestroyCryptoContext(encryptionCtx); + PltDestroyCryptoContext(decryptionCtx); + decryptionCtx = encryptionCtx = NULL; + return ret; }