diff options
| author | Roman Smrž <roman.smrz@seznam.cz> | 2021-01-24 22:46:48 +0100 | 
|---|---|---|
| committer | Roman Smrž <roman.smrz@seznam.cz> | 2021-01-29 22:28:01 +0100 | 
| commit | a16b33031c7bcf2eabf1e0c3571000234b7740df (patch) | |
| tree | f012ff7814abe7fa22c5a610388faf4310d53772 /src | |
| parent | 3a3cce8eed7e43faa8c9f606e56bb43ba3bd9451 (diff) | |
Attach service
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/attach.cpp | 124 | ||||
| -rw-r--r-- | src/identity.cpp | 5 | ||||
| -rw-r--r-- | src/network.cpp | 65 | ||||
| -rw-r--r-- | src/network.h | 5 | ||||
| -rw-r--r-- | src/pairing.cpp | 212 | ||||
| -rw-r--r-- | src/pubkey.cpp | 39 | ||||
| -rw-r--r-- | src/pubkey.h | 3 | ||||
| -rw-r--r-- | src/storage.cpp | 24 | 
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)  { |