mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-11 06:40:28 +00:00
Implement video encryption
This commit is contained in:
parent
23fb07d4a2
commit
c88fa655f5
@ -838,6 +838,64 @@ keybindings
|
|||||||
|
|
||||||
external_ip = 123.456.789.12
|
external_ip = 123.456.789.12
|
||||||
|
|
||||||
|
`lan_encryption_mode <https://localhost:47990/config/#lan_encryption_mode>`__
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
This determines when encryption will be used when streaming over your local network.
|
||||||
|
|
||||||
|
.. warning:: Encryption can reduce streaming performance, particularly on less powerful hosts and clients.
|
||||||
|
|
||||||
|
**Choices**
|
||||||
|
|
||||||
|
.. table::
|
||||||
|
:widths: auto
|
||||||
|
|
||||||
|
===== ===========
|
||||||
|
Value Description
|
||||||
|
===== ===========
|
||||||
|
0 encryption will not be used
|
||||||
|
1 encryption will be used if the client supports it
|
||||||
|
2 encryption is mandatory and unencrypted connections are rejected
|
||||||
|
===== ===========
|
||||||
|
|
||||||
|
**Default**
|
||||||
|
``0``
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
lan_encryption_mode = 0
|
||||||
|
|
||||||
|
`wan_encryption_mode <https://localhost:47990/config/#wan_encryption_mode>`__
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
This determines when encryption will be used when streaming over the Internet.
|
||||||
|
|
||||||
|
.. warning:: Encryption can reduce streaming performance, particularly on less powerful hosts and clients.
|
||||||
|
|
||||||
|
**Choices**
|
||||||
|
|
||||||
|
.. table::
|
||||||
|
:widths: auto
|
||||||
|
|
||||||
|
===== ===========
|
||||||
|
Value Description
|
||||||
|
===== ===========
|
||||||
|
0 encryption will not be used
|
||||||
|
1 encryption will be used if the client supports it
|
||||||
|
2 encryption is mandatory and unencrypted connections are rejected
|
||||||
|
===== ===========
|
||||||
|
|
||||||
|
**Default**
|
||||||
|
``1``
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
wan_encryption_mode = 1
|
||||||
|
|
||||||
`ping_timeout <https://localhost:47990/config/#ping_timeout>`__
|
`ping_timeout <https://localhost:47990/config/#ping_timeout>`__
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -376,7 +376,10 @@ namespace config {
|
|||||||
APPS_JSON_PATH,
|
APPS_JSON_PATH,
|
||||||
|
|
||||||
20, // fecPercentage
|
20, // fecPercentage
|
||||||
1 // channels
|
1, // channels
|
||||||
|
|
||||||
|
ENCRYPTION_MODE_NEVER, // lan_encryption_mode
|
||||||
|
ENCRYPTION_MODE_OPPORTUNISTIC, // wan_encryption_mode
|
||||||
};
|
};
|
||||||
|
|
||||||
nvhttp_t nvhttp {
|
nvhttp_t nvhttp {
|
||||||
@ -1016,6 +1019,9 @@ namespace config {
|
|||||||
|
|
||||||
int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits<int>::max() });
|
int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits<int>::max() });
|
||||||
|
|
||||||
|
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, { 0, 2 });
|
||||||
|
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, { 0, 2 });
|
||||||
|
|
||||||
path_f(vars, "file_apps", stream.file_apps);
|
path_f(vars, "file_apps", stream.file_apps);
|
||||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
|
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
|
||||||
|
|
||||||
|
@ -76,6 +76,10 @@ namespace config {
|
|||||||
bool install_steam_drivers;
|
bool install_steam_drivers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr int ENCRYPTION_MODE_NEVER = 0; // Never use video encryption, even if the client supports it
|
||||||
|
constexpr int ENCRYPTION_MODE_OPPORTUNISTIC = 1; // Use video encryption if available, but stream without it if not supported
|
||||||
|
constexpr int ENCRYPTION_MODE_MANDATORY = 2; // Always use video encryption and refuse clients that can't encrypt
|
||||||
|
|
||||||
struct stream_t {
|
struct stream_t {
|
||||||
std::chrono::milliseconds ping_timeout;
|
std::chrono::milliseconds ping_timeout;
|
||||||
|
|
||||||
@ -85,6 +89,10 @@ namespace config {
|
|||||||
|
|
||||||
// max unique instances of video and audio streams
|
// max unique instances of video and audio streams
|
||||||
int channels;
|
int channels;
|
||||||
|
|
||||||
|
// Video encryption settings for LAN and WAN streams
|
||||||
|
int lan_encryption_mode;
|
||||||
|
int wan_encryption_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct nvhttp_t {
|
struct nvhttp_t {
|
||||||
|
37
src/rtsp.cpp
37
src/rtsp.cpp
@ -524,6 +524,26 @@ namespace rtsp_stream {
|
|||||||
uint32_t encryption_flags_supported = SS_ENC_CONTROL_V2 | SS_ENC_AUDIO;
|
uint32_t encryption_flags_supported = SS_ENC_CONTROL_V2 | SS_ENC_AUDIO;
|
||||||
uint32_t encryption_flags_requested = SS_ENC_CONTROL_V2;
|
uint32_t encryption_flags_requested = SS_ENC_CONTROL_V2;
|
||||||
|
|
||||||
|
// Determine the encryption desired for this remote endpoint
|
||||||
|
auto nettype = net::from_address(sock.remote_endpoint().address().to_string());
|
||||||
|
int encryption_mode;
|
||||||
|
if (nettype == net::net_e::PC || nettype == net::net_e::LAN) {
|
||||||
|
encryption_mode = config::stream.lan_encryption_mode;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
encryption_mode = config::stream.wan_encryption_mode;
|
||||||
|
}
|
||||||
|
if (encryption_mode != config::ENCRYPTION_MODE_NEVER) {
|
||||||
|
// Advertise support for video encryption if it's not disabled
|
||||||
|
encryption_flags_supported |= SS_ENC_VIDEO;
|
||||||
|
|
||||||
|
// If it's mandatory, also request it to enable use if the client
|
||||||
|
// didn't explicitly opt in, but it otherwise has support.
|
||||||
|
if (encryption_mode == config::ENCRYPTION_MODE_MANDATORY) {
|
||||||
|
encryption_flags_requested |= SS_ENC_VIDEO | SS_ENC_AUDIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Report supported and required encryption flags
|
// Report supported and required encryption flags
|
||||||
ss << "a=x-ss-general.encryptionSupported:" << encryption_flags_supported << std::endl;
|
ss << "a=x-ss-general.encryptionSupported:" << encryption_flags_supported << std::endl;
|
||||||
ss << "a=x-ss-general.encryptionRequested:" << encryption_flags_requested << std::endl;
|
ss << "a=x-ss-general.encryptionRequested:" << encryption_flags_requested << std::endl;
|
||||||
@ -811,6 +831,23 @@ namespace rtsp_stream {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that any required encryption is enabled
|
||||||
|
auto nettype = net::from_address(sock.remote_endpoint().address().to_string());
|
||||||
|
int encryption_mode;
|
||||||
|
if (nettype == net::net_e::PC || nettype == net::net_e::LAN) {
|
||||||
|
encryption_mode = config::stream.lan_encryption_mode;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
encryption_mode = config::stream.wan_encryption_mode;
|
||||||
|
}
|
||||||
|
if (encryption_mode == config::ENCRYPTION_MODE_MANDATORY &&
|
||||||
|
(config.encryptionFlagsEnabled & (SS_ENC_VIDEO | SS_ENC_AUDIO)) != (SS_ENC_VIDEO | SS_ENC_AUDIO)) {
|
||||||
|
BOOST_LOG(error) << "Rejecting client that cannot comply with mandatory encryption requirement"sv;
|
||||||
|
|
||||||
|
respond(sock, &option, 403, "Forbidden", req->sequenceNumber, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto session = stream::session::alloc(config, *launch_session);
|
auto session = stream::session::alloc(config, *launch_session);
|
||||||
|
|
||||||
auto slot = server->accept(session);
|
auto slot = server->accept(session);
|
||||||
|
@ -121,6 +121,17 @@ namespace stream {
|
|||||||
NV_VIDEO_PACKET packet;
|
NV_VIDEO_PACKET packet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct video_packet_enc_prefix_t {
|
||||||
|
video_packet_raw_t *
|
||||||
|
payload() {
|
||||||
|
return (video_packet_raw_t *) (this + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint8_t iv[12]; // 12-byte IV is ideal for AES-GCM
|
||||||
|
std::uint32_t unused;
|
||||||
|
std::uint8_t tag[16];
|
||||||
|
};
|
||||||
|
|
||||||
struct audio_packet_raw_t {
|
struct audio_packet_raw_t {
|
||||||
uint8_t *
|
uint8_t *
|
||||||
payload() {
|
payload() {
|
||||||
@ -354,6 +365,9 @@ namespace stream {
|
|||||||
int lowseq;
|
int lowseq;
|
||||||
udp::endpoint peer;
|
udp::endpoint peer;
|
||||||
|
|
||||||
|
std::optional<crypto::cipher::gcm_t> cipher;
|
||||||
|
std::uint64_t gcm_iv_counter;
|
||||||
|
|
||||||
safe::mail_raw_t::event_t<bool> idr_events;
|
safe::mail_raw_t::event_t<bool> idr_events;
|
||||||
safe::mail_raw_t::event_t<std::pair<int64_t, int64_t>> invalidate_ref_frames_events;
|
safe::mail_raw_t::event_t<std::pair<int64_t, int64_t>> invalidate_ref_frames_events;
|
||||||
|
|
||||||
@ -588,16 +602,17 @@ namespace stream {
|
|||||||
size_t percentage;
|
size_t percentage;
|
||||||
|
|
||||||
size_t blocksize;
|
size_t blocksize;
|
||||||
|
size_t prefixsize;
|
||||||
util::buffer_t<char> shards;
|
util::buffer_t<char> shards;
|
||||||
|
|
||||||
char *
|
char *
|
||||||
data(size_t el) {
|
data(size_t el) {
|
||||||
return &shards[el * blocksize];
|
return &shards[(el + 1) * prefixsize + el * blocksize];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view
|
char *
|
||||||
operator[](size_t el) const {
|
prefix(size_t el) {
|
||||||
return { &shards[el * blocksize], blocksize };
|
return &shards[el * (prefixsize + blocksize)];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
@ -607,7 +622,7 @@ namespace stream {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static fec_t
|
static fec_t
|
||||||
encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage, size_t minparityshards) {
|
encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage, size_t minparityshards, size_t prefixsize) {
|
||||||
auto payload_size = payload.size();
|
auto payload_size = payload.size();
|
||||||
|
|
||||||
auto pad = payload_size % blocksize != 0;
|
auto pad = payload_size % blocksize != 0;
|
||||||
@ -634,15 +649,21 @@ namespace stream {
|
|||||||
fecpercentage = 0;
|
fecpercentage = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
util::buffer_t<char> shards { nr_shards * blocksize };
|
util::buffer_t<char> shards { nr_shards * (blocksize + prefixsize) };
|
||||||
util::buffer_t<uint8_t *> shards_p { nr_shards };
|
util::buffer_t<uint8_t *> shards_p { nr_shards };
|
||||||
|
|
||||||
// copy payload + padding
|
auto next = std::begin(payload);
|
||||||
auto next = std::copy(std::begin(payload), std::end(payload), std::begin(shards));
|
|
||||||
std::fill(next, std::end(shards), 0); // padding with zero
|
|
||||||
|
|
||||||
for (auto x = 0; x < nr_shards; ++x) {
|
for (auto x = 0; x < nr_shards; ++x) {
|
||||||
shards_p[x] = (uint8_t *) &shards[x * blocksize];
|
shards_p[x] = (uint8_t *) &shards[(x + 1) * prefixsize + x * blocksize];
|
||||||
|
|
||||||
|
auto copy_len = std::min<size_t>(blocksize, std::end(payload) - next);
|
||||||
|
std::copy_n(next, copy_len, shards_p[x]);
|
||||||
|
if (copy_len < blocksize) {
|
||||||
|
// Zero any additional space after the end of the payload
|
||||||
|
std::fill_n(shards_p[x] + copy_len, blocksize - copy_len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
next += copy_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data_shards + parity_shards <= DATA_SHARDS_MAX) {
|
if (data_shards + parity_shards <= DATA_SHARDS_MAX) {
|
||||||
@ -657,6 +678,7 @@ namespace stream {
|
|||||||
nr_shards,
|
nr_shards,
|
||||||
fecpercentage,
|
fecpercentage,
|
||||||
blocksize,
|
blocksize,
|
||||||
|
prefixsize,
|
||||||
std::move(shards)
|
std::move(shards)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1337,7 +1359,9 @@ namespace stream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto shards = fec::encode(current_payload, blocksize, fecPercentage, session->config.minRequiredFecPackets);
|
// If video encryption is enabled, we allocate space for the encryption header before each shard
|
||||||
|
auto shards = fec::encode(current_payload, blocksize, fecPercentage, session->config.minRequiredFecPackets,
|
||||||
|
session->video.cipher ? sizeof(video_packet_enc_prefix_t) : 0);
|
||||||
|
|
||||||
// set FEC info now that we know for sure what our percentage will be for this frame
|
// set FEC info now that we know for sure what our percentage will be for this frame
|
||||||
for (auto x = 0; x < shards.size(); ++x) {
|
for (auto x = 0; x < shards.size(); ++x) {
|
||||||
@ -1358,12 +1382,34 @@ namespace stream {
|
|||||||
|
|
||||||
inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex;
|
inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex;
|
||||||
inspect->packet.frameIndex = packet->frame_index();
|
inspect->packet.frameIndex = packet->frame_index();
|
||||||
|
|
||||||
|
// Encrypt this shard if video encryption is enabled
|
||||||
|
if (session->video.cipher) {
|
||||||
|
// We use the deterministic IV construction algorithm specified in NIST SP 800-38D
|
||||||
|
// Section 8.2.1. The sequence number is our "invocation" field and the 'V' in the
|
||||||
|
// high bytes is the "fixed" field. Because each client provides their own unique
|
||||||
|
// key, our values in the fixed field need only uniquely identify each independent
|
||||||
|
// use of the client's key with AES-GCM in our code.
|
||||||
|
//
|
||||||
|
// The IV counter is 64 bits long which allows for 2^64 encrypted video packets
|
||||||
|
// to be sent to each client before the IV repeats.
|
||||||
|
crypto::aes_t iv(12);
|
||||||
|
std::copy_n((uint8_t *) &session->video.gcm_iv_counter, sizeof(session->video.gcm_iv_counter), std::begin(iv));
|
||||||
|
iv[11] = 'V'; // Video stream
|
||||||
|
session->video.gcm_iv_counter++;
|
||||||
|
|
||||||
|
// Encrypt the target buffer in place
|
||||||
|
auto *prefix = (video_packet_enc_prefix_t *) shards.prefix(x);
|
||||||
|
prefix->unused = 0;
|
||||||
|
std::copy(std::begin(iv), std::end(iv), prefix->iv);
|
||||||
|
session->video.cipher->encrypt(std::string_view { (char *) inspect, (size_t) blocksize }, prefix->tag, &iv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto peer_address = session->video.peer.address();
|
auto peer_address = session->video.peer.address();
|
||||||
auto batch_info = platf::batched_send_info_t {
|
auto batch_info = platf::batched_send_info_t {
|
||||||
shards.shards.begin(),
|
shards.shards.begin(),
|
||||||
shards.blocksize,
|
shards.prefixsize + shards.blocksize,
|
||||||
shards.nr_shards,
|
shards.nr_shards,
|
||||||
(uintptr_t) sock.native_handle(),
|
(uintptr_t) sock.native_handle(),
|
||||||
peer_address,
|
peer_address,
|
||||||
@ -1377,8 +1423,8 @@ namespace stream {
|
|||||||
BOOST_LOG(verbose) << "Falling back to unbatched send"sv;
|
BOOST_LOG(verbose) << "Falling back to unbatched send"sv;
|
||||||
for (auto x = 0; x < shards.size(); ++x) {
|
for (auto x = 0; x < shards.size(); ++x) {
|
||||||
auto send_info = platf::send_info_t {
|
auto send_info = platf::send_info_t {
|
||||||
shards[x].data(),
|
shards.prefix(x),
|
||||||
shards[x].size(),
|
shards.prefixsize + shards.blocksize,
|
||||||
(uintptr_t) sock.native_handle(),
|
(uintptr_t) sock.native_handle(),
|
||||||
peer_address,
|
peer_address,
|
||||||
session->video.peer.port(),
|
session->video.peer.port(),
|
||||||
@ -1836,6 +1882,13 @@ namespace stream {
|
|||||||
session->video.invalidate_ref_frames_events = mail->event<std::pair<int64_t, int64_t>>(mail::invalidate_ref_frames);
|
session->video.invalidate_ref_frames_events = mail->event<std::pair<int64_t, int64_t>>(mail::invalidate_ref_frames);
|
||||||
session->video.lowseq = 0;
|
session->video.lowseq = 0;
|
||||||
session->video.ping_payload = launch_session.av_ping_payload;
|
session->video.ping_payload = launch_session.av_ping_payload;
|
||||||
|
if (config.encryptionFlagsEnabled & SS_ENC_VIDEO) {
|
||||||
|
BOOST_LOG(info) << "Video encryption enabled"sv;
|
||||||
|
session->video.cipher = crypto::cipher::gcm_t {
|
||||||
|
launch_session.gcm_key, false
|
||||||
|
};
|
||||||
|
session->video.gcm_iv_counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto max_block_size = crypto::cipher::round_to_pkcs7_padded(2048);
|
constexpr auto max_block_size = crypto::cipher::round_to_pkcs7_padded(2048);
|
||||||
|
|
||||||
|
@ -668,6 +668,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- LAN Encryption Mode -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="lan_encryption_mode" class="form-label">LAN Encryption Mode</label>
|
||||||
|
<select id="lan_encryption_mode" class="form-select" v-model="config.lan_encryption_mode">
|
||||||
|
<option value="0">
|
||||||
|
Disabled (default)
|
||||||
|
</option>
|
||||||
|
<option value="1">
|
||||||
|
Enabled for supported clients
|
||||||
|
</option>
|
||||||
|
<option value="2">
|
||||||
|
Required for all clients
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">
|
||||||
|
This determines when encryption will be used when streaming over your local network.<br>
|
||||||
|
Encryption can reduce streaming performance, particularly on less powerful hosts and clients.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- WAN Encryption Mode -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="wan_encryption_mode" class="form-label">WAN Encryption Mode</label>
|
||||||
|
<select id="wan_encryption_mode" class="form-select" v-model="config.wan_encryption_mode">
|
||||||
|
<option value="0">
|
||||||
|
Disabled
|
||||||
|
</option>
|
||||||
|
<option value="1">
|
||||||
|
Enabled for supported clients (default)
|
||||||
|
</option>
|
||||||
|
<option value="2">
|
||||||
|
Required for all clients
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">
|
||||||
|
This determines when encryption will be used when streaming over the Internet.<br>
|
||||||
|
Encryption can reduce streaming performance, particularly on less powerful hosts and clients.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Ping Timeout -->
|
<!-- Ping Timeout -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ping_timeout" class="form-label">Ping Timeout</label>
|
<label for="ping_timeout" class="form-label">Ping Timeout</label>
|
||||||
@ -1155,6 +1195,8 @@
|
|||||||
"origin_web_ui_allowed": "lan",
|
"origin_web_ui_allowed": "lan",
|
||||||
"upnp": "disabled",
|
"upnp": "disabled",
|
||||||
"external_ip": "",
|
"external_ip": "",
|
||||||
|
"lan_encryption_mode": 0,
|
||||||
|
"wan_encryption_mode": 1,
|
||||||
"ping_timeout": 10000,
|
"ping_timeout": 10000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user