diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index a0e78ada..ddf41bc4 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -4,6 +4,7 @@ #include "crypto.h" #include + namespace crypto { using big_num_t = util::safe_ptr; //using rsa_t = util::safe_ptr; @@ -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 &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 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 &salt, const std::string_view &pin) { diff --git a/sunshine/crypto.h b/sunshine/crypto.h index 596472b1..9c925845 100644 --- a/sunshine/crypto.h +++ b/sunshine/crypto.h @@ -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 diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index 6646a882..98d2de47 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -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; using video_packet_t = util::c_ptr; using audio_packet_t = util::c_ptr; using audio_fec_packet_t = util::c_ptr; +using audio_aes_t = std::array; using message_queue_t = std::shared_ptr>>; using message_queue_queue_t = std::shared_ptr>>; +// 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(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 &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 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 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(mail::broadcast_shutdown); auto packets = mail::man->queue(mail::audio_packets); - auto max_block_size = 2048; - util::buffer_t shards { RTPA_TOTAL_SHARDS * 2048 }; + constexpr auto max_block_size = crypto::cipher::round_to_pkcs7_padded(2048); + util::buffer_t shards { RTPA_TOTAL_SHARDS * max_block_size }; util::buffer_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(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 alloc(config_t &config, crypto::aes_t &gcm_key, crypt session->video.idr_events = mail->event(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;