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
#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

View File

@ -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);
}

View File

@ -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

View File

@ -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) {