Skip to content

Commit

Permalink
Implement signatures & verification #27
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Oct 19, 2020
1 parent d364932 commit 0491cae
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 68 deletions.
2 changes: 2 additions & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ ureq = "1.4.0"
rio_turtle = { version = "0.5.0", optional = true}
rio_api = { version = "0.5.0", optional = true}
rand = "0.7.3"
ring = "0.16.15"
base64 = "0.13.0"

[features]
db = ["sled", "bincode"]
Expand Down
26 changes: 20 additions & 6 deletions lib/defaults/default_store.ad3
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
["https://atomicdata.dev/classes/Datatype","https://atomicdata.dev/properties/description","A Datatype describes a possible type of value, such as 'string' or 'integer'."]
["https://atomicdata.dev/classes/Datatype","https://atomicdata.dev/properties/shortname","datatype"]
["https://atomicdata.dev/classes/Commit","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Class\"]"]
["https://atomicdata.dev/classes/Commit","https://atomicdata.dev/properties/requires","[\"https://atomicdata.dev/properties/subject\",\"https://atomicdata.dev/properties/createdAt\",\"https://atomicdata.dev/properties/actor\",\"https://atomicdata.dev/properties/signature\"]"]
["https://atomicdata.dev/classes/Commit","https://atomicdata.dev/properties/requires","[\"https://atomicdata.dev/properties/subject\",\"https://atomicdata.dev/properties/createdAt\",\"https://atomicdata.dev/properties/signer\",\"https://atomicdata.dev/properties/signature\"]"]
["https://atomicdata.dev/classes/Commit","https://atomicdata.dev/properties/recommends","[\"https://atomicdata.dev/properties/set\",\"https://atomicdata.dev/properties/remove\",\"https://atomicdata.dev/properties/destroy\"]"]
["https://atomicdata.dev/classes/Commit","https://atomicdata.dev/properties/requires","[\"https://atomicdata.dev/properties/shortname\",\"https://atomicdata.dev/properties/description\"]"]
["https://atomicdata.dev/classes/Commit","https://atomicdata.dev/properties/description","A Commit describes a possible type of value, such as 'string' or 'integer'."]
["https://atomicdata.dev/classes/Commit","https://atomicdata.dev/properties/shortname","commit"]
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Class\"]"]
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/requires","[\"https://atomicdata.dev/properties/createdAt\",\"https://atomicdata.dev/properties/name\",\"https://atomicdata.dev/properties/publicKey\"]"]
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/recommends","[\"https://atomicdata.dev/properties/description\",\"https://atomicdata.dev/properties/remove\",\"https://atomicdata.dev/properties/destroy\"]"]
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/description","An Agent is a user that can create or modify data. It has two keys: a private and a public one. The private key should be kept secret. The publik key is for proving that the "]
["https://atomicdata.dev/classes/Agent","https://atomicdata.dev/properties/shortname","agent"]
# Datatypes
["https://atomicdata.dev/datatypes/string","https://atomicdata.dev/properties/shortname","string"]
["https://atomicdata.dev/datatypes/string","https://atomicdata.dev/properties/description","A UTF-8 string. Allows newlines with `\n`. This is a generic string datatype - don't use this for things like [markdown](https://atomicdata.dev/datatypes/markdown) or html."]
Expand Down Expand Up @@ -92,14 +97,15 @@
["https://atomicdata.dev/properties/destroy","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/boolean"]
["https://atomicdata.dev/properties/destroy","https://atomicdata.dev/properties/shortname","destroy"]
["https://atomicdata.dev/properties/destroy","https://atomicdata.dev/properties/description","If set to true, the entire Subject resource will be removed in this commit. This will be executed _before_ other commands, such as set."]
["https://atomicdata.dev/properties/actor","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/actor","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/atomicURL"]
["https://atomicdata.dev/properties/actor","https://atomicdata.dev/properties/shortname","actor"]
["https://atomicdata.dev/properties/actor","https://atomicdata.dev/properties/description","The actor is the agent (person, organization or something else) that issued the commit."]
["https://atomicdata.dev/properties/signer","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/signer","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/atomicURL"]
["https://atomicdata.dev/properties/signer","https://atomicdata.dev/properties/classtype","https://atomicdata.dev/classes/Agent"]
["https://atomicdata.dev/properties/signer","https://atomicdata.dev/properties/shortname","signer"]
["https://atomicdata.dev/properties/signer","https://atomicdata.dev/properties/description","The signer is the agent (person, organization or something else) that issued the commit."]
["https://atomicdata.dev/properties/signature","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/signature","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/string"]
["https://atomicdata.dev/properties/signature","https://atomicdata.dev/properties/shortname","signature"]
["https://atomicdata.dev/properties/signature","https://atomicdata.dev/properties/description","The signature proves that a Commit is created by a specific Actor. It is a cryptographic proof - an RSA signature of the JSON serialized commit, minus the signature."]
["https://atomicdata.dev/properties/signature","https://atomicdata.dev/properties/description","The signature proves that a Commit is created by a specific Agent. It is a cryptographic proof - an RSA signature of the JSON serialized commit, minus the signature."]
["https://atomicdata.dev/properties/createdAt","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/createdAt","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/Timestamp"]
["https://atomicdata.dev/properties/createdAt","https://atomicdata.dev/properties/shortname","createdat"]
Expand All @@ -108,3 +114,11 @@
["https://atomicdata.dev/properties/subject","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/atomicURL"]
["https://atomicdata.dev/properties/subject","https://atomicdata.dev/properties/shortname","subject"]
["https://atomicdata.dev/properties/subject","https://atomicdata.dev/properties/description","The subject of a Delta - the resource ID that is being changed."]
["https://atomicdata.dev/properties/name","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/name","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/string"]
["https://atomicdata.dev/properties/name","https://atomicdata.dev/properties/shortname","name"]
["https://atomicdata.dev/properties/name","https://atomicdata.dev/properties/description","The name of a thing or person."]
["https://atomicdata.dev/properties/publicKey","https://atomicdata.dev/properties/isA","[\"https://atomicdata.dev/classes/Property\"]"]
["https://atomicdata.dev/properties/publicKey","https://atomicdata.dev/properties/datatype","https://atomicdata.dev/datatypes/string"]
["https://atomicdata.dev/properties/publicKey","https://atomicdata.dev/properties/shortname","publickey"]
["https://atomicdata.dev/properties/publicKey","https://atomicdata.dev/properties/description","The publicKey of an Agent. Is a base64 serialized Ed25519 key."]
35 changes: 35 additions & 0 deletions lib/examples/signing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
fn main() {
use ring::{
rand,
signature::{self, KeyPair},
};

// Generate a key pair in PKCS#8 (v2) format.
let rng = rand::SystemRandom::new();
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap();

// Normally the application would store the PKCS#8 file persistently. Later
// it would read the PKCS#8 file from persistent storage to use it.

let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap();

// Sign the message "hello, world".
const MESSAGE: &[u8] = b"hello, world";
let sig = key_pair.sign(MESSAGE);

let pubkey_b64 = base64::encode(key_pair.public_key());

let peer_public_key_bytes = base64::decode(pubkey_b64).unwrap();

// Normally an application would extract the bytes of the signature and
// send them in a protocol message to the peer(s). Here we just get the
// public key key directly from the key pair.
// let peer_public_key_bytes = key_pair.public_key().as_ref();

// Verify the signature of the message using the public key. Normally the
// verifier of the message would parse the inputs to this code out of the
// protocol message(s) sent by the signer.
let peer_public_key =
signature::UnparsedPublicKey::new(&signature::ED25519, peer_public_key_bytes);
peer_public_key.verify(MESSAGE, sig.as_ref()).unwrap();
}
2 changes: 1 addition & 1 deletion lib/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ mod test {

#[test] #[ignore]
fn post_commit_basic() {
let commit = crate::commit::CommitBuilder::new("subject".into(), "actor".into()).sign("private_key");
let commit = crate::commit::CommitBuilder::new("subject".into(), "actor".into()).sign("private_key").unwrap();
post_commit("https://atomicdata.dev/commit", &commit).unwrap();
}
}
87 changes: 45 additions & 42 deletions lib/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

