Skip to content

Commit

Permalink
Add key collection backend
Browse files Browse the repository at this point in the history
  • Loading branch information
ThetaSinner committed Feb 18, 2024
1 parent 6cd58e4 commit 7e3fc86
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 28 deletions.
8 changes: 2 additions & 6 deletions dnas/trusted/zomes/coordinator/trusted/src/gpg_key_dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ pub fn distribute_gpg_key(request: DistributeGpgKeyRequest) -> ExternResult<Reco
expires_at: summary.expires_at,
}))?;

let record = get(gpg_key_dist_hash.clone(), GetOptions::default())?.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly created GpgKey"))
let record = get(gpg_key_dist_hash.clone(), GetOptions::content())?.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly created GpgKeyDist"))
))?;

let entry_hash = record
Expand All @@ -63,8 +63,6 @@ pub fn distribute_gpg_key(request: DistributeGpgKeyRequest) -> ExternResult<Reco
)?;
}

tracing::info!("Fingerprint: {}", summary.fingerprint);

create_link(
make_base_hash(summary.fingerprint)?,
entry_hash.clone(),
Expand Down Expand Up @@ -119,8 +117,6 @@ pub fn search_keys(request: SearchKeysRequest) -> ExternResult<Vec<Record>> {
links.extend(email_links);
links.extend(fingerprint_links.clone());

tracing::info!("Found {} links and {} by fingerprint", links.len(), fingerprint_links.len());

let mut out = Vec::with_capacity(links.len());
for target in links
.into_iter()
Expand Down
85 changes: 85 additions & 0 deletions dnas/trusted/zomes/coordinator/trusted/src/key_collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use hdk::prelude::*;
use trusted_integrity::prelude::*;

#[hdk_extern]
pub fn create_key_collection(key_collection: KeyCollection) -> ExternResult<Record> {
check_key_collection_create(&key_collection)?;

let entry = EntryTypes::KeyCollection(key_collection);
let action_hash = create_entry(entry)?;

let record = get(action_hash.clone(), GetOptions::content())?.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from(
"Could not find the newly created KeyCollection"
))
))?;

let entry_hash = hash_entry(
record
.entry()
.as_option()
.ok_or_else(|| wasm_error!(WasmErrorInner::Guest(String::from("Missing entry hash"))))?
.clone(),
)?;
let my_agent_info = agent_info()?;
create_link(
my_agent_info.agent_latest_pubkey,
entry_hash,
LinkTypes::KeyCollection,
(),
)?;

Ok(record)
}

fn check_key_collection_create(key_collection: &KeyCollection) -> ExternResult<()> {
let existing_key_collections = query(
ChainQueryFilter::default()
.entry_type(EntryType::App(UnitEntryTypes::KeyCollection.try_into()?)),
)?;

// This is enforced by validation, but checked here for faster feedback
if existing_key_collections.len() >= KEY_COLLECTION_LIMIT {
return Err(wasm_error!(WasmErrorInner::Guest(String::from(
"Exceeded the maximum number of key collections",
))));
}

let names: HashSet<_> = existing_key_collections.into_iter().filter_map(|kc| match kc.entry().as_option().and_then(|e| e.as_app_entry()) {
Some(entry_bytes) => {
let key_collection: KeyCollection = entry_bytes.clone().into_sb().try_into().ok()?;
Some(key_collection.name)
}
None => None
}).collect();

// Not checked by validation, other users do not care about your key collection names being unique. The entries are private
// so they can't actually see them!
if names.contains(&key_collection.name) {
return Err(wasm_error!(WasmErrorInner::Guest(String::from(
"Key collection with the same name already exists",
))));
}

Ok(())
}

#[hdk_extern]
pub fn my_key_collections(_: ()) -> ExternResult<Vec<Record>> {
let my_agent_info = agent_info()?;
let key_collection_links = get_links(GetLinksInputBuilder::try_new(my_agent_info.agent_latest_pubkey, LinkTypes::KeyCollection)?.build())?;

let mut records = Vec::with_capacity(key_collection_links.len());
for link in key_collection_links {
let entry_hash: EntryHash = link.target.clone().try_into().map_err(|_| {
wasm_error!(WasmErrorInner::Guest(String::from("Could not convert link target to EntryHash")))
})?;
let record = get(entry_hash, GetOptions::content())?.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the KeyCollection"))
))?;
// No need to type check these records, validation ensures they are all KeyCollections
records.push(record);
}

Ok(records)
}
1 change: 1 addition & 0 deletions dnas/trusted/zomes/coordinator/trusted/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod gpg_key_dist;
mod key_collection;
mod gpg_util;

