From 25aa8b41a53393a55c4da60f263451c367efe154 Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 6 Jul 2021 19:11:16 +0200 Subject: [PATCH] Don't keep reinitializing the cipher context for gcm --- sunshine/crypto.cpp | 181 +++++++++++++++++++++++++++++++------------- sunshine/crypto.h | 44 ++++++++--- sunshine/nvhttp.cpp | 6 +- sunshine/stream.cpp | 33 ++++---- 4 files changed, 179 insertions(+), 85 deletions(-) diff --git a/sunshine/crypto.cpp b/sunshine/crypto.cpp index d109cc51..35fc8e98 100644 --- a/sunshine/crypto.cpp +++ b/sunshine/crypto.cpp @@ -54,71 +54,82 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) { return X509_verify_cert_error_string(err_code); } -cipher_t::cipher_t(const crypto::aes_t &key) : ctx { EVP_CIPHER_CTX_new() }, key { key }, padding { true } {} -int cipher_t::decrypt(const std::string_view &cipher, std::vector &plaintext) { - int len; - - auto fg = util::fail_guard([this]() { - EVP_CIPHER_CTX_reset(ctx.get()); - }); - - // Gen 7 servers use 128-bit AES ECB - if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) { - return -1; - } - - EVP_CIPHER_CTX_set_padding(ctx.get(), padding); - - plaintext.resize((cipher.size() + 15) / 16 * 16); - auto size = (int)plaintext.size(); - // Encrypt into the caller's buffer, leaving room for the auth tag to be prepended - if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) { - return -1; - } - - if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) { - return -1; - } - - plaintext.resize(len + size); - return 0; +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; } -int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher, - std::vector &plaintext) { - auto cipher = tagged_cipher.substr(16); - auto tag = tagged_cipher.substr(0, 16); +static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) { + ctx.reset(EVP_CIPHER_CTX_new()); - auto fg = util::fail_guard([this]() { - EVP_CIPHER_CTX_reset(ctx.get()); - }); + if(!ctx) { + return -1; + } if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) { return -1; } - if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) { + if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) { return -1; } - if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) { + if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) { return -1; } - EVP_CIPHER_CTX_set_padding(ctx.get(), padding); + + return 0; +} + +static int init_encrypt_gcm(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_gcm(), nullptr, nullptr, nullptr) != 1) { + return -1; + } + + if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) { + return -1; + } + + if(EVP_EncryptInit_ex(ctx.get(), nullptr, 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; + } + + // Calling with cipher == nullptr results in a parameter change + // without requiring a reallocation of the internal cipher ctx. + if(EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv.data()) != 1) { + return false; + } + + auto cipher = tagged_cipher.substr(16); + auto tag = tagged_cipher.substr(0, 16); + plaintext.resize((cipher.size() + 15) / 16 * 16); int size; - if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) { + if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) { return -1; } - if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast(tag.data())) != 1) { + if(EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast(tag.data())) != 1) { return -1; } int len = size; - if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) { + if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) { return -1; } @@ -126,28 +137,28 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher, return 0; } -int cipher_t::encrypt(const std::string_view &plaintext, std::vector &cipher) { - int len; - - auto fg = util::fail_guard([this]() { - EVP_CIPHER_CTX_reset(ctx.get()); - }); - - // Gen 7 servers use 128-bit AES ECB - if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) { +int gcm_t::encrypt(const std::string_view &plaintext, std::vector &cipher) { + if(!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, &iv, padding)) { return -1; } - EVP_CIPHER_CTX_set_padding(ctx.get(), padding); + // 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; cipher.resize((plaintext.size() + 15) / 16 * 16); auto size = (int)cipher.size(); + // Encrypt into the caller's buffer - if(EVP_EncryptUpdate(ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) { + if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) { return -1; } - if(EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) { + if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) { return -1; } @@ -155,6 +166,70 @@ int cipher_t::encrypt(const std::string_view &plaintext, std::vector &plaintext) { + int len; + + auto fg = util::fail_guard([this]() { + EVP_CIPHER_CTX_reset(decrypt_ctx.get()); + }); + + // Gen 7 servers use 128-bit AES ECB + if(EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) { + return -1; + } + + EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding); + + plaintext.resize((cipher.size() + 15) / 16 * 16); + auto size = (int)plaintext.size(); + // Decrypt into the caller's buffer, leaving room for the auth tag to be prepended + if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) { + return -1; + } + + if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) { + return -1; + } + + plaintext.resize(len + size); + return 0; +} + +int ecb_t::encrypt(const std::string_view &plaintext, std::vector &cipher) { + auto fg = util::fail_guard([this]() { + EVP_CIPHER_CTX_reset(encrypt_ctx.get()); + }); + + // Gen 7 servers use 128-bit AES ECB + if(EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) { + return -1; + } + + EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding); + + int len; + + cipher.resize((plaintext.size() + 15) / 16 * 16); + auto size = (int)cipher.size(); + + // Encrypt into the caller's buffer + if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) { + return -1; + } + + if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) { + return -1; + } + + cipher.resize(len + size); + return 0; +} + +ecb_t::ecb_t(const aes_t &key, bool padding) + : cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {} + +} // namespace cipher + aes_t gen_aes_key(const std::array &salt, const std::string_view &pin) { aes_t key; diff --git a/sunshine/crypto.h b/sunshine/crypto.h index a8599507..596472b1 100644 --- a/sunshine/crypto.h +++ b/sunshine/crypto.h @@ -66,24 +66,46 @@ private: x509_store_ctx_t _cert_ctx; }; +namespace cipher { + class cipher_t { public: - cipher_t(const aes_t &key); - cipher_t(cipher_t &&) noexcept = default; - cipher_t &operator=(cipher_t &&) noexcept = default; + cipher_ctx_t decrypt_ctx; + cipher_ctx_t encrypt_ctx; - int encrypt(const std::string_view &plaintext, std::vector &cipher); - - int decrypt_gcm(aes_t &iv, const std::string_view &cipher, std::vector &plaintext); - int decrypt(const std::string_view &cipher, std::vector &plaintext); - -private: - cipher_ctx_t ctx; aes_t key; -public: bool padding; }; + +class ecb_t : public cipher_t { +public: + ecb_t() = default; + ecb_t(ecb_t &&) noexcept = default; + ecb_t &operator=(ecb_t &&) noexcept = default; + + ecb_t(const aes_t &key, bool padding = true); + + int encrypt(const std::string_view &plaintext, std::vector &cipher); + int decrypt(const std::string_view &cipher, std::vector &plaintext); +}; + +class gcm_t : public cipher_t { +public: + gcm_t() = default; + gcm_t(gcm_t &&) noexcept = default; + gcm_t &operator=(gcm_t &&) noexcept = default; + + gcm_t(const crypto::aes_t &key, const crypto::aes_t &iv, bool padding = true); + + int encrypt(const std::string_view &plaintext, std::vector &cipher); + int decrypt(const std::string_view &cipher, std::vector &plaintext); + + aes_t &get_iv() { return iv; } + + aes_t iv; +}; +} // namespace cipher } // namespace crypto #endif //SUNSHINE_CRYPTO_H diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index ed87884e..500d5ba0 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -222,8 +222,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar auto encrypted_response = util::from_hex_vec(args.at("serverchallengeresp"s), true); std::vector decrypted; - crypto::cipher_t cipher(*sess.cipher_key); - cipher.padding = false; + crypto::cipher::ecb_t cipher(*sess.cipher_key, false); cipher.decrypt(encrypted_response, decrypted); @@ -242,8 +241,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) { auto challenge = util::from_hex_vec(args.at("clientchallenge"s), true); - crypto::cipher_t cipher(*sess.cipher_key); - cipher.padding = false; + crypto::cipher::ecb_t cipher(*sess.cipher_key, false); std::vector decrypted; cipher.decrypt(challenge, decrypted); diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index e329d639..26900ea8 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -224,12 +224,11 @@ struct session_t { } audio; struct { + crypto::cipher::gcm_t cipher; + net::peer_t peer; } control; - crypto::aes_t gcm_key; - crypto::aes_t iv; - safe::mail_raw_t::event_t shutdown_event; safe::signal_t controlEnd; @@ -495,11 +494,10 @@ void controlBroadcastThread(control_server_t *server) { auto tagged_cipher_length = util::endian::big(*(int32_t *)payload.data()); std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length }; - crypto::cipher_t cipher { session->gcm_key }; - cipher.padding = false; - std::vector plaintext; - if(cipher.decrypt_gcm(session->iv, tagged_cipher, plaintext)) { + + auto &cipher = session->control.cipher; + if(cipher.decrypt(tagged_cipher, plaintext)) { // something went wrong :( BOOST_LOG(error) << "Failed to verify tag"sv; @@ -508,8 +506,8 @@ void controlBroadcastThread(control_server_t *server) { return; } - if(tagged_cipher_length >= 16 + session->iv.size()) { - std::copy(payload.end() - 16, payload.end(), std::begin(session->iv)); + if(tagged_cipher_length >= 16 + sizeof(crypto::aes_t)) { + std::copy(payload.end() - 16, payload.end(), std::begin(cipher.get_iv())); } input::print(plaintext.data()); @@ -532,14 +530,13 @@ void controlBroadcastThread(control_server_t *server) { auto tagged_cipher_length = length - 4; std::string_view tagged_cipher { (char *)header->payload(), (size_t)tagged_cipher_length }; + auto &cipher = session->control.cipher; crypto::aes_t iv {}; - iv[0] = (char)seq; - - crypto::cipher_t cipher { session->gcm_key }; - cipher.padding = false; + iv[0] = (char)seq; + cipher.get_iv() = iv; std::vector plaintext; - if(cipher.decrypt_gcm(iv, tagged_cipher, plaintext)) { + if(cipher.decrypt(tagged_cipher, plaintext)) { // something went wrong :( BOOST_LOG(error) << "Failed to verify tag"sv; @@ -1109,9 +1106,11 @@ std::shared_ptr alloc(config_t &config, crypto::aes_t &gcm_key, crypt session->shutdown_event = mail->event(mail::shutdown); - session->config = config; - session->gcm_key = gcm_key; - session->iv = iv; + session->config = config; + + session->control.cipher = crypto::cipher::gcm_t { + gcm_key, iv, false + }; session->video.idr_events = mail->event(mail::idr); session->video.lowseq = 0;