use crate::{
datatype::DataType, errors::AtomicResult, resources::PropVals, urls, Resource,
Storelike, Value,
datatype::DataType, errors::AtomicResult, resources::PropVals, urls, Resource, Storelike, Value,
};

/// A Commit is a set of changes to a Resource.
Expand All @@ -17,28 +16,23 @@ pub struct Commit {
/// The date it was created, as a unix timestamp
pub created_at: u128,
/// The URL of the one suggesting this Commit
pub actor: String,
pub signer: String,
/// The set of PropVals that need to be added.
/// Overwrites existing values
pub set: Option<std::collections::HashMap<String, String>>,
/// The set of property URLs that need to be removed
pub remove: Option<Vec<String>>,
/// If set to true, deletes the entire resource
pub destroy: Option<bool>,
/// Hash signed by the actor
/// Base64 encoded signature of the JSON serialized Commit
pub signature: String,
}

impl Commit {
/// Converts the Commit into a HashMap of strings.
/// Creates an identifier using the base_url or a default.
pub fn into_resource<'a>(self, store: &'a dyn Storelike) -> AtomicResult<Resource<'a>> {
let default_base_url = String::from("https://localhost/");
let subject = format!(
"{}commits/{}",
store.get_base_url().unwrap_or(default_base_url),
self.signature
);
let subject = format!("{}commits/{}", store.get_base_url(), self.signature);
let mut resource = Resource::new_instance(urls::COMMIT, store)?;
resource.set_subject(subject);
resource.set_propval(
Expand All @@ -50,8 +44,8 @@ impl Commit {
Value::new(&self.created_at.to_string(), &DataType::Timestamp).unwrap(),
)?;
resource.set_propval(
urls::ACTOR.into(),
Value::new(&self.actor, &DataType::AtomicUrl).unwrap(),
urls::SIGNER.into(),
Value::new(&self.signer, &DataType::AtomicUrl).unwrap(),
)?;
if self.set.is_some() {
let mut newset = PropVals::new();
Expand All @@ -70,13 +64,10 @@ impl Commit {
resource.set_propval(urls::DESTROY.into(), true.into())?;
}
resource.set_propval(
urls::ACTOR.into(),
Value::new(&self.actor, &DataType::AtomicUrl).unwrap(),
)?;
resource.set_propval(
urls::SIGNATURE.into(),
self.signature.into(),
urls::SIGNER.into(),
Value::new(&self.signer, &DataType::AtomicUrl).unwrap(),
)?;
resource.set_propval(urls::SIGNATURE.into(), self.signature.into())?;
Ok(resource)
}
}
Expand All @@ -89,7 +80,7 @@ pub struct CommitBuilder {
/// The date it was created, as a unix timestamp
created_at: Option<u128>,
/// The URL of the one suggesting this Commit
actor: String,
signer: String,
/// The set of PropVals that need to be added.
/// Overwrites existing values
set: std::collections::HashMap<String, String>,
Expand All @@ -101,11 +92,11 @@ pub struct CommitBuilder {
}

impl CommitBuilder {
pub fn new(subject: String, actor: String) -> Self {
pub fn new(subject: String, signer: String) -> Self {
CommitBuilder {
subject,
created_at: None,
actor,
signer,
set: HashMap::new(),
remove: HashSet::new(),
destroy: false,
Expand All @@ -114,32 +105,41 @@ impl CommitBuilder {

/// Creates the Commit and signs it using a signature.
/// Does not send it - see atomic_lib::client::post_commit
pub fn sign(mut self, _private_key: &str) -> Commit {
self.created_at = Some(std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards")
.as_millis());
pub fn sign(mut self, private_key: &str) -> AtomicResult<Commit> {
self.created_at = Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("You're a time traveler")
.as_millis(),
);

// Todo: Implement signature
// let string = serde_json::to_string(&self);
// TODO: use actual stringified resource, also change in Storelike::commit
// let stringified = serde_json::to_string(&self)?;
let stringified = "full_resource";
let private_key_bytes = base64::decode(private_key)?;
let key_pair = ring::signature::Ed25519KeyPair::from_pkcs8(&private_key_bytes)
.map_err(|_| "Can't create keypair")?;
// let signature = some_lib::sign(string, private_key);
let signature = base64::encode(key_pair.sign(&stringified.as_bytes()));

Commit {
Ok(Commit {
subject: self.subject,
actor: self.actor,
signer: self.signer,
set: Some(self.set),
remove: Some(self.remove.into_iter().collect()),
destroy: Some(self.destroy),
created_at: self.created_at.unwrap(),
// TODO: Hashing signature logic
signature: "correct_signature".into(),
}
signature,
})
}

/// Set Property / Value combinations that will either be created or overwritten.
pub fn set(&mut self, prop: String, val: String) {
self.set.insert(prop, val);
}

/// Set Property URLs which values to be removed
pub fn remove(&mut self, prop: String) {
self.remove.insert(prop);
}
Expand All @@ -156,21 +156,24 @@ mod test {
use crate::Storelike;

#[test]
fn apply_commit() {
fn agent_and_commit() {
let store = crate::Store::init();
store.populate().unwrap();
let subject = String::from("https://example.com/somesubject");
let actor = "HashedThing".into();
let mut partial_commit = CommitBuilder::new(subject.clone(), actor);
// Creates a new Agent with some crypto stuff
let (agent_subject, private_key) = store.create_agent("test_actor").unwrap();
let subject = "https://localhost/new_thing";
let mut commitbuiler = crate::commit::CommitBuilder::new(subject.into(), agent_subject);
let property = crate::urls::DESCRIPTION;
let value = "Some value";
partial_commit.set(property.into(), value.into());
let full_commit = partial_commit.sign("correct_signature");
let stored_commit = store.commit(full_commit).unwrap();
commitbuiler.set(property.into(), value.into());
let commit = commitbuiler.sign(&private_key).unwrap();
let commit_subject = commit.subject.clone();
let _created_resource = store.commit(commit).unwrap();

let resource = store.get_resource(&subject).unwrap();
assert!(resource.get(property).unwrap().to_string() == value);
let found_commit = store.get_resource(stored_commit.get_subject()).unwrap();
println!("{}",found_commit.get_subject());
assert!(found_commit.get_shortname("subject").unwrap().to_string() == subject);
let found_commit = store.get_resource(&commit_subject).unwrap();
println!("{}", found_commit.get_subject());
assert!(found_commit.get_shortname("description").unwrap().to_string() == value);
}
}
7 changes: 3 additions & 4 deletions lib/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ impl<'a> Resource<'a> {
let subject = format!(
"{}/{}/{}",
store
.get_base_url()
.ok_or("No base_url set in this store.")?,
.get_base_url(),
classes_vec[0].shortname.clone(),
random_string
);
Expand Down Expand Up @@ -130,7 +129,7 @@ impl<'a> Resource<'a> {
self.save().ok();
}

/// Tries to resolve the shortname to a URL.
/// Tries to resolve the shortname of a Property to a Property URL.
// Currently assumes that classes have been set before.
pub fn resolve_shortname(&mut self, shortname: &str) -> AtomicResult<Option<Property>> {
let classes = self.get_classes()?;
Expand Down Expand Up @@ -181,7 +180,7 @@ impl<'a> Resource<'a> {
let fullprop = if is_url(property) {
self.store.get_property(property)?
} else {
self.resolve_shortname(property)?.unwrap()
self.resolve_shortname(property)?.ok_or(format!("Shortname {} not found in {}", property, self.get_subject()))?
};
let fullval = Value::new(value, &fullprop.data_type)?;
self.set_propval(fullprop.subject, fullval)?;
Expand Down
Loading

0 comments on commit 0491cae

Please sign in to comment.