diff options
author | Roman Smrž <roman.smrz@seznam.cz> | 2023-08-19 09:42:34 +0200 |
---|---|---|
committer | Roman Smrž <roman.smrz@seznam.cz> | 2023-08-19 09:42:34 +0200 |
commit | 4153da3c16d184a1e6ffa15d2c504c6e3f6b0e1f (patch) | |
tree | ed8578a0c4d2dadc28da8480d379607675dfcc23 /src/network | |
parent | 86b465cfccef5552aa111941fb74ec622e2e7c03 (diff) |
Network: move secure channel to protocol module
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/channel.cpp | 200 | ||||
-rw-r--r-- | src/network/channel.h | 73 | ||||
-rw-r--r-- | src/network/protocol.cpp | 7 | ||||
-rw-r--r-- | src/network/protocol.h | 35 |
4 files changed, 315 insertions, 0 deletions
diff --git a/src/network/channel.cpp b/src/network/channel.cpp new file mode 100644 index 0000000..b317f3d --- /dev/null +++ b/src/network/channel.cpp @@ -0,0 +1,200 @@ +#include "channel.h" + +#include <algorithm> +#include <cstring> +#include <stdexcept> + +#include <endian.h> + +using std::remove_const; +using std::runtime_error; + +using namespace erebos; + +Ref ChannelRequestData::store(const Storage & st) const +{ + vector<Record::Item> 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))); +} + +ChannelRequestData ChannelRequestData::load(const Ref & ref) +{ + if (auto rec = ref->asRecord()) { + if (rec->item("enc").asText() == "aes-128-gcm") + if (auto key = rec->item("key").as<PublicKexKey>()) + return ChannelRequestData { + .peers = rec->items("peer").as<Signed<IdentityData>>(), + .key = *key, + }; + } + + return ChannelRequestData { + .peers = {}, + .key = Stored<PublicKexKey>::load(ref.storage().zref()), + }; +} + +Ref ChannelAcceptData::store(const Storage & st) const +{ + vector<Record::Item> 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))); +} + +ChannelAcceptData ChannelAcceptData::load(const Ref & ref) +{ + if (auto rec = ref->asRecord()) + if (rec->item("enc").asText() == "aes-128-gcm") + return ChannelAcceptData { + .request = *rec->item("req").as<ChannelRequest>(), + .key = *rec->item("key").as<PublicKexKey>(), + }; + + return ChannelAcceptData { + .request = Stored<ChannelRequest>::load(ref.storage().zref()), + .key = Stored<PublicKexKey>::load(ref.storage().zref()), + }; +} + +unique_ptr<Channel> ChannelAcceptData::channel() const +{ + if (auto secret = SecretKexKey::load(key)) + return make_unique<Channel>( + request->data->peers, + secret->dh(*request->data->key), + false + ); + + if (auto secret = SecretKexKey::load(request->data->key)) + return make_unique<Channel>( + request->data->peers, + secret->dh(*key), + true + ); + + throw runtime_error("failed to load secret DH key"); +} + + +Stored<ChannelRequest> 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<Signed<IdentityData>>> { + Stored<Signed<IdentityData>>::load(*self.ref()), + Stored<Signed<IdentityData>>::load(*peer.ref()), + } : + vector<Stored<Signed<IdentityData>>> { + Stored<Signed<IdentityData>>::load(*peer.ref()), + Stored<Signed<IdentityData>>::load(*self.ref()), + }, + .key = SecretKexKey::generate(st).pub(), + })); +} + +optional<Stored<ChannelAccept>> Channel::acceptRequest(const Identity & self, + const Identity & peer, const Stored<ChannelRequest> & 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<uint8_t> Channel::encrypt(const vector<uint8_t> & plain) +{ + vector<uint8_t> res(plain.size() + 8 + 16 + 16); + array<uint8_t, 12> iv; + + uint64_t beCount = htobe64(nonceCounter++); + std::memcpy(res.data(), &beCount, 8); + std::copy_n(nonceFixedOur.begin(), 6, iv.begin()); + std::copy_n(res.begin() + 2, 6, iv.begin() + 6); + + const unique_ptr<EVP_CIPHER_CTX, void(*)(EVP_CIPHER_CTX*)> + ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), + nullptr, key.data(), iv.data()); + + int outl = 0; + uint8_t * cur = res.data() + 8; + + 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<vector<uint8_t>> Channel::decrypt(const vector<uint8_t> & ctext) +{ + vector<uint8_t> res(ctext.size()); + array<uint8_t, 12> iv; + + std::copy_n(nonceFixedPeer.begin(), 6, iv.begin()); + std::copy_n(ctext.begin() + 2, 6, iv.begin() + 6); + + const unique_ptr<EVP_CIPHER_CTX, void(*)(EVP_CIPHER_CTX*)> + ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), + nullptr, key.data(), iv.data()); + + int outl = 0; + uint8_t * cur = res.data(); + + if (EVP_DecryptUpdate(ctx.get(), cur, &outl, + ctext.data() + 8, ctext.size() - 8 - 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; +} diff --git a/src/network/channel.h b/src/network/channel.h new file mode 100644 index 0000000..f932c84 --- /dev/null +++ b/src/network/channel.h @@ -0,0 +1,73 @@ +#pragma once + +#include <erebos/storage.h> + +#include "../identity.h" + +#include <atomic> +#include <memory> + +namespace erebos { + +using std::array; +using std::atomic; +using std::unique_ptr; + +struct ChannelRequestData +{ + Ref store(const Storage & st) const; + static ChannelRequestData load(const Ref &); + + const vector<Stored<Signed<IdentityData>>> peers; + const Stored<PublicKexKey> key; +}; + +typedef Signed<ChannelRequestData> ChannelRequest; + +struct ChannelAcceptData +{ + Ref store(const Storage & st) const; + static ChannelAcceptData load(const Ref &); + + unique_ptr<class Channel> channel() const; + + const Stored<ChannelRequest> request; + const Stored<PublicKexKey> key; +}; + +typedef Signed<ChannelAcceptData> ChannelAccept; + +class Channel +{ +public: + Channel(const vector<Stored<Signed<IdentityData>>> & peers, + vector<uint8_t> && key, bool ourRequest): + peers(peers), + key(std::move(key)), + nonceFixedOur({ uint8_t(ourRequest ? 1 : 2), 0, 0, 0, 0, 0 }), + nonceFixedPeer({ uint8_t(ourRequest ? 2 : 1), 0, 0, 0, 0, 0 }) + {} + + Channel(const Channel &) = delete; + Channel(Channel &&) = delete; + Channel & operator=(const Channel &) = delete; + Channel & operator=(Channel &&) = delete; + + static Stored<ChannelRequest> generateRequest(const Storage &, + const Identity & self, const Identity & peer); + static optional<Stored<ChannelAccept>> acceptRequest(const Identity & self, + const Identity & peer, const Stored<ChannelRequest> & request); + + vector<uint8_t> encrypt(const vector<uint8_t> &); + optional<vector<uint8_t>> decrypt(const vector<uint8_t> &); + +private: + const vector<Stored<Signed<IdentityData>>> peers; + const vector<uint8_t> key; + + const array<uint8_t, 6> nonceFixedOur; + const array<uint8_t, 6> nonceFixedPeer; + atomic<uint64_t> nonceCounter = 0; +}; + +} diff --git a/src/network/protocol.cpp b/src/network/protocol.cpp index fb3a5ea..4151bf2 100644 --- a/src/network/protocol.cpp +++ b/src/network/protocol.cpp @@ -24,6 +24,8 @@ struct NetworkProtocol::ConnectionPriv mutex cmutex {}; vector<uint8_t> buffer {}; + + ChannelState channel = monostate(); }; @@ -202,6 +204,11 @@ void NetworkProtocol::Connection::close() p = nullptr; } +NetworkProtocol::ChannelState & NetworkProtocol::Connection::channel() +{ + return p->channel; +} + /******************************************************************************/ /* Header */ diff --git a/src/network/protocol.h b/src/network/protocol.h index 4794ba6..88abf67 100644 --- a/src/network/protocol.h +++ b/src/network/protocol.h @@ -1,5 +1,7 @@ #pragma once +#include "channel.h" + #include <erebos/storage.h> #include <netinet/in.h> @@ -45,6 +47,12 @@ public: PollResult poll(); + using ChannelState = variant<monostate, + Stored<ChannelRequest>, + shared_ptr<struct WaitingRef>, + Stored<ChannelAccept>, + unique_ptr<Channel>>; + Connection connect(sockaddr_in6 addr); bool recvfrom(vector<uint8_t> & buffer, sockaddr_in6 & addr); @@ -84,6 +92,9 @@ public: void close(); + // temporary: + ChannelState & channel(); + private: unique_ptr<ConnectionPriv> p; }; @@ -121,4 +132,28 @@ struct NetworkProtocol::Header const vector<Item> items; }; +class ReplyBuilder +{ +public: + void header(NetworkProtocol::Header::Item &&); + void body(const Ref &); + + const vector<NetworkProtocol::Header::Item> & header() const { return mheader; } + vector<Object> body() const; + +private: + vector<NetworkProtocol::Header::Item> mheader; + vector<Ref> mbody; +}; + +struct WaitingRef +{ + const Storage storage; + const PartialRef ref; + vector<Digest> missing; + + optional<Ref> check(); + optional<Ref> check(ReplyBuilder &); +}; + } |