summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/erebos/identity.h19
-rw-r--r--include/erebos/storage.h39
-rw-r--r--src/identity.cpp76
-rw-r--r--src/identity.h12
-rw-r--r--src/pubkey.cpp95
-rw-r--r--src/pubkey.h53
-rw-r--r--src/storage.cpp58
-rw-r--r--src/storage.h1
8 files changed, 346 insertions, 7 deletions
diff --git a/include/erebos/identity.h b/include/erebos/identity.h
index 7b66d82..f57a12e 100644
--- a/include/erebos/identity.h
+++ b/include/erebos/identity.h
@@ -12,6 +12,25 @@ public:
std::optional<std::string> name() const;
std::optional<Identity> owner() const;
+ std::optional<Ref> ref() const;
+
+ class Builder
+ {
+ public:
+ Identity commit() const;
+
+ void name(const std::string &);
+ void owner(const Identity &);
+
+ private:
+ friend class Identity;
+ struct Priv;
+ const std::shared_ptr<Priv> p;
+ Builder(Priv * p): p(p) {}
+ };
+
+ static Builder create(const Storage &);
+ Builder modify() const;
private:
struct Priv;
diff --git a/include/erebos/storage.h b/include/erebos/storage.h
index ea0e894..3777572 100644
--- a/include/erebos/storage.h
+++ b/include/erebos/storage.h
@@ -23,9 +23,21 @@ public:
Storage & operator=(const Storage &) = delete;
static std::optional<Storage> open(std::filesystem::path path);
+
+ bool operator==(const Storage &) const;
+ bool operator!=(const Storage &) const;
+
std::optional<Ref> ref(const Digest &) const;
- std::optional<Object> load(const Digest &) const;
- Ref store(const Object &) const;
+
+ std::optional<Object> loadObject(const Digest &) const;
+ Ref storeObject(const Object &) const;
+ Ref storeObject(const class Record &) const;
+ Ref storeObject(const class Blob &) const;
+
+ template<typename T> Stored<T> store(const T &) const;
+
+ void storeKey(Ref pubref, const std::vector<uint8_t> &) const;
+ std::optional<std::vector<uint8_t>> loadKey(Ref pubref) const;
private:
friend class Ref;
@@ -71,6 +83,8 @@ public:
const Object & operator*() const;
const Object * operator->() const;
+ const Storage & storage() const;
+
private:
friend class Storage;
struct Priv;
@@ -94,6 +108,10 @@ public:
Item(name, std::monostate()) {}
Item(const std::string & name, Variant value):
name(name), value(value) {}
+ template<typename T>
+ Item(const std::string & name, const Stored<T> & value):
+ Item(name, value.ref) {}
+
Item(const Item &) = default;
Item & operator=(const Item &) = delete;
@@ -118,6 +136,7 @@ private:
public:
Record(const std::vector<Item> &);
+ Record(std::vector<Item> &&);
std::vector<uint8_t> encode() const;
const std::vector<Item> & items() const;
@@ -192,8 +211,10 @@ template<typename T>
class Stored
{
Stored(Ref ref, std::shared_ptr<T> val): ref(ref), val(val) {}
+ friend class Storage;
public:
static std::optional<Stored<T>> load(const Ref &);
+ Ref store(const Storage &) const;
bool operator==(const Stored<T> & other) const
{ return ref.digest() == other.ref.digest(); }
@@ -219,6 +240,12 @@ public:
};
template<typename T>
+Stored<T> Storage::store(const T & val) const
+{
+ return Stored(val.store(*this), std::make_shared<T>(val));
+}
+
+template<typename T>
std::optional<Stored<T>> Stored<T>::load(const Ref & ref)
{
if (auto val = T::load(ref))
@@ -227,6 +254,14 @@ std::optional<Stored<T>> Stored<T>::load(const Ref & ref)
}
template<typename T>
+Ref Stored<T>::store(const Storage & st) const
+{
+ if (st == ref.storage())
+ return ref;
+ return st.storeObject(*ref);
+}
+
+template<typename T>
std::vector<Stored<T>> Stored<T>::previous() const
{
auto rec = ref->asRecord();
diff --git a/src/identity.cpp b/src/identity.cpp
index 0160a69..4833f71 100644
--- a/src/identity.cpp
+++ b/src/identity.cpp
@@ -2,11 +2,13 @@
#include <algorithm>
#include <set>
+#include <stdexcept>
using namespace erebos;
using std::async;
using std::nullopt;
+using std::runtime_error;
using std::set;
optional<Identity> Identity::load(const Ref & ref)
@@ -41,6 +43,63 @@ optional<Identity> Identity::owner() const
return p->owner;
}
+optional<Ref> Identity::ref() const
+{
+ if (p->data.size() == 1)
+ return p->data[0].ref;
+ return nullopt;
+}
+
+Identity::Builder Identity::create(const Storage & st)
+{
+ return Builder (new Builder::Priv {
+ .storage = st,
+ .keyIdentity = SecretKey::generate(st).pub(),
+ .keyMessage = SecretKey::generate(st).pub(),
+ });
+}
+
+Identity::Builder Identity::modify() const
+{
+ return Builder (new Builder::Priv {
+ .storage = p->data[0].ref.storage(),
+ .prev = p->data,
+ .keyIdentity = p->data[0]->data->keyIdentity,
+ .keyMessage = p->data[0]->data->keyMessage,
+ });
+}
+
+
+Identity Identity::Builder::commit() const
+{
+ auto idata = p->storage.store(IdentityData {
+ .prev = p->prev,
+ .name = p->name,
+ .owner = p->owner && p->owner->p->data.size() == 1 ?
+ optional(p->owner->p->data[0]) : nullopt,
+ .keyIdentity = p->keyIdentity,
+ .keyMessage = p->keyMessage,
+ });
+
+ auto key = SecretKey::load(p->keyIdentity);
+ if (!key)
+ throw runtime_error("failed to load secret key");
+
+ auto sdata = key->sign(idata);
+
+ return Identity(Identity::Priv::validate({ sdata }));
+}
+
+void Identity::Builder::name(const string & val)
+{
+ p->name = val;
+}
+
+void Identity::Builder::owner(const Identity & val)
+{
+ p->owner.emplace(val);
+}
+
optional<IdentityData> IdentityData::load(const Ref & ref)
{
auto rec = ref->asRecord();
@@ -65,6 +124,23 @@ optional<IdentityData> IdentityData::load(const Ref & ref)
};
}
+Ref IdentityData::store(const Storage & st) const
+{
+ vector<Record::Item> items;
+
+ for (const auto p : prev)
+ items.emplace_back("SPREV", p.ref);
+ if (name)
+ items.emplace_back("name", *name);
+ if (owner)
+ items.emplace_back("owner", owner->ref);
+ items.emplace_back("key-id", keyIdentity.ref);
+ if (keyMessage)
+ items.emplace_back("key-msg", keyMessage->ref);
+
+ return st.storeObject(Record(std::move(items)));
+}
+
bool Identity::Priv::verifySignatures(const Stored<Signed<IdentityData>> & sdata)
{
if (!sdata->isSignedBy(sdata->data->keyIdentity))
diff --git a/src/identity.h b/src/identity.h
index d31951f..4335d32 100644
--- a/src/identity.h
+++ b/src/identity.h
@@ -14,6 +14,7 @@ class IdentityData
{
public:
static optional<IdentityData> load(const Ref &);
+ Ref store(const Storage & st) const;
const vector<Stored<Signed<IdentityData>>> prev;
const optional<string> name;
@@ -35,4 +36,15 @@ public:
function<bool(const IdentityData &)> sel) const;
};
+class Identity::Builder::Priv
+{
+public:
+ Storage storage;
+ vector<Stored<Signed<IdentityData>>> prev = {};
+ optional<string> name = nullopt;
+ optional<Identity> owner = nullopt;
+ Stored<PublicKey> keyIdentity;
+ optional<Stored<PublicKey>> keyMessage;
+};
+
}
diff --git a/src/pubkey.cpp b/src/pubkey.cpp
index 3247ce2..e26bead 100644
--- a/src/pubkey.cpp
+++ b/src/pubkey.cpp
@@ -25,6 +25,91 @@ optional<PublicKey> PublicKey::load(const Ref & ref)
return nullopt;
}
+Ref PublicKey::store(const Storage & st) const
+{
+ vector<Record::Item> items;
+
+ items.emplace_back("type", "ed25519");
+
+ vector<uint8_t> keyData;
+ size_t keyLen;
+ EVP_PKEY_get_raw_public_key(key.get(), nullptr, &keyLen);
+ keyData.resize(keyLen);
+ EVP_PKEY_get_raw_public_key(key.get(), keyData.data(), &keyLen);
+ items.emplace_back("pubkey", keyData);
+
+ return st.storeObject(Record(std::move(items)));
+}
+
+SecretKey SecretKey::generate(const Storage & st)
+{
+ unique_ptr<EVP_PKEY_CTX, void(*)(EVP_PKEY_CTX*)>
+ pctx(EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL), &EVP_PKEY_CTX_free);
+ if (!pctx)
+ throw runtime_error("failed to generate key");
+
+ if (EVP_PKEY_keygen_init(pctx.get()) != 1)
+ throw runtime_error("failed to generate key");
+
+ EVP_PKEY *pkey = NULL;
+ if (EVP_PKEY_keygen(pctx.get(), &pkey) != 1)
+ throw runtime_error("failed to generate key");
+ shared_ptr<EVP_PKEY> seckey(pkey, EVP_PKEY_free);
+
+ vector<uint8_t> keyData;
+ size_t keyLen;
+
+ EVP_PKEY_get_raw_public_key(seckey.get(), nullptr, &keyLen);
+ keyData.resize(keyLen);
+ EVP_PKEY_get_raw_public_key(seckey.get(), keyData.data(), &keyLen);
+ auto pubkey = st.store(PublicKey(EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr,
+ keyData.data(), keyData.size())));
+
+ EVP_PKEY_get_raw_private_key(seckey.get(), nullptr, &keyLen);
+ keyData.resize(keyLen);
+ EVP_PKEY_get_raw_private_key(seckey.get(), keyData.data(), &keyLen);
+ st.storeKey(pubkey.ref, keyData);
+
+ return SecretKey(std::move(seckey), pubkey);
+}
+
+optional<SecretKey> SecretKey::load(const Stored<PublicKey> & pub)
+{
+ auto keyData = pub.ref.storage().loadKey(pub.ref);
+ if (!keyData)
+ return nullopt;
+
+ EVP_PKEY * key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr,
+ keyData->data(), keyData->size());
+ if (!key)
+ throw runtime_error("falied to parse secret key");
+ return SecretKey(key, pub);
+}
+
+vector<uint8_t> SecretKey::sign(const Digest & dgst) const
+{
+ unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>
+ mdctx(EVP_MD_CTX_create(), &EVP_MD_CTX_free);
+ if (!mdctx)
+ throw runtime_error("failed to create EVP_MD_CTX");
+
+ if (EVP_DigestSignInit(mdctx.get(), nullptr, EVP_md_null(),
+ nullptr, key.get()) != 1)
+ throw runtime_error("failed to initialize EVP_MD_CTX");
+
+ size_t sigLen;
+ if (EVP_DigestSign(mdctx.get(), nullptr, &sigLen,
+ dgst.arr().data(), Digest::size) != 1)
+ throw runtime_error("failed to sign data");
+
+ vector<uint8_t> sigData(sigLen);
+ if (EVP_DigestSign(mdctx.get(), sigData.data(), &sigLen,
+ dgst.arr().data(), Digest::size) != 1)
+ throw runtime_error("failed to sign data");
+
+ return sigData;
+}
+
optional<Signature> Signature::load(const Ref & ref)
{
auto rec = ref->asRecord();
@@ -43,6 +128,16 @@ optional<Signature> Signature::load(const Ref & ref)
};
}
+Ref Signature::store(const Storage & st) const
+{
+ vector<Record::Item> items;
+
+ items.emplace_back("key", key);
+ items.emplace_back("sig", sig);
+
+ return st.storeObject(Record(std::move(items)));
+}
+
bool Signature::verify(const Ref & ref) const
{
unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>
diff --git a/src/pubkey.h b/src/pubkey.h
index 7fe37ec..80da3fa 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -10,34 +10,57 @@ using std::shared_ptr;
namespace erebos {
+template<typename T> class Signed;
+
class PublicKey
{
PublicKey(EVP_PKEY * key):
key(key, EVP_PKEY_free) {}
+ friend class SecretKey;
public:
static optional<PublicKey> load(const Ref &);
+ Ref store(const Storage &) const;
+
const shared_ptr<EVP_PKEY> key;
};
class SecretKey
{
SecretKey(EVP_PKEY * key, const Stored<PublicKey> & pub):
- key(key, EVP_PKEY_free), pub(pub) {}
+ key(key, EVP_PKEY_free), pub_(pub) {}
+ SecretKey(shared_ptr<EVP_PKEY> && key, const Stored<PublicKey> & pub):
+ key(key), pub_(pub) {}
+public:
+ static SecretKey generate(const Storage & st);
+ static optional<SecretKey> load(const Stored<PublicKey> & st);
+
+ Stored<PublicKey> pub() const { return pub_; }
+
+ template<class T>
+ Stored<Signed<T>> sign(const Stored<T> &) const;
private:
+ vector<uint8_t> sign(const Digest &) const;
+
const shared_ptr<EVP_PKEY> key;
- Stored<PublicKey> pub;
+ Stored<PublicKey> pub_;
};
class Signature
{
public:
static optional<Signature> load(const Ref &);
+ Ref store(const Storage &) const;
bool verify(const Ref &) const;
Stored<PublicKey> key;
vector<uint8_t> sig;
+
+private:
+ friend class SecretKey;
+ Signature(const Stored<PublicKey> & key, const vector<uint8_t> & sig):
+ key(key), sig(sig) {}
};
template<typename T>
@@ -45,13 +68,27 @@ class Signed
{
public:
static optional<Signed<T>> load(const Ref &);
+ Ref store(const Storage &) const;
bool isSignedBy(const Stored<PublicKey> &) const;
const Stored<T> data;
const vector<Stored<Signature>> sigs;
+
+private:
+ friend class SecretKey;
+ Signed(const Stored<T> & data, const vector<Stored<Signature>> & sigs):
+ data(data), sigs(sigs) {}
};
+template<class T>
+Stored<Signed<T>> SecretKey::sign(const Stored<T> & val) const
+{
+ auto st = val.ref.storage();
+ auto sig = st.store(Signature(pub(), sign(val.ref.digest())));
+ return st.store(Signed(val, { sig }));
+}
+
template<typename T>
optional<Signed<T>> Signed<T>::load(const Ref & ref)
{
@@ -76,6 +113,18 @@ optional<Signed<T>> Signed<T>::load(const Ref & ref)
}
template<typename T>
+Ref Signed<T>::store(const Storage & st) const
+{
+ vector<Record::Item> items;
+
+ items.emplace_back("SDATA", data);
+ for (const auto & sig : sigs)
+ items.emplace_back("sig", sig);
+
+ return st.storeObject(Record(std::move(items)));
+}
+
+template<typename T>
bool Signed<T>::isSignedBy(const Stored<PublicKey> & key) const
{
for (const auto & sig : sigs)
diff --git a/src/storage.cpp b/src/storage.cpp
index d549959..b5e71b7 100644
--- a/src/storage.cpp
+++ b/src/storage.cpp
@@ -24,6 +24,7 @@ using std::ifstream;
using std::make_shared;
using std::monostate;
using std::nullopt;
+using std::ofstream;
using std::runtime_error;
using std::shared_ptr;
using std::string;
@@ -43,6 +44,16 @@ optional<Storage> Storage::open(fs::path path)
return Storage(shared_ptr<const Priv>(new Priv { path }));
}
+bool Storage::operator==(const Storage & other) const
+{
+ return p == other.p;
+}
+
+bool Storage::operator!=(const Storage & other) const
+{
+ return p != other.p;
+}
+
fs::path Storage::Priv::objectPath(const Digest & digest) const
{
string name(digest);
@@ -51,6 +62,12 @@ fs::path Storage::Priv::objectPath(const Digest & digest) const
fs::path(name.begin() + 2, name.end());
}
+fs::path Storage::Priv::keyPath(const Digest & digest) const
+{
+ string name(digest);
+ return root/"keys"/fs::path(name.begin(), name.end());
+}
+
optional<Ref> Storage::ref(const Digest & digest) const
{
return Ref::create(*this, digest);
@@ -115,7 +132,7 @@ optional<vector<uint8_t>> Storage::Priv::loadBytes(const Digest & digest) const
return out;
}
-optional<Object> Storage::load(const Digest & digest) const
+optional<Object> Storage::loadObject(const Digest & digest) const
{
auto ocontent = p->loadBytes(digest);
if (!ocontent.has_value())
@@ -193,7 +210,7 @@ void Storage::Priv::storeBytes(const Digest & digest, const vector<uint8_t> & in
fs::rename(lock, path);
}
-Ref Storage::store(const Object & object) const
+Ref Storage::storeObject(const Object & object) const
{
// TODO: ensure storage transitively
auto content = object.encode();
@@ -209,6 +226,32 @@ Ref Storage::store(const Object & object) const
return Ref::create(*this, digest).value();
}
+Ref Storage::storeObject(const class Record & val) const
+{ return storeObject(Object(val)); }
+
+Ref Storage::storeObject(const class Blob & val) const
+{ return storeObject(Object(val)); }
+
+void Storage::storeKey(Ref pubref, const vector<uint8_t> & key) const
+{
+ ofstream file(p->keyPath(pubref.digest()));
+ file.write((const char *) key.data(), key.size());
+}
+
+optional<vector<uint8_t>> Storage::loadKey(Ref pubref) const
+{
+ fs::path path = p->keyPath(pubref.digest());
+ std::error_code err;
+ size_t size = fs::file_size(path, err);
+ if (err)
+ return nullopt;
+
+ vector<uint8_t> key(size);
+ ifstream file(p->keyPath(pubref.digest()));
+ file.read((char *) key.data(), size);
+ return key;
+}
+
Digest::Digest(const string & str)
{
@@ -243,7 +286,7 @@ optional<Ref> Ref::create(Storage st, const Digest & digest)
};
p->object = std::async(std::launch::deferred, [p] {
- auto obj = p->storage.load(p->digest);
+ auto obj = p->storage.loadObject(p->digest);
if (!obj.has_value())
throw runtime_error("failed to decode bytes");
@@ -268,6 +311,11 @@ const Object * Ref::operator->() const
return &p->object.get();
}
+const Storage & Ref::storage() const
+{
+ return p->storage;
+}
+
Record::Item::operator bool() const
{
@@ -307,6 +355,10 @@ Record::Record(const vector<Item> & from):
ptr(new vector<Item>(from))
{}
+Record::Record(vector<Item> && from):
+ ptr(new vector<Item>(std::move(from)))
+{}
+
Record Record::decode(Storage st,
vector<uint8_t>::const_iterator begin,
vector<uint8_t>::const_iterator end)
diff --git a/src/storage.h b/src/storage.h
index 2a0ad7e..e675848 100644
--- a/src/storage.h
+++ b/src/storage.h
@@ -19,6 +19,7 @@ struct Storage::Priv
fs::path root;
fs::path objectPath(const Digest &) const;
+ fs::path keyPath(const Digest &) const;
optional<vector<uint8_t>> loadBytes(const Digest &) const;
void storeBytes(const Digest &, const vector<uint8_t> &) const;
};