Add support for audio stream encryption

Clients must opt-in using the new encryptionFlags field
This commit is contained in:
Cameron Gutman 2021-04-22 00:20:17 -05:00
parent 55cf1f8d30
commit db81f1e512
6 changed files with 71 additions and 4 deletions

View File

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

View File

@ -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] = {

View File

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

View File

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

View File

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

View File

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