summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/attach.cpp124
-rw-r--r--src/identity.cpp5
-rw-r--r--src/network.cpp65
-rw-r--r--src/network.h5
-rw-r--r--src/pairing.cpp212
-rw-r--r--src/pubkey.cpp39
-rw-r--r--src/pubkey.h3
-rw-r--r--src/storage.cpp24
9 files changed, 466 insertions, 13 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b97293c..d8a076a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,10 +3,12 @@ include_directories(
)
add_library(erebos
+ attach
channel
identity
message
network
+ pairing
pubkey
service
state
diff --git a/src/attach.cpp b/src/attach.cpp
new file mode 100644
index 0000000..2bc2b74
--- /dev/null
+++ b/src/attach.cpp
@@ -0,0 +1,124 @@
+#include <erebos/attach.h>
+
+#include "identity.h"
+#include "pubkey.h"
+
+#include <erebos/network.h>
+
+#include <stdexcept>
+
+using namespace erebos;
+using std::lock_guard;
+using std::nullopt;
+using std::runtime_error;
+
+static const UUID myUUID("4995a5f9-2d4d-48e9-ad3b-0bf1c2a1be7f");
+
+AttachService::AttachService() = default;
+AttachService::~AttachService() = default;
+
+UUID AttachService::uuid() const
+{
+ return myUUID;
+}
+
+void AttachService::attachTo(const Peer & peer)
+{
+ requestPairing(myUUID, peer);
+}
+
+Stored<AttachIdentity> AttachService::handlePairingComplete(const Peer & peer)
+{
+ auto owner = peer.server().identity().finalOwner();
+ auto id = peer.identity()->ref();
+ auto prev = Stored<Signed<IdentityData>>::load(*peer.identity()->ref());
+
+ auto idata = peer.tempStorage().store(IdentityData {
+ .prev = { prev },
+ .name = nullopt,
+ .owner = Stored<Signed<IdentityData>>::load(*owner.ref()),
+ .keyIdentity = prev->data->keyIdentity,
+ .keyMessage = nullopt,
+ });
+
+ auto key = SecretKey::load(owner.keyIdentity());
+ if (!key)
+ throw runtime_error("failed to load secret key");
+
+ auto mkey = SecretKey::load(owner.keyMessage());
+ if (!mkey)
+ throw runtime_error("failed to load secret key");
+
+ auto sdata = key->sign(idata);
+
+ return peer.tempStorage().store(AttachIdentity {
+ .identity = sdata,
+ .keys = { key->getData(), mkey->getData() },
+ });
+}
+
+void AttachService::handlePairingResult(Context & ctx, Stored<AttachIdentity> att)
+{
+ if (att->identity->data->prev.size() != 1 ||
+ att->identity->data->prev[0].ref().digest() !=
+ ctx.local()->identity()->ref()->digest())
+ return;
+
+ if (att->identity->data->keyIdentity.ref().digest() !=
+ ctx.local()->identity()->keyIdentity().ref().digest())
+ return;
+
+ auto key = SecretKey::load(ctx.peer().server().identity().keyIdentity());
+ if (!key)
+ throw runtime_error("failed to load secret key");
+
+ auto id = Identity::load(key->signAdd(att->identity).ref());
+ if (!id)
+ printf("New identity validation failed\n");
+
+ auto rid = ctx.local().ref().storage().copy(*id->ref());
+ id = Identity::load(rid);
+
+ auto owner = id->owner();
+ if (!owner)
+ printf("New identity without owner\n");
+
+ // Store the keys
+ for (const auto & k : att->keys) {
+ SecretKey::fromData(owner->keyIdentity(), k);
+ SecretKey::fromData(owner->keyMessage(), k);
+ }
+
+ ctx.local(ctx.local()->identity(*id));
+}
+
+AttachIdentity AttachIdentity::load(const Ref & ref)
+{
+ auto rec = ref->asRecord();
+ if (!rec)
+ return AttachIdentity {
+ .identity = Stored<Signed<IdentityData>>::load(ref.storage().zref()),
+ .keys = {},
+ };
+
+ vector<vector<uint8_t>> keys;
+ for (auto s : rec->items("skey"))
+ if (const auto & b = s.asBinary())
+ keys.push_back(*b);
+
+ return AttachIdentity {
+ .identity = *rec->item("identity").as<Signed<IdentityData>>(),
+ .keys = keys,
+ };
+}
+
+Ref AttachIdentity::store(const Storage & st) const
+{
+ vector<Record::Item> items;
+
+ items.emplace_back("identity", identity.ref());
+ for (const auto & key : keys)
+ items.emplace_back("skey", key);
+
+ return st.storeObject(Record(std::move(items)));
+}
diff --git a/src/identity.cpp b/src/identity.cpp
index d7dd1f9..fe4cf53 100644
--- a/src/identity.cpp
+++ b/src/identity.cpp
@@ -60,6 +60,11 @@ const Identity & Identity::finalOwner() const
return *this;
}
+Stored<PublicKey> Identity::keyIdentity() const
+{
+ return p->data[0]->data->keyIdentity;
+}
+
Stored<PublicKey> Identity::keyMessage() const
{
return p->keyMessage;
diff --git a/src/network.cpp b/src/network.cpp
index c0bcc2a..48ba4b9 100644
--- a/src/network.cpp
+++ b/src/network.cpp
@@ -26,8 +26,23 @@ Server::Server(const Head<LocalState> & head, vector<unique_ptr<Service>> && svc
{
}
+Server:: Server(const std::shared_ptr<Priv> & ptr):
+ p(ptr)
+{
+}
+
Server::~Server() = default;
+const Head<LocalState> & Server::localHead() const
+{
+ return p->localHead;
+}
+
+const Identity & Server::identity() const
+{
+ return p->self;
+}
+
Service & Server::svcHelper(const std::type_info & tinfo)
{
for (auto & s : p->services)
@@ -45,6 +60,27 @@ PeerList & Server::peerList() const
Peer::Peer(const shared_ptr<Priv> & p): p(p) {}
Peer::~Peer() = default;
+Server Peer::server() const
+{
+ if (auto speer = p->speer.lock())
+ return Server(speer->server.getptr());
+ throw runtime_error("Server no longer running");
+}
+
+const Storage & Peer::tempStorage() const
+{
+ if (auto speer = p->speer.lock())
+ return speer->tempStorage;
+ throw runtime_error("Server no longer running");
+}
+
+const PartialStorage & Peer::partialStorage() const
+{
+ if (auto speer = p->speer.lock())
+ return speer->partStorage;
+ throw runtime_error("Server no longer running");
+}
+
string Peer::name() const
{
if (auto speer = p->speer.lock()) {
@@ -88,19 +124,41 @@ bool Peer::hasChannel() const
bool Peer::send(UUID uuid, const Ref & ref) const
{
+ return send(uuid, ref, *ref);
+}
+
+bool Peer::send(UUID uuid, const Object & obj) const
+{
+ if (auto speer = p->speer.lock()) {
+ auto ref = speer->tempStorage.storeObject(obj);
+ return send(uuid, ref, obj);
+ }
+
+ return false;
+}
+
+bool Peer::send(UUID uuid, const Ref & ref, const Object & obj) const
+{
if (hasChannel())
if (auto speer = p->speer.lock()) {
TransportHeader header({
{ TransportHeader::Type::ServiceType, uuid },
{ TransportHeader::Type::ServiceRef, ref },
});
- speer->send(header, { *ref });
+ speer->send(header, { obj });
return true;
}
return false;
}
+bool Peer::operator==(const Peer & other) const { return p == other.p; }
+bool Peer::operator!=(const Peer & other) const { return p != other.p; }
+bool Peer::operator<(const Peer & other) const { return p < other.p; }
+bool Peer::operator<=(const Peer & other) const { return p <= other.p; }
+bool Peer::operator>(const Peer & other) const { return p > other.p; }
+bool Peer::operator>=(const Peer & other) const { return p >= other.p; }
+
PeerList::PeerList(): p(new Priv) {}
PeerList::PeerList(const shared_ptr<PeerList::Priv> & p): p(p) {}
@@ -200,6 +258,11 @@ Server::Priv::~Priv()
close(sock);
}
+shared_ptr<Server::Priv> Server::Priv::getptr()
+{
+ return shared_from_this();
+}
+
void Server::Priv::doListen()
{
vector<uint8_t> buf, decrypted, *current;
diff --git a/src/network.h b/src/network.h
index c02dbc3..e22d453 100644
--- a/src/network.h
+++ b/src/network.h
@@ -134,11 +134,14 @@ struct WaitingRef
optional<Ref> check(ReplyBuilder &);
};
-struct Server::Priv
+struct Server::Priv : enable_shared_from_this<Server::Priv>
{
Priv(const Head<LocalState> & local, const Identity & self,
vector<unique_ptr<Service>> && svcs);
~Priv();
+
+ shared_ptr<Priv> getptr();
+
void doListen();
void doAnnounce();
diff --git a/src/pairing.cpp b/src/pairing.cpp
new file mode 100644
index 0000000..9e161df
--- /dev/null
+++ b/src/pairing.cpp
@@ -0,0 +1,212 @@
+#include <erebos/pairing.h>
+
+#include "service.h"
+
+#include <openssl/rand.h>
+
+#include <arpa/inet.h>
+
+#include <algorithm>
+#include <stdexcept>
+#include <thread>
+#include <vector>
+
+using namespace erebos;
+
+using std::lock_guard;
+using std::runtime_error;
+
+void PairingServiceBase::onRequestInit(RequestInitHook hook)
+{
+ lock_guard lock(stateLock);
+ requestInitHook = hook;
+}
+
+void PairingServiceBase::onResponse(ConfirmHook hook)
+{
+ lock_guard lock(stateLock);
+ responseHook = hook;
+}
+
+void PairingServiceBase::onRequest(ConfirmHook hook)
+{
+ lock_guard lock(stateLock);
+ requestHook = hook;
+}
+
+void PairingServiceBase::onRequestNonceFailed(RequestNonceFailedHook hook)
+{
+ lock_guard lock(stateLock);
+ requestNonceFailedHook = hook;
+}
+
+void PairingServiceBase::handle(Context & ctx)
+{
+ auto rec = ctx.ref()->asRecord();
+ if (!rec)
+ return;
+
+ auto pid = ctx.peer().identity();
+ if (!pid)
+ throw runtime_error("Pairing request for peer without known identity");
+
+ lock_guard lock(stateLock);
+ auto & state = peerStates.try_emplace(ctx.peer(), State()).first->second;
+
+ if (auto request = rec->item("request").asBinary()) {
+ if (state.phase != StatePhase::NoPairing)
+ return;
+
+ if (requestInitHook)
+ requestInitHook(ctx.peer());
+
+ state.phase = StatePhase::PeerRequest;
+ state.peerCheck = *request;
+ state.nonce.resize(32);
+ RAND_bytes(state.nonce.data(), state.nonce.size());
+
+ ctx.peer().send(uuid(), Object(Record({
+ { "response", state.nonce },
+ })));
+ }
+
+ else if (auto response = rec->item("response").asBinary()) {
+ if (state.phase != StatePhase::OurRequest) {
+ fprintf(stderr, "Unexpected pairing response.\n"); // TODO
+ return;
+ }
+
+ if (responseHook) {
+ string confirm = confirmationNumber(nonceDigest(
+ ctx.peer().server().identity(), *pid,
+ state.nonce, *response));
+ std::thread(&PairingServiceBase::waitForConfirmation,
+ this, ctx.peer(), confirm).detach();
+ }
+
+ state.phase = StatePhase::OurRequestConfirm;
+
+ ctx.peer().send(uuid(), Object(Record({
+ { "reqnonce", state.nonce },
+ })));
+ }
+
+ else if (auto reqnonce = rec->item("reqnonce").asBinary()) {
+ auto check = nonceDigest(
+ *pid, ctx.peer().server().identity(),
+ *reqnonce, vector<uint8_t>());
+ if (check != state.peerCheck) {
+ if (requestNonceFailedHook)
+ requestNonceFailedHook(ctx.peer());
+ state.phase = StatePhase::PairingFailed;
+ return;
+ }
+
+ if (requestHook) {
+ string confirm = confirmationNumber(nonceDigest(
+ *pid, ctx.peer().server().identity(),
+ *reqnonce, state.nonce));
+ std::thread(&PairingServiceBase::waitForConfirmation,
+ this, ctx.peer(), confirm).detach();
+ }
+
+ state.phase = StatePhase::PeerRequestConfirm;
+ }
+
+ else {
+ if (state.phase == StatePhase::OurRequestReady) {
+ handlePairingResult(ctx);
+ state.phase = StatePhase::PairingDone;
+ } else {
+ result = ctx.ref();
+ }
+ }
+}
+
+void PairingServiceBase::requestPairing(UUID serviceId, const Peer & peer)
+{
+ auto pid = peer.identity();
+ if (!pid)
+ throw runtime_error("Pairing request for peer without known identity");
+
+ lock_guard lock(stateLock);
+ auto & state = peerStates.try_emplace(peer, State()).first->second;
+
+ state.phase = StatePhase::OurRequest;
+ state.nonce.resize(32);
+ RAND_bytes(state.nonce.data(), state.nonce.size());
+
+ vector<Record::Item> items;
+ items.emplace_back("request", nonceDigest(
+ peer.server().identity(), *pid,
+ state.nonce, vector<uint8_t>()));
+
+ peer.send(serviceId, Object(Record(std::move(items))));
+}
+
+vector<uint8_t> PairingServiceBase::nonceDigest(const Identity & id1, const Identity & id2,
+ const vector<uint8_t> & nonce1, const vector<uint8_t> & nonce2)
+{
+ vector<Record::Item> items;
+ items.emplace_back("id", id1.ref().value());
+ items.emplace_back("id", id2.ref().value());
+ items.emplace_back("nonce", nonce1);
+ items.emplace_back("nonce", nonce2);
+
+ const auto arr = Digest::of(Object(Record(std::move(items)))).arr();
+ vector<uint8_t> ret(arr.size());
+ std::copy_n(arr.begin(), arr.size(), ret.begin());
+ return ret;
+}
+
+string PairingServiceBase::confirmationNumber(const vector<uint8_t> & digest)
+{
+ uint32_t confirm;
+ memcpy(&confirm, digest.data(), sizeof(confirm));
+ string ret(7, '\0');
+ snprintf(ret.data(), ret.size(), "%06d", ntohl(confirm) % 1000000);
+ return ret;
+}
+
+void PairingServiceBase::waitForConfirmation(Peer peer, string confirm)
+{
+ ConfirmHook hook;
+ {
+ lock_guard lock(stateLock);
+ auto & state = peerStates.try_emplace(peer, State()).first->second;
+ if (state.phase == StatePhase::OurRequestConfirm)
+ hook = responseHook;
+ if (state.phase == StatePhase::PeerRequestConfirm)
+ hook = requestHook;
+ }
+
+ bool ok = hook(peer, confirm).get();
+
+ lock_guard lock(stateLock);
+ auto & state = peerStates.try_emplace(peer, State()).first->second;
+
+ if (ok) {
+ if (state.phase == StatePhase::OurRequestConfirm) {
+ if (result) {
+ peer.server().localHead().update([&] (const Stored<LocalState> & local) {
+ Service::Context ctx(new Service::Context::Priv {
+ .ref = *result,
+ .peer = peer,
+ .local = local,
+ });
+
+ handlePairingResult(ctx);
+ return ctx.local();
+ });
+ state.phase = StatePhase::PairingDone;
+ } else {
+ state.phase = StatePhase::OurRequestReady;
+ }
+ } else if (state.phase == StatePhase::PeerRequestConfirm) {
+ peer.send(uuid(), handlePairingCompleteRef(peer));
+ state.phase = StatePhase::PairingDone;
+ }
+ } else {
+ state.phase = StatePhase::PairingFailed;
+ }
+}
diff --git a/src/pubkey.cpp b/src/pubkey.cpp
index 6d85d33..6461c10 100644
--- a/src/pubkey.cpp
+++ b/src/pubkey.cpp
@@ -88,6 +88,45 @@ optional<SecretKey> SecretKey::load(const Stored<PublicKey> & pub)
return SecretKey(key, pub);
}
+optional<SecretKey> SecretKey::fromData(const Stored<PublicKey> & pub, const vector<uint8_t> & sdata)
+{
+ shared_ptr<EVP_PKEY> pkey(
+ EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
+ sdata.data(), sdata.size()),
+ EVP_PKEY_free);
+ if (!pkey)
+ return nullopt;
+
+ vector<uint8_t> keyData;
+ size_t keyLen;
+
+ EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &keyLen);
+ keyData.resize(keyLen);
+ EVP_PKEY_get_raw_public_key(pkey.get(), keyData.data(), &keyLen);
+
+ EVP_PKEY_get_raw_public_key(pub->key.get(), nullptr, &keyLen);
+ keyData.resize(keyLen);
+ EVP_PKEY_get_raw_public_key(pub->key.get(), keyData.data(), &keyLen);
+
+ if (EVP_PKEY_cmp(pkey.get(), pub->key.get()) != 1)
+ return nullopt;
+
+ pub.ref().storage().storeKey(pub.ref(), sdata);
+ return SecretKey(std::move(pkey), pub);
+}
+
+vector<uint8_t> SecretKey::getData() const
+{
+ vector<uint8_t> keyData;
+ size_t keyLen;
+
+ EVP_PKEY_get_raw_private_key(key.get(), nullptr, &keyLen);
+ keyData.resize(keyLen);
+ EVP_PKEY_get_raw_private_key(key.get(), keyData.data(), &keyLen);
+
+ return keyData;
+}
+
vector<uint8_t> SecretKey::sign(const Digest & dgst) const
{
unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>
diff --git a/src/pubkey.h b/src/pubkey.h
index ef7e322..5cb693b 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -34,6 +34,9 @@ public:
static SecretKey generate(const Storage & st);
static optional<SecretKey> load(const Stored<PublicKey> & st);
+ static optional<SecretKey> fromData(const Stored<PublicKey> &, const vector<uint8_t> &);
+ vector<uint8_t> getData() const;
+
Stored<PublicKey> pub() const { return pub_; }
template<class T>
diff --git a/src/storage.cpp b/src/storage.cpp
index 51ce9bc..6e2f118 100644
--- a/src/storage.cpp
+++ b/src/storage.cpp
@@ -613,13 +613,7 @@ Ref Storage::zref() const
Digest PartialStorage::Priv::storeBytes(const vector<uint8_t> & content) const
{
- array<uint8_t, Digest::size> arr;
- int ret = blake2b(arr.data(), content.data(), nullptr,
- Digest::size, content.size(), 0);
- if (ret != 0)
- throw runtime_error("failed to compute digest");
-
- Digest digest(arr);
+ Digest digest = Digest::of(content);
backend->storeBytes(digest, content);
return digest;
}
@@ -631,10 +625,7 @@ optional<vector<uint8_t>> PartialStorage::Priv::loadBytes(const Digest & digest)
return nullopt;
auto content = ocontent.value();
- array<uint8_t, Digest::size> arr;
- int ret = blake2b(arr.data(), content.data(), nullptr,
- Digest::size, content.size(), 0);
- if (ret != 0 || digest != Digest(arr))
+ if (digest != Digest::of(content))
throw runtime_error("digest verification failed");
return content;
@@ -820,6 +811,17 @@ bool Digest::isZero() const
return true;
}
+Digest Digest::of(const vector<uint8_t> & content)
+{
+ array<uint8_t, size> arr;
+ int ret = blake2b(arr.data(), content.data(), nullptr,
+ size, content.size(), 0);
+ if (ret != 0)
+ throw runtime_error("failed to compute digest");
+
+ return Digest(arr);
+}
+
PartialRef PartialRef::create(PartialStorage st, const Digest & digest)
{