summaryrefslogtreecommitdiff
path: root/src/network
diff options
context:
space:
mode:
authorRoman Smrž <roman.smrz@seznam.cz>2023-08-19 09:42:34 +0200
committerRoman Smrž <roman.smrz@seznam.cz>2023-08-19 09:42:34 +0200
commit4153da3c16d184a1e6ffa15d2c504c6e3f6b0e1f (patch)
treeed8578a0c4d2dadc28da8480d379607675dfcc23 /src/network
parent86b465cfccef5552aa111941fb74ec622e2e7c03 (diff)
Network: move secure channel to protocol module
Diffstat (limited to 'src/network')
-rw-r--r--src/network/channel.cpp200
-rw-r--r--src/network/channel.h73
-rw-r--r--src/network/protocol.cpp7
-rw-r--r--src/network/protocol.h35
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 &);
+};
+
}