diff --git a/src/AudioStream.c b/src/AudioStream.c index 65547d4..baf6623 100644 --- a/src/AudioStream.c +++ b/src/AudioStream.c @@ -13,6 +13,8 @@ static PLT_THREAD udpPingThread; static PLT_THREAD receiveThread; static PLT_THREAD decoderThread; +static PPLT_CRYPTO_CONTEXT audioDecryptionCtx; + static unsigned short lastSeq; static bool receivedDataFromPeer; @@ -65,6 +67,7 @@ int initializeAudioStream(void) { RtpqInitializeQueue(&rtpReorderQueue, RTPQ_DEFAULT_MAX_SIZE, RTPQ_DEFAULT_QUEUE_TIME); lastSeq = 0; receivedDataFromPeer = false; + audioDecryptionCtx = PltCreateCryptoContext(); // For GFE 3.22 compatibility, we must start the audio ping thread before the RTSP handshake. // It will not reply to our RTSP PLAY request until the audio ping has been received. @@ -109,6 +112,7 @@ void destroyAudioStream(void) { rtpSocket = INVALID_SOCKET; } + PltDestroyCryptoContext(audioDecryptionCtx); freePacketList(LbqDestroyLinkedBlockingQueue(&packetQueue)); RtpqCleanupQueue(&rtpReorderQueue); } @@ -144,7 +148,36 @@ static void decodeInputData(PQUEUED_AUDIO_PACKET packet) { lastSeq = rtp->sequenceNumber; - AudioCallbacks.decodeAndPlaySample((char*)(rtp + 1), packet->size - sizeof(*rtp)); + if (AudioEncryptionEnabled) { + // We must have room for the AES padding which may be written to the buffer + unsigned char decryptedOpusData[MAX_PACKET_SIZE+16]; + unsigned char iv[16] = {}; + int dataLength = packet->size - sizeof(*rtp); + + LC_ASSERT(dataLength <= MAX_PACKET_SIZE); + + // The IV is the avkeyid (equivalent to the rikeyid) + + // the RTP sequence number, in big endian. + uint32_t ivSeq = BE32(*(uint32_t*)StreamConfig.remoteInputAesIv); + ivSeq += rtp->sequenceNumber; + ivSeq = BE32(ivSeq); + + memcpy(iv, &ivSeq, sizeof(ivSeq)); + + if (!PltDecryptMessage(audioDecryptionCtx, ALGORITHM_AES_CBC, CIPHER_FLAG_RESET_IV | CIPHER_FLAG_FINISH, + (unsigned char*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey), + iv, sizeof(iv), + NULL, 0, + (unsigned char*)(rtp + 1), dataLength, + decryptedOpusData, &dataLength)) { + return; + } + + AudioCallbacks.decodeAndPlaySample((char*)decryptedOpusData, dataLength); + } + else { + AudioCallbacks.decodeAndPlaySample((char*)(rtp + 1), packet->size - sizeof(*rtp)); + } } static void ReceiveThreadProc(void* context) { diff --git a/src/Connection.c b/src/Connection.c index 4195534..274d5a9 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -24,6 +24,7 @@ OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig; OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig; int OriginalVideoBitrate; int AudioPacketDuration; +bool AudioEncryptionEnabled; // Connection stages static const char* stageNames[STAGE_MAX] = { diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h index 7a768f3..501c079 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -27,6 +27,7 @@ extern OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig; extern OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig; extern int OriginalVideoBitrate; extern int AudioPacketDuration; +extern bool AudioEncryptionEnabled; #ifndef UINT24_MAX #define UINT24_MAX 0xFFFFFF diff --git a/src/Limelight.h b/src/Limelight.h index d5ec789..2b472a9 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -29,6 +29,11 @@ extern "C" { #define COLOR_RANGE_LIMITED 0 #define COLOR_RANGE_FULL 1 +// Values for 'encryptionFlags' field below +#define ENCFLG_NONE 0x00000000 +#define ENCFLG_AUDIO 0x00000001 +#define ENCFLG_ALL 0xFFFFFFFF + typedef struct _STREAM_CONFIGURATION { // Dimensions in pixels of the desired video stream int width; @@ -87,6 +92,14 @@ typedef struct _STREAM_CONFIGURATION { // option (listed above). If not set, the encoder will default to Limited. int colorRange; + // Specifies the data streams where encryption may be enabled if supported + // by the host PC. Ideally, you would pass ENCFLG_ALL to encrypt everything + // that we support encrypting. However, lower performance hardware may not + // be able to support encrypting heavy stuff like video or audio data, so + // that encryption may be disabled here. Remote input encryption is always + // enabled. + int encryptionFlags; + // AES encryption data for the remote input stream. This must be // the same as what was passed as rikey and rikeyid // in /launch and /resume requests. diff --git a/src/RtspConnection.c b/src/RtspConnection.c index 4f9a134..07efce1 100644 --- a/src/RtspConnection.c +++ b/src/RtspConnection.c @@ -607,6 +607,7 @@ int performRtspHandshake(void) { currentSeqNumber = 1; hasSessionId = false; controlStreamId = APP_VERSION_AT_LEAST(7, 1, 431) ? "streamid=control/13/0" : "streamid=control/1/0"; + AudioEncryptionEnabled = false; switch (AppVersionQuad[0]) { case 3: diff --git a/src/SdpGenerator.c b/src/SdpGenerator.c index ec5a9bd..7d28af8 100644 --- a/src/SdpGenerator.c +++ b/src/SdpGenerator.c @@ -139,13 +139,31 @@ static int addGen4Options(PSDP_OPTION* head, char* addrStr) { return err; } +#define NVFF_BASE 0x07 +#define NVFF_AUDIO_ENCRYPTION 0x20 +#define NVFF_RI_ENCRYPTION 0x80 + static int addGen5Options(PSDP_OPTION* head) { int err = 0; + char payloadStr[32]; + + // This must be initialized to false already + LC_ASSERT(!AudioEncryptionEnabled); if (APP_VERSION_AT_LEAST(7, 1, 431)) { - // 0x20 enables audio encryption (which we don't support yet) - // 0x80 enables remote input encryption (which we do want) - err |= addAttributeString(head, "x-nv-general.featureFlags", "135"); + unsigned int featureFlags; + + // RI encryption is always enabled + featureFlags = NVFF_BASE | NVFF_RI_ENCRYPTION; + + // Enable audio encryption if the client opted in + if (StreamConfig.encryptionFlags & ENCFLG_AUDIO) { + featureFlags |= NVFF_AUDIO_ENCRYPTION; + AudioEncryptionEnabled = true; + } + + sprintf(payloadStr, "%u", featureFlags); + err |= addAttributeString(head, "x-nv-general.featureFlags", payloadStr); // Ask for the encrypted control protocol to ensure remote input will be encrypted. // This used to be done via separate RI encryption, but now it is all or nothing.