use hdk::prelude::*;
Expand Down
32 changes: 14 additions & 18 deletions dnas/trusted/zomes/integrity/trusted/src/gpg_key_dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,20 @@ pub fn validate_create_gpg_key_dist_link(
let entry = must_get_entry(entry_hash)?;
match entry.as_app_entry() {
Some(app_entry) => {
let _: crate::gpg_key_dist::GpgKeyDist = match app_entry.clone().into_sb().try_into() {
Ok(gpg_key) => gpg_key,
Err(_) => {
return Ok(ValidateCallbackResult::Invalid(format!(
"The target for {:?} must be a {}",
link_type,
std::any::type_name::<crate::gpg_key_dist::GpgKeyDist>()
)));
}
};
}
None => {
return Ok(ValidateCallbackResult::Invalid(format!(
"The target for {:?} must be an app entry",
link_type
)));
match <SerializedBytes as TryInto<crate::gpg_key_dist::GpgKeyDist>>::try_into(
app_entry.clone().into_sb(),
) {
Ok(_) => Ok(ValidateCallbackResult::Valid),
Err(_) => Ok(ValidateCallbackResult::Invalid(format!(
"The target for {:?} must be a {}",
link_type,
std::any::type_name::<crate::gpg_key_dist::GpgKeyDist>()
))),
}
}
None => Ok(ValidateCallbackResult::Invalid(format!(
"The target for {:?} must be an app entry",
link_type
))),
}

Ok(ValidateCallbackResult::Valid)
}
112 changes: 112 additions & 0 deletions dnas/trusted/zomes/integrity/trusted/src/key_collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use hdi::prelude::*;

use crate::LinkTypes;
use crate::UnitEntryTypes;

pub const KEY_COLLECTION_LIMIT: usize = 10;
pub const KEY_COLLECTION_NAME_MIN_LENGTH: usize = 3;

#[hdk_entry_helper]
pub struct KeyCollection {
pub name: String,
}

pub fn validate_create_key_collection(
create_action: EntryCreationAction,
key_collection: KeyCollection,
) -> ExternResult<ValidateCallbackResult> {
if key_collection.name.len() < KEY_COLLECTION_NAME_MIN_LENGTH {
return Ok(ValidateCallbackResult::Invalid(
format!(
"Key collection name must be at least {} characters long",
KEY_COLLECTION_NAME_MIN_LENGTH
)
.to_string(),
));
}

let entry_def: AppEntryDef = UnitEntryTypes::KeyCollection.try_into()?;

let action: Action = create_action.clone().into();
let action_hash = hash_action(action.clone())?;
let activity = must_get_agent_activity(action.author().clone(), ChainFilter::new(action_hash))?;

// Find all key collection creates
let mut key_collection_creates: HashSet<_> = activity
.iter()
.filter_map(|activity| match activity.action.action() {
Action::Create(Create {
entry_type: EntryType::App(app_entry),
entry_hash,
..
}) if app_entry == &entry_def => Some(entry_hash),
_ => None,
})
.collect();

// Run through every delete and grab the entry hash that it deletes, then remove those entries from the key collection set
activity
.iter()
.filter_map(|activity| match activity.action.action() {
Action::Delete(Delete {
deletes_entry_address,
..
}) => Some(deletes_entry_address),
_ => None,
})
.for_each(|entry_address| {
key_collection_creates.remove(&entry_address);
});

// Now check the remaining number of key collections is under the limit
// Note that being at the limit is allowed because the newly created key collection is already in the agent activity.
if key_collection_creates.len() > KEY_COLLECTION_LIMIT {
return Ok(ValidateCallbackResult::Invalid(
"Exceeded the maximum number of key collections".to_string(),
));
}

Ok(ValidateCallbackResult::Valid)
}

pub fn validate_key_collection_link(
action: CreateLink,
base_address: AnyLinkableHash,
target_address: AnyLinkableHash,
link_type: LinkTypes,
) -> ExternResult<ValidateCallbackResult> {
let action_author_anylinkable: AnyLinkableHash = action.author.clone().into();

if action_author_anylinkable != base_address {
return Ok(ValidateCallbackResult::Invalid(
"Key collection must be linked from the author".to_string(),
));
}

let entry_hash = match target_address.clone().try_into() {
Ok(entry_hash) => entry_hash,
Err(_) => {
return Ok(ValidateCallbackResult::Invalid(format!(
"The target address for {:?} must be an entry hash",
link_type
)));
}
};
let entry = must_get_entry(entry_hash)?;
match entry.as_app_entry() {
Some(app_entry) => {
match <SerializedBytes as TryInto<crate::key_collection::KeyCollection>>::try_into(app_entry.clone().into_sb()) {
Ok(_) => Ok(ValidateCallbackResult::Valid),
Err(_) => Ok(ValidateCallbackResult::Invalid(format!(
"The target for {:?} must be a {}",
link_type,
std::any::type_name::<crate::key_collection::KeyCollection>()
))),
}
}
None => Ok(ValidateCallbackResult::Invalid(format!(
"The target for {:?} must be an app entry",
link_type
))),
}
}
Loading

0 comments on commit 7e3fc86

Please sign in to comment.