Add video encryption support

This commit is contained in:
Cameron Gutman 2024-01-14 14:04:52 -06:00
parent 6083a75d1b
commit b74b6e883c
4 changed files with 69 additions and 7 deletions

View File

@ -32,6 +32,7 @@ extern "C" {
// Values for 'encryptionFlags' field below // Values for 'encryptionFlags' field below
#define ENCFLG_NONE 0x00000000 #define ENCFLG_NONE 0x00000000
#define ENCFLG_AUDIO 0x00000001 #define ENCFLG_AUDIO 0x00000001
#define ENCFLG_VIDEO 0x00000002
#define ENCFLG_ALL 0xFFFFFFFF #define ENCFLG_ALL 0xFFFFFFFF
// This function returns a string that you SHOULD append to the /launch and /resume // This function returns a string that you SHOULD append to the /launch and /resume

View File

@ -276,6 +276,18 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
if (EncryptionFeaturesSupported & SS_ENC_CONTROL_V2) { if (EncryptionFeaturesSupported & SS_ENC_CONTROL_V2) {
EncryptionFeaturesEnabled |= 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); snprintf(payloadStr, sizeof(payloadStr), "%u", EncryptionFeaturesEnabled);
err |= addAttributeString(&optionHead, "x-ss-general.encryptionEnabled", payloadStr); err |= addAttributeString(&optionHead, "x-ss-general.encryptionEnabled", payloadStr);
} }

View File

@ -9,6 +9,11 @@ typedef struct _QUEUED_DECODE_UNIT {
#pragma pack(push, 1) #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_CONTAINS_PIC_DATA 0x1
#define FLAG_EOF 0x2 #define FLAG_EOF 0x2
#define FLAG_SOF 0x4 #define FLAG_SOF 0x4

View File

@ -10,6 +10,8 @@ static RTP_VIDEO_QUEUE rtpQueue;
static SOCKET rtpSocket = INVALID_SOCKET; static SOCKET rtpSocket = INVALID_SOCKET;
static SOCKET firstFrameSocket = INVALID_SOCKET; static SOCKET firstFrameSocket = INVALID_SOCKET;
static PPLT_CRYPTO_CONTEXT decryptionCtx;
static PLT_THREAD udpPingThread; static PLT_THREAD udpPingThread;
static PLT_THREAD receiveThread; static PLT_THREAD receiveThread;
static PLT_THREAD decoderThread; static PLT_THREAD decoderThread;
@ -36,6 +38,7 @@ static bool receivedFullFrame;
void initializeVideoStream(void) { void initializeVideoStream(void) {
initializeVideoDepacketizer(StreamConfig.packetSize); initializeVideoDepacketizer(StreamConfig.packetSize);
RtpvInitializeQueue(&rtpQueue); RtpvInitializeQueue(&rtpQueue);
decryptionCtx = PltCreateCryptoContext();
receivedDataFromPeer = false; receivedDataFromPeer = false;
firstDataTimeMs = 0; firstDataTimeMs = 0;
receivedFullFrame = false; receivedFullFrame = false;
@ -43,6 +46,7 @@ void initializeVideoStream(void) {
// Clean up the video stream // Clean up the video stream
void destroyVideoStream(void) { void destroyVideoStream(void) {
PltDestroyCryptoContext(decryptionCtx);
destroyVideoDepacketizer(); destroyVideoDepacketizer();
RtpvCleanupQueue(&rtpQueue); RtpvCleanupQueue(&rtpQueue);
} }
@ -80,14 +84,19 @@ static void VideoPingThreadProc(void* context) {
// Receive thread proc // Receive thread proc
static void VideoReceiveThreadProc(void* context) { static void VideoReceiveThreadProc(void* context) {
int err; int err;
int bufferSize, receiveSize; int bufferSize, receiveSize, decryptedSize, minSize;
char* buffer; char* buffer;
char* encryptedBuffer;
int queueStatus; int queueStatus;
bool useSelect; bool useSelect;
int waitingForVideoMs; int waitingForVideoMs;
bool encrypted;
receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE; encrypted = !!(EncryptionFeaturesEnabled & SS_ENC_VIDEO);
bufferSize = receiveSize + sizeof(RTPV_QUEUE_ENTRY); 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; buffer = NULL;
if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) { if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) {
@ -99,6 +108,19 @@ static void VideoReceiveThreadProc(void* context) {
useSelect = false; 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; waitingForVideoMs = 0;
while (!PltIsThreadInterrupted(&receiveThread)) { while (!PltIsThreadInterrupted(&receiveThread)) {
PRTP_PACKET packet; PRTP_PACKET packet;
@ -108,11 +130,14 @@ static void VideoReceiveThreadProc(void* context) {
if (buffer == NULL) { if (buffer == NULL) {
Limelog("Video Receive: malloc() failed\n"); Limelog("Video Receive: malloc() failed\n");
ListenerCallbacks.connectionTerminated(-1); ListenerCallbacks.connectionTerminated(-1);
return; break;
} }
} }
err = recvUdpSocket(rtpSocket, buffer, receiveSize, useSelect); err = recvUdpSocket(rtpSocket,
encrypted ? encryptedBuffer : buffer,
receiveSize,
useSelect);
if (err < 0) { if (err < 0) {
Limelog("Video Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError()); Limelog("Video Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail()); ListenerCallbacks.connectionTerminated(LastSocketFail());
@ -153,18 +178,33 @@ static void VideoReceiveThreadProc(void* context) {
} }
#endif #endif
if (err < (int)sizeof(RTP_PACKET)) { if (err < minSize) {
// Runt packet // Runt packet
continue; 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 // Convert fields to host byte-order
packet = (PRTP_PACKET)&buffer[0]; packet = (PRTP_PACKET)&buffer[0];
packet->sequenceNumber = BE16(packet->sequenceNumber); packet->sequenceNumber = BE16(packet->sequenceNumber);
packet->timestamp = BE32(packet->timestamp); packet->timestamp = BE32(packet->timestamp);
packet->ssrc = BE32(packet->ssrc); 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) { if (queueStatus == RTPF_RET_QUEUED) {
// The queue owns the buffer // The queue owns the buffer
@ -175,6 +215,10 @@ static void VideoReceiveThreadProc(void* context) {
if (buffer != NULL) { if (buffer != NULL) {
free(buffer); free(buffer);
} }
if (encryptedBuffer != NULL) {
free(encryptedBuffer);
}
} }
void notifyKeyFrameReceived(void) { void notifyKeyFrameReceived(void) {