mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-28 22:13:29 +00:00
Encrypt audio when requested by Moonlight
This commit is contained in:
parent
7f9467d759
commit
cbd2a8269c
@ -4,6 +4,7 @@
|
||||
|
||||
#include "crypto.h"
|
||||
#include <openssl/pem.h>
|
||||
|
||||
namespace crypto {
|
||||
using big_num_t = util::safe_ptr<BIGNUM, BN_free>;
|
||||
//using rsa_t = util::safe_ptr<RSA, RSA_free>;
|
||||
@ -20,7 +21,7 @@ void cert_chain_t::add(x509_t &&cert) {
|
||||
static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
|
||||
int err_code = X509_STORE_CTX_get_error(ctx);
|
||||
|
||||
switch (err_code) {
|
||||
switch(err_code) {
|
||||
//FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get mmonlight-embedded to work on the raspberry pi
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
||||
return 1;
|
||||
@ -72,10 +73,6 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
}
|
||||
|
||||
namespace cipher {
|
||||
gcm_t::gcm_t(const crypto::aes_t &key, const crypto::aes_t &iv, bool padding)
|
||||
: cipher_t { nullptr, nullptr, key, padding } {
|
||||
this->iv = iv;
|
||||
}
|
||||
|
||||
static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
@ -120,6 +117,19 @@ static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool paddi
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
if(!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, &iv, padding)) {
|
||||
return -1;
|
||||
@ -242,9 +252,44 @@ int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t>
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
|
||||
if(!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calling with cipher == nullptr results in a parameter change
|
||||
// without requiring a reallocation of the internal cipher ctx.
|
||||
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int len;
|
||||
|
||||
int size = plaintext.size(); //round_to_pkcs7_padded(plaintext.size());
|
||||
|
||||
// Encrypt into the caller's buffer
|
||||
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return size + len;
|
||||
}
|
||||
|
||||
ecb_t::ecb_t(const aes_t &key, bool padding)
|
||||
: cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
|
||||
|
||||
cbc_t::cbc_t(const aes_t &key, bool padding)
|
||||
: cipher_t { nullptr, nullptr, key, padding } {}
|
||||
|
||||
gcm_t::gcm_t(const crypto::aes_t &key, const crypto::aes_t &iv, bool padding)
|
||||
: cipher_t { nullptr, nullptr, key, padding } {
|
||||
this->iv = iv;
|
||||
}
|
||||
|
||||
} // namespace cipher
|
||||
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
|
@ -67,6 +67,9 @@ private:
|
||||
};
|
||||
|
||||
namespace cipher {
|
||||
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
|
||||
return ((size + 15) / 16) * 16;
|
||||
}
|
||||
|
||||
class cipher_t {
|
||||
public:
|
||||
@ -105,6 +108,23 @@ public:
|
||||
|
||||
aes_t iv;
|
||||
};
|
||||
|
||||
class cbc_t : public cipher_t {
|
||||
public:
|
||||
cbc_t() = default;
|
||||
cbc_t(cbc_t &&) noexcept = default;
|
||||
cbc_t &operator=(cbc_t &&) noexcept = default;
|
||||
|
||||
cbc_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
/**
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
};
|
||||
} // namespace cipher
|
||||
} // namespace crypto
|
||||
|
||||
|
@ -107,14 +107,35 @@ struct audio_fec_packet_raw_t {
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
|
||||
return ((size + 15) / 16) * 16;
|
||||
}
|
||||
constexpr std::size_t MAX_AUDIO_PACKET_SIZE = 1400;
|
||||
|
||||
using rh_t = util::safe_ptr<reed_solomon, reed_solomon_release>;
|
||||
using video_packet_t = util::c_ptr<video_packet_raw_t>;
|
||||
using audio_packet_t = util::c_ptr<audio_packet_raw_t>;
|
||||
using audio_fec_packet_t = util::c_ptr<audio_fec_packet_raw_t>;
|
||||
using audio_aes_t = std::array<char, round_to_pkcs7_padded(MAX_AUDIO_PACKET_SIZE)>;
|
||||
|
||||
using message_queue_t = std::shared_ptr<safe::queue_t<std::pair<std::uint16_t, std::string>>>;
|
||||
using message_queue_queue_t = std::shared_ptr<safe::queue_t<std::tuple<socket_e, asio::ip::address, message_queue_t>>>;
|
||||
|
||||
// return bytes written on success
|
||||
// return -1 on error
|
||||
int encode_audio(int featureSet, const audio::buffer_t &plaintext, audio_packet_t &destination, std::uint32_t avRiKeyIv, crypto::cipher::cbc_t &cbc) {
|
||||
// If encryption isn't enabled
|
||||
if(!(featureSet & 0x20)) {
|
||||
std::copy(std::begin(plaintext), std::end(plaintext), destination->payload());
|
||||
return plaintext.size();
|
||||
}
|
||||
|
||||
crypto::aes_t iv {};
|
||||
*(std::uint32_t *)iv.data() = util::endian::big<std::uint32_t>(avRiKeyIv + destination->rtp.sequenceNumber);
|
||||
|
||||
return cbc.encrypt(std::string_view { (char *)std::begin(plaintext), plaintext.size() }, destination->payload(), &iv);
|
||||
}
|
||||
|
||||
static inline void while_starting_do_nothing(std::atomic<session::state_e> &state) {
|
||||
while(state.load(std::memory_order_acquire) == session::state_e::STARTING) {
|
||||
std::this_thread::sleep_for(1ms);
|
||||
@ -219,7 +240,11 @@ struct session_t {
|
||||
} video;
|
||||
|
||||
struct {
|
||||
crypto::cipher::cbc_t cipher;
|
||||
|
||||
std::uint16_t sequenceNumber;
|
||||
// avRiKeyId == util::endian::big(First (sizeof(avRiKeyId)) bytes of launch_session->iv)
|
||||
std::uint32_t avRiKeyId;
|
||||
std::uint32_t timestamp;
|
||||
udp::endpoint peer;
|
||||
} audio;
|
||||
@ -610,23 +635,27 @@ void controlBroadcastThread(control_server_t *server) {
|
||||
if(proc::proc.running() == -1) {
|
||||
BOOST_LOG(debug) << "Process terminated"sv;
|
||||
|
||||
std::uint16_t reason = 0x0100;
|
||||
|
||||
std::array<std::uint16_t, 2> payload;
|
||||
payload[0] = packetTypes[IDX_TERMINATION];
|
||||
payload[1] = reason;
|
||||
|
||||
server->send(std::string_view { (char *)payload.data(), payload.size() });
|
||||
|
||||
auto lg = server->_map_addr_session.lock();
|
||||
for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) {
|
||||
auto session = pos->second.second;
|
||||
session->shutdown_event->raise(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
server->iterate(500ms);
|
||||
}
|
||||
|
||||
// Let all remaining connections know the server is shutting down
|
||||
std::uint16_t reason = 0x0100;
|
||||
|
||||
std::array<std::uint16_t, 2> payload;
|
||||
payload[0] = packetTypes[IDX_TERMINATION];
|
||||
payload[1] = reason;
|
||||
|
||||
server->send(std::string_view { (char *)payload.data(), payload.size() });
|
||||
|
||||
auto lg = server->_map_addr_session.lock();
|
||||
for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) {
|
||||
auto session = pos->second.second;
|
||||
session->shutdown_event->raise(true);
|
||||
session->controlEnd.raise(true);
|
||||
}
|
||||
}
|
||||
|
||||
void recvThread(broadcast_ctx_t &ctx) {
|
||||
@ -819,8 +848,8 @@ void audioBroadcastThread(udp::socket &sock) {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
|
||||
auto packets = mail::man->queue<audio::packet_t>(mail::audio_packets);
|
||||
|
||||
auto max_block_size = 2048;
|
||||
util::buffer_t<char> shards { RTPA_TOTAL_SHARDS * 2048 };
|
||||
constexpr auto max_block_size = crypto::cipher::round_to_pkcs7_padded(2048);
|
||||
util::buffer_t<char> shards { RTPA_TOTAL_SHARDS * max_block_size };
|
||||
util::buffer_t<uint8_t *> shards_p { RTPA_TOTAL_SHARDS };
|
||||
|
||||
for(auto x = 0; x < RTPA_TOTAL_SHARDS; ++x) {
|
||||
@ -863,16 +892,26 @@ void audioBroadcastThread(udp::socket &sock) {
|
||||
auto sequenceNumber = session->audio.sequenceNumber;
|
||||
auto timestamp = session->audio.timestamp;
|
||||
|
||||
// This will be mapped to big-endianness later
|
||||
// For now, encode_audio needs it to be the proper sequenceNumber
|
||||
audio_packet->rtp.sequenceNumber = sequenceNumber;
|
||||
|
||||
auto bytes = encode_audio(session->config.featureFlags, packet_data, audio_packet, session->audio.avRiKeyId, session->audio.cipher);
|
||||
if(bytes < 0) {
|
||||
BOOST_LOG(error) << "Couldn't encode audio packet"sv;
|
||||
break;
|
||||
}
|
||||
|
||||
audio_packet->rtp.sequenceNumber = util::endian::big(sequenceNumber);
|
||||
audio_packet->rtp.timestamp = util::endian::big(timestamp);
|
||||
|
||||
session->audio.sequenceNumber++;
|
||||
session->audio.timestamp += session->config.audio.packetDuration;
|
||||
|
||||
std::copy(std::begin(packet_data), std::end(packet_data), audio_packet->payload());
|
||||
std::copy(std::begin(packet_data), std::end(packet_data), shards_p[sequenceNumber % RTPA_DATA_SHARDS]);
|
||||
std::copy_n(audio_packet->payload(), bytes, shards_p[sequenceNumber % RTPA_DATA_SHARDS]);
|
||||
sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + bytes), session->audio.peer);
|
||||
|
||||
|
||||
sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer);
|
||||
BOOST_LOG(verbose) << "Audio ["sv << sequenceNumber << "] :: send..."sv;
|
||||
|
||||
// initialize the FEC header at the beginning of the FEC block
|
||||
@ -883,13 +922,13 @@ void audioBroadcastThread(udp::socket &sock) {
|
||||
|
||||
// generate parity shards at the end of the FEC block
|
||||
if((sequenceNumber + 1) % RTPA_DATA_SHARDS == 0) {
|
||||
reed_solomon_encode(rs.get(), shards_p.begin(), RTPA_TOTAL_SHARDS, packet_data.size());
|
||||
reed_solomon_encode(rs.get(), shards_p.begin(), RTPA_TOTAL_SHARDS, bytes);
|
||||
|
||||
for(auto x = 0; x < RTPA_FEC_SHARDS; ++x) {
|
||||
audio_fec_packet->rtp.sequenceNumber = util::endian::big(sequenceNumber + x + 1);
|
||||
audio_fec_packet->rtp.sequenceNumber = util::endian::big<std::uint16_t>(sequenceNumber + x + 1);
|
||||
audio_fec_packet->fecHeader.fecShardIndex = x;
|
||||
memcpy(audio_fec_packet->payload(), shards_p[RTPA_DATA_SHARDS + x], packet_data.size());
|
||||
sock.send_to(asio::buffer((char *)audio_fec_packet.get(), sizeof(audio_fec_packet_raw_t) + packet_data.size()), session->audio.peer);
|
||||
memcpy(audio_fec_packet->payload(), shards_p[RTPA_DATA_SHARDS + x], bytes);
|
||||
sock.send_to(asio::buffer((char *)audio_fec_packet.get(), sizeof(audio_fec_packet_raw_t) + bytes), session->audio.peer);
|
||||
BOOST_LOG(verbose) << "Audio FEC ["sv << (sequenceNumber & ~(RTPA_DATA_SHARDS - 1)) << ' ' << x << "] :: send..."sv;
|
||||
}
|
||||
}
|
||||
@ -1122,6 +1161,12 @@ std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypt
|
||||
session->video.idr_events = mail->event<bool>(mail::idr);
|
||||
session->video.lowseq = 0;
|
||||
|
||||
|
||||
session->audio.cipher = crypto::cipher::cbc_t {
|
||||
gcm_key, true
|
||||
};
|
||||
|
||||
session->audio.avRiKeyId = util::endian::big(*(std::uint32_t *)iv.data());
|
||||
session->audio.sequenceNumber = 0;
|
||||
session->audio.timestamp = 0;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user