diff --git a/src/Limelight.h b/src/Limelight.h index 3f3c277..bc0a2e3 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -32,6 +32,7 @@ extern "C" { // Values for 'encryptionFlags' field below #define ENCFLG_NONE 0x00000000 #define ENCFLG_AUDIO 0x00000001 +#define ENCFLG_VIDEO 0x00000002 #define ENCFLG_ALL 0xFFFFFFFF // This function returns a string that you SHOULD append to the /launch and /resume diff --git a/src/SdpGenerator.c b/src/SdpGenerator.c index 2a304da..db3262d 100644 --- a/src/SdpGenerator.c +++ b/src/SdpGenerator.c @@ -276,6 +276,18 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) { if (EncryptionFeaturesSupported & SS_ENC_CONTROL_V2) { EncryptionFeaturesEnabled |= SS_ENC_CONTROL_V2; } + + // If video encryption is supported by the host and desired by the client, use it + if ((EncryptionFeaturesSupported & SS_ENC_VIDEO) && (StreamConfig.encryptionFlags & ENCFLG_VIDEO)) { + EncryptionFeaturesEnabled |= SS_ENC_VIDEO; + } + else if ((EncryptionFeaturesRequested & SS_ENC_VIDEO) && !(StreamConfig.encryptionFlags & ENCFLG_VIDEO)) { + // If video encryption is explicitly requested by the host but *not* by the client, + // we'll encrypt anyway (since we are capable of doing so) and print a warning. + Limelog("Enabling video encryption by host request despite client opt-out. Performance may suffer!"); + EncryptionFeaturesEnabled |= SS_ENC_VIDEO; + } + snprintf(payloadStr, sizeof(payloadStr), "%u", EncryptionFeaturesEnabled); err |= addAttributeString(&optionHead, "x-ss-general.encryptionEnabled", payloadStr); } diff --git a/src/Video.h b/src/Video.h index 34667b1..5175237 100644 --- a/src/Video.h +++ b/src/Video.h @@ -9,6 +9,11 @@ typedef struct _QUEUED_DECODE_UNIT { #pragma pack(push, 1) +typedef struct _ENC_VIDEO_HEADER { + uint8_t iv[16]; + uint8_t tag[16]; +} ENC_VIDEO_HEADER, *PENC_VIDEO_HEADER; + #define FLAG_CONTAINS_PIC_DATA 0x1 #define FLAG_EOF 0x2 #define FLAG_SOF 0x4 diff --git a/src/VideoStream.c b/src/VideoStream.c index e1f3e65..b1d367c 100644 --- a/src/VideoStream.c +++ b/src/VideoStream.c @@ -10,6 +10,8 @@ static RTP_VIDEO_QUEUE rtpQueue; static SOCKET rtpSocket = INVALID_SOCKET; static SOCKET firstFrameSocket = INVALID_SOCKET; +static PPLT_CRYPTO_CONTEXT decryptionCtx; + static PLT_THREAD udpPingThread; static PLT_THREAD receiveThread; static PLT_THREAD decoderThread; @@ -36,6 +38,7 @@ static bool receivedFullFrame; void initializeVideoStream(void) { initializeVideoDepacketizer(StreamConfig.packetSize); RtpvInitializeQueue(&rtpQueue); + decryptionCtx = PltCreateCryptoContext(); receivedDataFromPeer = false; firstDataTimeMs = 0; receivedFullFrame = false; @@ -43,6 +46,7 @@ void initializeVideoStream(void) { // Clean up the video stream void destroyVideoStream(void) { + PltDestroyCryptoContext(decryptionCtx); destroyVideoDepacketizer(); RtpvCleanupQueue(&rtpQueue); } @@ -80,14 +84,19 @@ static void VideoPingThreadProc(void* context) { // Receive thread proc static void VideoReceiveThreadProc(void* context) { int err; - int bufferSize, receiveSize; + int bufferSize, receiveSize, decryptedSize, minSize; char* buffer; + char* encryptedBuffer; int queueStatus; bool useSelect; int waitingForVideoMs; + bool encrypted; - receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE; - bufferSize = receiveSize + sizeof(RTPV_QUEUE_ENTRY); + encrypted = !!(EncryptionFeaturesEnabled & SS_ENC_VIDEO); + decryptedSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE; + minSize = sizeof(RTP_PACKET) + ((EncryptionFeaturesEnabled & SS_ENC_VIDEO) ? sizeof(ENC_VIDEO_HEADER) : 0); + receiveSize = decryptedSize + ((EncryptionFeaturesEnabled & SS_ENC_VIDEO) ? sizeof(ENC_VIDEO_HEADER) : 0); + bufferSize = decryptedSize + sizeof(RTPV_QUEUE_ENTRY); buffer = NULL; if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) { @@ -99,6 +108,19 @@ static void VideoReceiveThreadProc(void* context) { useSelect = false; } + // Allocate a staging buffer to use for each received packet + if (encrypted) { + encryptedBuffer = (char*)malloc(receiveSize); + if (encryptedBuffer == NULL) { + Limelog("Video Receive: malloc() failed\n"); + ListenerCallbacks.connectionTerminated(-1); + return; + } + } + else { + encryptedBuffer = NULL; + } + waitingForVideoMs = 0; while (!PltIsThreadInterrupted(&receiveThread)) { PRTP_PACKET packet; @@ -108,11 +130,14 @@ static void VideoReceiveThreadProc(void* context) { if (buffer == NULL) { Limelog("Video Receive: malloc() failed\n"); ListenerCallbacks.connectionTerminated(-1); - return; + break; } } - err = recvUdpSocket(rtpSocket, buffer, receiveSize, useSelect); + err = recvUdpSocket(rtpSocket, + encrypted ? encryptedBuffer : buffer, + receiveSize, + useSelect); if (err < 0) { Limelog("Video Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); @@ -153,18 +178,33 @@ static void VideoReceiveThreadProc(void* context) { } #endif - if (err < (int)sizeof(RTP_PACKET)) { + if (err < minSize) { // Runt packet continue; } + // Decrypt the packet into the buffer if encryption is enabled + if (encrypted) { + PENC_VIDEO_HEADER encHeader = (PENC_VIDEO_HEADER)encryptedBuffer; + + if (!PltDecryptMessage(decryptionCtx, ALGORITHM_AES_GCM, 0, + (unsigned char*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey), + encHeader->iv, sizeof(encHeader->iv), + encHeader->tag, sizeof(encHeader->tag), + ((unsigned char*)(encHeader + 1)), err - sizeof(ENC_VIDEO_HEADER), // The ciphertext is after the header + (unsigned char*)buffer, &err)) { + Limelog("Failed to decrypt video packet!\n"); + continue; + } + } + // Convert fields to host byte-order packet = (PRTP_PACKET)&buffer[0]; packet->sequenceNumber = BE16(packet->sequenceNumber); packet->timestamp = BE32(packet->timestamp); packet->ssrc = BE32(packet->ssrc); - queueStatus = RtpvAddPacket(&rtpQueue, packet, err, (PRTPV_QUEUE_ENTRY)&buffer[receiveSize]); + queueStatus = RtpvAddPacket(&rtpQueue, packet, err, (PRTPV_QUEUE_ENTRY)&buffer[decryptedSize]); if (queueStatus == RTPF_RET_QUEUED) { // The queue owns the buffer @@ -175,6 +215,10 @@ static void VideoReceiveThreadProc(void* context) { if (buffer != NULL) { free(buffer); } + + if (encryptedBuffer != NULL) { + free(encryptedBuffer); + } } void notifyKeyFrameReceived(void) {