From ab86a1f0c3b86050e65fc5b7ac1e88a00f0d228c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Smr=C5=BE?= Date: Mon, 27 Jan 2020 21:25:39 +0100 Subject: Encrypted channels --- src/channel.cpp | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 src/channel.cpp (limited to 'src/channel.cpp') diff --git a/src/channel.cpp b/src/channel.cpp new file mode 100644 index 0000000..38d263e --- /dev/null +++ b/src/channel.cpp @@ -0,0 +1,243 @@ +#include "channel.h" + +#include +#include + +#include + +using std::remove_const; +using std::runtime_error; + +using namespace erebos; + +Ref ChannelRequestData::store(const Storage & st) const +{ + vector items; + + for (const auto p : peers) + items.emplace_back("peer", p); + items.emplace_back("enc", "aes-128-gcm"); + items.emplace_back("key", key); + + return st.storeObject(Record(std::move(items))); +} + +optional ChannelRequestData::load(const Ref & ref) +{ + auto rec = ref->asRecord(); + if (!rec) + return nullopt; + + remove_const::type peers; + for (const auto & i : rec->items("peer")) + if (auto p = i.as>()) + peers.push_back(*p); + + auto enc = rec->item("enc").asText(); + if (!enc || enc != "aes-128-gcm") + return nullopt; + + auto key = rec->item("key").as(); + if (!key) + return nullopt; + + return ChannelRequestData { + .peers = std::move(peers), + .key = *key, + }; +} + +Ref ChannelAcceptData::store(const Storage & st) const +{ + vector items; + + items.emplace_back("req", request); + items.emplace_back("enc", "aes-128-gcm"); + items.emplace_back("key", key); + + return st.storeObject(Record(std::move(items))); +} + +optional ChannelAcceptData::load(const Ref & ref) +{ + auto rec = ref->asRecord(); + if (!rec) + return nullopt; + + auto request = rec->item("req").as(); + if (!request) + return nullopt; + + auto enc = rec->item("enc").asText(); + if (!enc || enc != "aes-128-gcm") + return nullopt; + + auto key = rec->item("key").as(); + if (!key) + return nullopt; + + return ChannelAcceptData { + .request = *request, + .key = *key, + }; +} + +Stored ChannelAcceptData::channel() const +{ + const auto & st = request.ref.storage(); + + if (auto secret = SecretKexKey::load(key)) + return st.store(Channel( + request->data->peers, + secret->dh(*request->data->key) + )); + + if (auto secret = SecretKexKey::load(request->data->key)) + return st.store(Channel( + request->data->peers, + secret->dh(*key) + )); + + throw runtime_error("failed to load secret DH key"); +} + + +Ref Channel::store(const Storage & st) const +{ + vector items; + + for (const auto p : peers) + items.emplace_back("peer", p); + items.emplace_back("enc", "aes-128-gcm"); + items.emplace_back("key", key); + + return st.storeObject(Record(std::move(items))); +} + +optional Channel::load(const Ref & ref) +{ + auto rec = ref->asRecord(); + if (!rec) + return nullopt; + + remove_const::type peers; + for (const auto & i : rec->items("peer")) + if (auto p = i.as>()) + peers.push_back(*p); + + auto enc = rec->item("enc").asText(); + if (!enc || enc != "aes-128-gcm") + return nullopt; + + auto key = rec->item("key").asBinary(); + if (!key) + return nullopt; + + return Channel(peers, std::move(*key)); +} + +Stored Channel::generateRequest(const Storage & st, + const Identity & self, const Identity & peer) +{ + auto signKey = SecretKey::load(self.keyMessage()); + if (!signKey) + throw runtime_error("failed to load own message key"); + + return signKey->sign(st.store(ChannelRequestData { + .peers = self.ref()->digest() < peer.ref()->digest() ? + vector>> { + *Stored>::load(*self.ref()), + *Stored>::load(*peer.ref()), + } : + vector>> { + *Stored>::load(*peer.ref()), + *Stored>::load(*self.ref()), + }, + .key = SecretKexKey::generate(st).pub(), + })); +} + +optional> Channel::acceptRequest(const Identity & self, + const Identity & peer, const Stored & request) +{ + if (!request->isSignedBy(peer.keyMessage())) + return nullopt; + + auto & peers = request->data->peers; + if (peers.size() != 2 || + std::none_of(peers.begin(), peers.end(), [&self](const auto & x) + { return x.ref.digest() == self.ref()->digest(); }) || + std::none_of(peers.begin(), peers.end(), [&peer](const auto & x) + { return x.ref.digest() == peer.ref()->digest(); })) + return nullopt; + + auto & st = request.ref.storage(); + + auto signKey = SecretKey::load(self.keyMessage()); + if (!signKey) + throw runtime_error("failed to load own message key"); + + return signKey->sign(st.store(ChannelAcceptData { + .request = request, + .key = SecretKexKey::generate(st).pub(), + })); +} + +vector Channel::encrypt(const vector & plain) const +{ + vector res(plain.size() + 12 + 16 + 16); + + if (RAND_bytes(res.data(), 12) != 1) + throw runtime_error("failed to generate random IV"); + + const unique_ptr + ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), + nullptr, key.data(), res.data()); + + int outl = 0; + uint8_t * cur = res.data() + 12; + + if (EVP_EncryptUpdate(ctx.get(), cur, &outl, plain.data(), plain.size()) != 1) + throw runtime_error("failed to encrypt data"); + cur += outl; + + if (EVP_EncryptFinal(ctx.get(), cur, &outl) != 1) + throw runtime_error("failed to encrypt data"); + cur += outl; + + EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, 16, cur); + cur += 16; + + res.resize(cur - res.data()); + return res; +} + +optional> Channel::decrypt(const vector & ctext) const +{ + vector res(ctext.size()); + + const unique_ptr + ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), + nullptr, key.data(), ctext.data()); + + int outl = 0; + uint8_t * cur = res.data(); + + if (EVP_DecryptUpdate(ctx.get(), cur, &outl, + ctext.data() + 12, ctext.size() - 12 - 16) != 1) + return nullopt; + cur += outl; + + if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, 16, + (void *) (ctext.data() + ctext.size() - 16))) + return nullopt; + + if (EVP_DecryptFinal_ex(ctx.get(), cur, &outl) != 1) + return nullopt; + cur += outl; + + res.resize(cur - res.data()); + return res; +} -- cgit v1.2.3