#include "channel.h" #include #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("key", key); return st.storeObject(Record(std::move(items))); } ChannelRequestData ChannelRequestData::load(const Ref & ref) { if (auto rec = ref->asRecord()) { if (auto key = rec->item("key").as()) return ChannelRequestData { .peers = rec->items("peer").as>(), .key = *key, }; } return ChannelRequestData { .peers = {}, .key = Stored::load(ref.storage().zref()), }; } Ref ChannelAcceptData::store(const Storage & st) const { vector items; items.emplace_back("req", request); items.emplace_back("key", key); return st.storeObject(Record(std::move(items))); } ChannelAcceptData ChannelAcceptData::load(const Ref & ref) { if (auto rec = ref->asRecord()) return ChannelAcceptData { .request = *rec->item("req").as(), .key = *rec->item("key").as(), }; return ChannelAcceptData { .request = Stored::load(ref.storage().zref()), .key = Stored::load(ref.storage().zref()), }; } unique_ptr ChannelAcceptData::channel() const { if (auto secret = SecretKexKey::load(key)) return make_unique( request->data->peers, secret->dh(*request->data->key), false ); if (auto secret = SecretKexKey::load(request->data->key)) return make_unique( request->data->peers, secret->dh(*key), true ); throw runtime_error("failed to load secret DH 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(), })); } uint64_t Channel::encrypt(BufferCIt plainBegin, BufferCIt plainEnd, Buffer & encBuffer, size_t encOffset) { auto plainSize = plainEnd - plainBegin; encBuffer.resize(encOffset + plainSize + 1 /* counter */ + 16 /* tag */); array iv; uint64_t count = counterNextOut.fetch_add(1); uint64_t beCount = htobe64(count); encBuffer[encOffset] = count % 0x100; constexpr size_t nonceFixedSize = std::tuple_size_v; static_assert(nonceFixedSize + sizeof beCount == iv.size()); std::copy_n(nonceFixedOur.begin(), nonceFixedSize, iv.begin()); std::memcpy(iv.data() + nonceFixedSize, &beCount, sizeof beCount); const unique_ptr ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); EVP_EncryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, key.data(), iv.data()); int outl = 0; uint8_t * cur = encBuffer.data() + encOffset + 1; if (EVP_EncryptUpdate(ctx.get(), cur, &outl, &*plainBegin, plainSize) != 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_AEAD_GET_TAG, 16, cur); return count; } optional Channel::decrypt(BufferCIt encBegin, BufferCIt encEnd, Buffer & decBuffer, const size_t decOffset) { auto encSize = encEnd - encBegin; decBuffer.resize(decOffset + encSize); array iv; if (encBegin + 1 /* counter */ + 16 /* tag */ > encEnd) return nullopt; uint64_t expectedCount = counterNextIn.load(); uint64_t guessedCount = expectedCount - 0x80u + ((0x80u + encBegin[0] - expectedCount) % 0x100u); uint64_t beCount = htobe64(guessedCount); constexpr size_t nonceFixedSize = std::tuple_size_v; static_assert(nonceFixedSize + sizeof beCount == iv.size()); std::copy_n(nonceFixedPeer.begin(), nonceFixedSize, iv.begin()); std::memcpy(iv.data() + nonceFixedSize, &beCount, sizeof beCount); const unique_ptr ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); EVP_DecryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, key.data(), iv.data()); int outl = 0; uint8_t * cur = decBuffer.data() + decOffset; if (EVP_DecryptUpdate(ctx.get(), cur, &outl, &*encBegin + 1, encSize - 1 - 16) != 1) return nullopt; cur += outl; if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, 16, (void *) (&*encEnd - 16))) return nullopt; if (EVP_DecryptFinal_ex(ctx.get(), cur, &outl) != 1) return nullopt; cur += outl; while (expectedCount < guessedCount + 1 && not counterNextIn.compare_exchange_weak(expectedCount, guessedCount + 1)) ; // empty loop body decBuffer.resize(cur - decBuffer.data()); return guessedCount; }