Don't keep reinitializing the cipher context for gcm

This commit is contained in:
loki 2021-07-06 19:11:16 +02:00
parent a587338701
commit 25aa8b41a5
4 changed files with 179 additions and 85 deletions

View File

@ -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<std::uint8_t> &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<std::uint8_t> &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<std::uint8_t> &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<char *>(tag.data())) != 1) {
if(EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(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<std::uint8_t> &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<std::uint8_t> &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<std::uint8_
return 0;
}
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &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<std::uint8_t> &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<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key;

View File

@ -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<std::uint8_t> &cipher);
int decrypt_gcm(aes_t &iv, const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &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<std::uint8_t> &cipher);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &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<std::uint8_t> &cipher);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
aes_t &get_iv() { return iv; }
aes_t iv;
};
} // namespace cipher
} // namespace crypto
#endif //SUNSHINE_CRYPTO_H

View File

@ -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<uint8_t> 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<uint8_t> decrypted;
cipher.decrypt(challenge, decrypted);

View File

@ -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<bool> 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<uint8_t> 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<uint8_t> 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<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypt
session->shutdown_event = mail->event<bool>(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<video::idr_t>(mail::idr);
session->video.lowseq = 0;