From 0c601b383bee3d1265b93d8f92cc27c75c6ebedb Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Sat, 6 Apr 2024 00:12:50 +0100 Subject: [PATCH] Check fetched signatures and report --- checked_cli/Cargo.lock | 1 + checked_cli/Cargo.toml | 1 + checked_cli/src/fetch.rs | 157 +++++++++++++++++- checked_cli/tests/happ_integration.rs | 2 +- .../zomes/coordinator/fetch/src/lib.rs | 150 ++++++++++++++--- .../signing_keys/src/verification_key_dist.rs | 48 ++++-- tests/src/checked/fetch/common.ts | 21 ++- tests/src/checked/fetch/fetch.test.ts | 4 +- types/checked_types/src/lib.rs | 15 +- types/fetch/src/lib.rs | 2 +- 10 files changed, 339 insertions(+), 62 deletions(-) diff --git a/checked_cli/Cargo.lock b/checked_cli/Cargo.lock index 5888a3a..f14bad4 100644 --- a/checked_cli/Cargo.lock +++ b/checked_cli/Cargo.lock @@ -833,6 +833,7 @@ dependencies = [ "holochain_conductor_api", "holochain_types", "indicatif", + "itertools 0.12.1", "minisign", "rand 0.8.5", "reqwest 0.12.2", diff --git a/checked_cli/Cargo.toml b/checked_cli/Cargo.toml index 8b34075..961fc59 100644 --- a/checked_cli/Cargo.toml +++ b/checked_cli/Cargo.toml @@ -24,6 +24,7 @@ ed25519-dalek = "2.1.1" serde = { version = "1", features = ["derive"] } serde_json = "1" rand = "0.8.5" +itertools = "0.12.1" [target.'cfg(any(windows, unix))'.dependencies] dirs = "5.0.1" diff --git a/checked_cli/src/fetch.rs b/checked_cli/src/fetch.rs index 82006de..7f21688 100644 --- a/checked_cli/src/fetch.rs +++ b/checked_cli/src/fetch.rs @@ -7,12 +7,16 @@ use crate::prelude::SignArgs; use crate::sign::sign; use anyhow::Context; use checked_types::{ - CreateAssetSignature, FetchCheckSignature, PrepareFetchRequest, VerificationKeyType, + CreateAssetSignature, FetchCheckSignature, FetchCheckSignatureReason, PrepareFetchRequest, + VerificationKeyType, }; use holochain_client::ZomeCallTarget; -use holochain_types::prelude::ExternIO; +use holochain_types::prelude::{ActionHash, AgentPubKey, ExternIO}; use indicatif::{ProgressFinish, ProgressStyle}; -use std::io::{BufWriter, Write}; +use itertools::Itertools; +use minisign::PublicKeyBox; +use std::fs::File; +use std::io::{BufReader, BufWriter, Seek, SeekFrom, Write}; use std::path::PathBuf; use std::sync::atomic::AtomicUsize; use std::sync::Arc; @@ -125,7 +129,8 @@ pub async fn fetch(fetch_args: FetchArgs) -> anyhow::Result { println!("Downloaded to {:?}", path); - // TODO validate the signatures here and report + let reports = check_signatures(path.clone(), response)?; + show_report(&reports); std::fs::rename(path.clone(), &output_path)?; @@ -153,7 +158,7 @@ pub async fn fetch(fetch_args: FetchArgs) -> anyhow::Result { "create_asset_signature".into(), ExternIO::encode(CreateAssetSignature { fetch_url: fetch_args.url.clone(), - signature: std::fs::read(&signature_path)?, + signature: std::fs::read_to_string(&signature_path)?, key_type: VerificationKeyType::MiniSignEd25519, verification_key: std::fs::read_to_string(vk_path)?, }) @@ -169,6 +174,148 @@ pub async fn fetch(fetch_args: FetchArgs) -> anyhow::Result { }) } +pub struct CheckedSignature { + pub key_dist_address: ActionHash, + pub author: AgentPubKey, +} + +pub struct SignatureCheckReport { + reason: FetchCheckSignatureReason, + passed_signatures: Vec, + failed_signatures: Vec, +} + +pub fn check_signatures( + check_file: PathBuf, + signatures: Vec, +) -> anyhow::Result> { + let check_file = File::options().read(true).open(check_file)?; + let mut check_file_reader = BufReader::new(check_file); + + let mut signature_reports = Vec::new(); + for (reason, sigs) in signatures.iter().group_by(|s| s.reason.clone()).into_iter() { + let mut group_report = SignatureCheckReport { + reason, + passed_signatures: vec![], + failed_signatures: vec![], + }; + for sig in sigs { + println!("Checking signature from {:?}... ", sig.author); + match check_one_signature(&mut check_file_reader, sig) { + Ok(true) => group_report.passed_signatures.push(CheckedSignature { + key_dist_address: sig.key_dist_address.clone(), + author: sig.author.clone(), + }), + Ok(false) => group_report.failed_signatures.push(CheckedSignature { + key_dist_address: sig.key_dist_address.clone(), + author: sig.author.clone(), + }), + Err(e) => { + println!("Error during verification: {:?}", e); + group_report.failed_signatures.push(CheckedSignature { + key_dist_address: sig.key_dist_address.clone(), + author: sig.author.clone(), + }) + } + } + check_file_reader.seek(SeekFrom::Start(0))?; + } + signature_reports.push(group_report); + } + + Ok(signature_reports) +} + +fn check_one_signature( + check_file_reader: &mut BufReader, + sig: &FetchCheckSignature, +) -> anyhow::Result { + match sig.key_type { + VerificationKeyType::MiniSignEd25519 => { + let vf_key = PublicKeyBox::from_string(&sig.verification_key)?; + let sig = minisign::SignatureBox::from_string(&sig.signature)?; + + match minisign::verify(&vf_key.into(), &sig, check_file_reader, true, false, false) { + Ok(()) => Ok(true), + Err(_) => Ok(false), + } + } + } +} + +pub fn show_report(report: &[SignatureCheckReport]) { + println!("Looking for historical signatures:"); + let maybe_historical_report = report + .iter() + .find(|r| r.reason == FetchCheckSignatureReason::RandomHistorical); + if let Some(historical_report) = maybe_historical_report { + if !historical_report.passed_signatures.is_empty() + && historical_report.failed_signatures.is_empty() + { + println!("{} historical signature{} passed verification. This means that you are likely to have the same asset that was originally published.", historical_report.passed_signatures.len(), if historical_report.passed_signatures.len() == 1 { "" } else { "s" }); + } else if historical_report.passed_signatures.is_empty() + && !historical_report.failed_signatures.is_empty() + { + println!("{} historical signature{} failed verification. This means that you may not have the same asset that was originally published.", historical_report.failed_signatures.len(), if historical_report.failed_signatures.len() == 1 { "" } else { "s" }); + } else { + println!("{}/{} historical signatures failed verification. Inconsistent signatures do not mean that the asset you have fetched is valid or invalid but provides you with a piece of information you can use in making a judgement for yourself.", historical_report.passed_signatures.len(), historical_report.passed_signatures.len() + historical_report.failed_signatures.len()); + } + } else { + println!("No historical signatures were found."); + } + + println!("\nLooking for signatures from pinned keys:"); + let maybe_pinned_report = report + .iter() + .find(|r| matches!(r.reason, FetchCheckSignatureReason::Pinned(_))); + if let Some(pinned_report) = maybe_pinned_report { + for checked_sig in &pinned_report.passed_signatures { + println!( + "Signature from author {:?} with key {:?}: ✅", + checked_sig.author, checked_sig.key_dist_address + ); + } + for checked_sig in &pinned_report.failed_signatures { + println!( + "Signature from author {:?} with key {:?}: ❌", + checked_sig.author, checked_sig.key_dist_address + ); + } + + if !pinned_report.passed_signatures.is_empty() && pinned_report.failed_signatures.is_empty() + { + println!("{} pinned signature{} passed verification. This means that the asset you have fetched is likely to be the same as other pinned signatories are seeing.", pinned_report.passed_signatures.len(), if pinned_report.passed_signatures.len() == 1 { "" } else { "s" }); + } else if pinned_report.passed_signatures.is_empty() + && !pinned_report.failed_signatures.is_empty() + { + println!("{} pinned signature{} failed verification. This means that the asset you have fetched is likely not the same as other pinned signatories are seeing.", pinned_report.failed_signatures.len(), if pinned_report.failed_signatures.len() == 1 { "" } else { "s" }); + } else { + println!("{}/{} pinned signatures failed verification. Please ensure that your key collections only contain keys from signatories you trust. If you are happy with your pinned keys then consider contacting the author to see if you have received different assets.", pinned_report.passed_signatures.len(), pinned_report.passed_signatures.len() + pinned_report.failed_signatures.len()); + } + } else { + println!("No pinned signatures were found."); + } + + println!("\nLooking for recent signatures:"); + let maybe_recent_report = report + .iter() + .find(|r| r.reason == FetchCheckSignatureReason::RandomRecent); + if let Some(recent_report) = maybe_recent_report { + if !recent_report.passed_signatures.is_empty() && recent_report.failed_signatures.is_empty() + { + println!("{} recent signature{} passed verification. This means that the asset you have fetched is likely to be the same as the one that others have been getting recently.", recent_report.passed_signatures.len(), if recent_report.passed_signatures.len() == 1 { "" } else { "s" }); + } else if recent_report.passed_signatures.is_empty() + && !recent_report.failed_signatures.is_empty() + { + println!("{} recent signature{} failed verification. This means that the asset you have fetched is likely not the same as the one that others have been getting recently.", recent_report.failed_signatures.len(), if recent_report.failed_signatures.len() == 1 { "" } else { "s" }); + } else { + println!("{}/{} recent signatures failed verification. Inconsistent signatures do not mean that the asset you have fetched is valid or invalid but provides you with a piece of information you can use in making a judgement for yourself.", recent_report.passed_signatures.len(), recent_report.passed_signatures.len() + recent_report.failed_signatures.len()); + } + } else { + println!("No recent signatures were found."); + } +} + fn get_output_path(fetch_args: &FetchArgs, fetch_url: &Url) -> anyhow::Result { let guessed_file_name = fetch_url .path_segments() diff --git a/checked_cli/tests/happ_integration.rs b/checked_cli/tests/happ_integration.rs index 9c22e6b..7dbe855 100644 --- a/checked_cli/tests/happ_integration.rs +++ b/checked_cli/tests/happ_integration.rs @@ -132,7 +132,7 @@ async fn create_first_asset_signature() -> anyhow::Result<()> { assert_eq!(1, signatures.len()); assert_eq!(url, signatures[0].fetch_url); assert_eq!( - std::fs::read(fetch_info.signature_path.unwrap())?, + std::fs::read_to_string(fetch_info.signature_path.unwrap())?, signatures[0].signature ); diff --git a/dnas/checked/zomes/coordinator/fetch/src/lib.rs b/dnas/checked/zomes/coordinator/fetch/src/lib.rs index 1b37bd3..8478373 100644 --- a/dnas/checked/zomes/coordinator/fetch/src/lib.rs +++ b/dnas/checked/zomes/coordinator/fetch/src/lib.rs @@ -181,6 +181,11 @@ fn pick_signatures( possible_signatures: Vec, key_collections: Vec, ) -> ExternResult> { + info!( + "Selecting from {} possible signatures", + possible_signatures.len() + ); + let mut possible_signatures: Vec<(Action, AssetSignature)> = possible_signatures .into_iter() .filter_map(|record| { @@ -216,8 +221,11 @@ fn pick_signatures( if let Some((action, sig)) = matched_signature { picked_signatures.push(FetchCheckSignature { signature: sig.signature.clone(), + key_type: key.verification_key_dist.key_type, + verification_key: key.verification_key_dist.verification_key, + author: action.author().clone(), + key_dist_address: sig.key_dist_address.clone(), reason: FetchCheckSignatureReason::Pinned(FetchCheckSignaturePinned { - author: action.author().clone(), key_collection: key_collection.name.clone(), key_name: key.verification_key_dist.name.clone(), }), @@ -235,7 +243,10 @@ fn pick_signatures( possible_signatures.sort_by(|(a, _), (b, _)| a.timestamp().cmp(&b.timestamp())); - picked_signatures.extend(select_early_signatures(&possible_signatures)); + picked_signatures.extend(select_historical_signatures( + &possible_signatures, + get_vf_key_dist, + )); // Drop signatures that we've already picked from the possible set. possible_signatures.retain(|(_, asset_signature)| { @@ -244,7 +255,11 @@ fn pick_signatures( .any(|p| p.signature == asset_signature.signature) }); - picked_signatures.extend(select_recent_signatures(sys_time()?, &possible_signatures)?); + picked_signatures.extend(select_recent_signatures( + sys_time()?, + &possible_signatures, + get_vf_key_dist, + )?); Ok(picked_signatures) } @@ -255,8 +270,9 @@ fn pick_signatures( /// This function assumes that the input is sorted by the [Action] timestamp. /// /// The reason on the [FetchCheckSignature] will be [FetchCheckSignatureReason::RandomHistorical]. -fn select_early_signatures( +fn select_historical_signatures( possible_signatures: &[(Action, AssetSignature)], + fetcher: VfKeyDistFetcher, ) -> Vec { let earliest = match possible_signatures.first().map(|(a, _)| a.timestamp()) { Some(earliest) => earliest, @@ -276,15 +292,34 @@ fn select_early_signatures( Some(x) => x, }; + info!( + "Selecting up to 5 signatures randomly from {} possible historical signatures", + take_many + ); + let mut rng = &mut thread_rng(); possible_signatures .iter() .take(take_many) .choose_multiple(&mut rng, 5) .iter() - .map(|(_, sig)| FetchCheckSignature { - signature: sig.signature.clone(), - reason: FetchCheckSignatureReason::RandomHistorical, + .filter_map(|(action, sig)| { + match fetcher(&sig.key_dist_address) { + Ok(Some(vf_key_dist)) => { + Some(FetchCheckSignature { + signature: sig.signature.clone(), + key_type: vf_key_dist.verification_key_dist.key_type, + verification_key: vf_key_dist.verification_key_dist.verification_key, + author: action.author().clone(), + key_dist_address: sig.key_dist_address.clone(), + reason: FetchCheckSignatureReason::RandomHistorical, + }) + }, + _ => { + warn!("Discarding possible signature because the key distribution could not be fetched: {:?}", sig.key_dist_address); + None + }, + } }) .collect() } @@ -298,6 +333,7 @@ fn select_early_signatures( fn select_recent_signatures( current_time: Timestamp, possible_signatures: &[(Action, AssetSignature)], + fetcher: VfKeyDistFetcher, ) -> ExternResult> { let take_after = current_time .sub(Duration::from_secs(60 * 60 * 24 * 7)) @@ -316,15 +352,30 @@ fn select_recent_signatures( let mut rng = &mut thread_rng(); + info!( + "Selecting up to 5 signatures randomly from {} possible recent signatures", + take_many + ); + Ok(possible_signatures .iter() .rev() .take(take_many) .choose_multiple(&mut rng, 5) .iter() - .map(|(_, sig)| FetchCheckSignature { - signature: sig.signature.clone(), - reason: FetchCheckSignatureReason::RandomRecent, + .filter_map(|(action, sig)| match fetcher(&sig.key_dist_address) { + Ok(Some(vf_key_dist)) => Some(FetchCheckSignature { + signature: sig.signature.clone(), + key_type: vf_key_dist.verification_key_dist.key_type, + verification_key: vf_key_dist.verification_key_dist.verification_key, + author: action.author().clone(), + key_dist_address: sig.key_dist_address.clone(), + reason: FetchCheckSignatureReason::RandomRecent, + }), + _ => { + warn!("Discarding possible signature because the key distribution could not be fetched: {:?}", sig.key_dist_address); + None + }, }) .collect()) } @@ -341,6 +392,33 @@ fn make_asset_url_address(asset_url: &str) -> ExternResult { Ok(ExternalHash::from_raw_36(hash)) } +type VfKeyDistFetcher = fn(&ActionHash) -> ExternResult>; + +fn get_vf_key_dist(vf_key_dist_address: &ActionHash) -> ExternResult> { + let response = call( + CallTargetCell::Local, + "signing_keys".to_string(), + "get_verification_key_dist".into(), + None, + vf_key_dist_address.clone(), + )?; + + match response { + ZomeCallResponse::Ok(response) => { + let response: Option = response.decode().map_err(|e| { + wasm_error!(WasmErrorInner::Guest(format!( + "Failed to decode get_verification_key_dist response: {:?}", + e + ))) + })?; + Ok(response) + } + _ => Err(wasm_error!(WasmErrorInner::Guest( + "Unexpected response from get_verification_key_dist".to_string() + ))), + } +} + struct KeyConvertible(Option); impl Deref for KeyConvertible { @@ -359,18 +437,20 @@ impl From<&str> for KeyConvertible { #[cfg(test)] mod tests { - use crate::{select_early_signatures, select_recent_signatures}; + use crate::{select_historical_signatures, select_recent_signatures}; + use checked_types::VerificationKeyType; use fetch_types::AssetSignature; use hdk::prelude::{ Action, ActionHash, AgentPubKey, AppEntryDef, Create, EntryHash, EntryRateWeight, EntryType, EntryVisibility, Timestamp, }; + use signing_keys_types::{VerificationKeyDistResponse, VfKeyResponse}; use std::ops::Sub; use std::time::Duration; #[test] fn select_early_signatures_empty() { - let picked = select_early_signatures(&[]); + let picked = select_historical_signatures(&[], test_fetcher); assert_eq!(0, picked.len()); } @@ -391,14 +471,15 @@ mod tests { ( a, AssetSignature { - signature: vec![idx as u8], + fetch_url: "http://example.com".to_string(), + signature: String::from_utf8(vec![idx as u8]).unwrap(), key_dist_address: ActionHash::from_raw_36(vec![0; 36]), }, ) }) .collect::>(); - let picked = select_early_signatures(&possible_signatures); + let picked = select_historical_signatures(&possible_signatures, test_fetcher); // Picked 5 assert_eq!(5, picked.len()); @@ -425,14 +506,15 @@ mod tests { ( a, AssetSignature { - signature: vec![idx as u8], + fetch_url: "http://example.com".to_string(), + signature: String::from_utf8(vec![idx as u8]).unwrap(), key_dist_address: ActionHash::from_raw_36(vec![0; 36]), }, ) }) .collect::>(); - let picked = select_early_signatures(&possible_signatures); + let picked = select_historical_signatures(&possible_signatures, test_fetcher); // Picked 5 assert_eq!(5, picked.len()); @@ -445,7 +527,7 @@ mod tests { #[test] fn select_recent_signatures_empty() { - let picked = select_recent_signatures(current_time(), &[]).unwrap(); + let picked = select_recent_signatures(current_time(), &[], test_fetcher).unwrap(); assert_eq!(0, picked.len()); } @@ -466,14 +548,16 @@ mod tests { ( a, AssetSignature { - signature: vec![idx as u8], + fetch_url: "http://example.com".to_string(), + signature: String::from_utf8(vec![idx as u8]).unwrap(), key_dist_address: ActionHash::from_raw_36(vec![0; 36]), }, ) }) .collect::>(); - let picked = select_recent_signatures(current_time(), &possible_signatures).unwrap(); + let picked = + select_recent_signatures(current_time(), &possible_signatures, test_fetcher).unwrap(); // Picked 5 assert_eq!(5, picked.len()); @@ -498,14 +582,16 @@ mod tests { ( a, AssetSignature { - signature: vec![idx as u8], + fetch_url: "http://example.com".to_string(), + signature: String::from_utf8(vec![idx as u8]).unwrap(), key_dist_address: ActionHash::from_raw_36(vec![0; 36]), }, ) }) .collect::>(); - let picked = select_recent_signatures(current_time(), &possible_signatures).unwrap(); + let picked = + select_recent_signatures(current_time(), &possible_signatures, test_fetcher).unwrap(); // Picked 5 assert_eq!(5, picked.len()); @@ -528,14 +614,16 @@ mod tests { ( a, AssetSignature { - signature: vec![idx as u8], + fetch_url: "http://example.com".to_string(), + signature: String::from_utf8(vec![idx as u8]).unwrap(), key_dist_address: ActionHash::from_raw_36(vec![0; 36]), }, ) }) .collect::>(); - let picked = select_recent_signatures(current_time(), &possible_signatures).unwrap(); + let picked = + select_recent_signatures(current_time(), &possible_signatures, test_fetcher).unwrap(); // Picked 5 assert_eq!(5, picked.len()); @@ -563,4 +651,20 @@ mod tests { weight: EntryRateWeight::default(), }) } + + fn test_fetcher(_: &ActionHash) -> crate::ExternResult> { + Ok(Some(VfKeyResponse { + verification_key_dist: VerificationKeyDistResponse { + verification_key: "test key".to_string(), + key_type: VerificationKeyType::MiniSignEd25519, + name: "test".to_string(), + expires_at: None, + marks: vec![], + }, + key_dist_address: ActionHash::from_raw_36(vec![0; 36]), + reference_count: 0, + author: AgentPubKey::from_raw_36(vec![0; 36]), + created_at: Timestamp(0), + })) + } } diff --git a/dnas/checked/zomes/coordinator/signing_keys/src/verification_key_dist.rs b/dnas/checked/zomes/coordinator/signing_keys/src/verification_key_dist.rs index c5e5c02..bddb273 100644 --- a/dnas/checked/zomes/coordinator/signing_keys/src/verification_key_dist.rs +++ b/dnas/checked/zomes/coordinator/signing_keys/src/verification_key_dist.rs @@ -31,6 +31,21 @@ pub fn distribute_verification_key(request: DistributeVfKeyRequest) -> ExternRes Ok(record) } +#[hdk_extern] +pub fn get_verification_key_dist( + vf_key_dist_address: ActionHash, +) -> ExternResult> { + let record = get(vf_key_dist_address, GetOptions::network())?; + match record { + None => Ok(None), + Some(r) => { + let r = Ok(Some(build_vf_key_dist_response(r)?)); + info!("Returning VerificationKeyDist: {:?}", r); + r + } + } +} + #[hdk_extern] pub fn get_my_verification_key_distributions() -> ExternResult> { let q = ChainQueryFilter::default() @@ -45,25 +60,30 @@ pub fn get_my_verification_key_distributions() -> ExternResult ExternResult { + let author = record.action().author().clone(); + let created_at = record.action().timestamp(); + let key_dist_address = record.action_address().clone(); + let vf_key_dist: VerificationKeyDist = convert_to_app_entry_type(record)?; + let marks = get_key_marks(key_dist_address.clone(), GetOptions::local())?; + let reference_count = + get_key_collections_reference_count(key_dist_address.clone(), &GetOptions::local())?; + + Ok(VfKeyResponse { + verification_key_dist: (vf_key_dist, marks).into(), + key_dist_address, + reference_count, + author, + created_at, + }) +} + #[derive(Serialize, Deserialize, Debug, Clone, SerializedBytes)] pub struct SearchKeysRequest { pub agent_pub_key: Option, diff --git a/tests/src/checked/fetch/common.ts b/tests/src/checked/fetch/common.ts index 8bea6f9..8007a18 100644 --- a/tests/src/checked/fetch/common.ts +++ b/tests/src/checked/fetch/common.ts @@ -1,5 +1,5 @@ import { CallableCell } from "@holochain/tryorama"; -import { AgentPubKey } from "@holochain/client"; +import { ActionHash, AgentPubKey } from "@holochain/client"; const utf8Encode = new TextEncoder(); @@ -8,7 +8,6 @@ export interface PrepareFetchRequest { } export interface FetchCheckSignaturePinned { - author: AgentPubKey; key_collection: string; key_name: string; } @@ -20,12 +19,16 @@ export type FetchCheckSignatureReason = export interface FetchCheckSignature { signature: Uint8Array; + key_type: { MiniSignEd25519: null }; + verification_key: string; + author: AgentPubKey; + key_dist_address: ActionHash; reason: FetchCheckSignatureReason; } export interface CreateAssetSignature { fetch_url: string; - signature: number[]; + signature: string; key_type: { MiniSignEd25519: null }; verification_key: string; } @@ -97,23 +100,19 @@ zvqpLC4ZziJ5Z8DRtugyjkDn/oHtKu6o71acc0H9dnCmpJVdYsyXxVcvKCCUSNTe7dQDm3Dc7LGvsgYX // Primary signature for `sampleFetchAsset` export const sampleFetchAssetSignature = () => { // Similar with the signature, this must be EXACT. Removing whitespace is permitted but extra whitespace is not. - return Array.from( - utf8Encode.encode(`untrusted comment: signature from rsign secret key + return `untrusted comment: signature from rsign secret key RUTxWagDsFvtt7zP24HuGOxNlWk93OYpP9dJJ3k6y+ZEQ7Ym56Loy5/KusNLnWHi0PCOB5ore+kK+wfImf+ZwFewvLuBPE3tYAU= trusted comment: timestamp:1711592494\tfile:sample-asset.txt\tprehashed TlTxfjqY85HlrHFPjOgCmhFIKfH5Jz2MoE+zJMju9iLJ150Zdidzm3ucRsk4wU3B/OEifaL6clxmJGSWZ3bcAg== -`), - ); +`; }; // Alternate signature that will match `sampleFetchAsset` export const sampleFetchOtherAssetSignature = () => { // Similar with the signature, this must be EXACT. Removing whitespace is permitted but extra whitespace is not. - return Array.from( - utf8Encode.encode(`untrusted comment: signature from rsign secret key + return `untrusted comment: signature from rsign secret key RUQWJLAztSy29pIrQ81/cjlDxoU4docg3ox7hU241qV4G4IApnBMBLuk6mkrWWk4eQPF1IdVkL78y0yLepHpXerf4aJzuWIPPg0= trusted comment: timestamp:1711757915\tfile:sample-asset.txt\tprehashed nmLg2TZIzWELS+DY87/dd/SFzhqrE6LCww5+prCeoa4aNJVKeNLAwNcj3NBF9PmRcXMWb6Adw1UL2MUvL+NzBw== -`), - ); +`; }; diff --git a/tests/src/checked/fetch/fetch.test.ts b/tests/src/checked/fetch/fetch.test.ts index 6b211bd..530bd3a 100644 --- a/tests/src/checked/fetch/fetch.test.ts +++ b/tests/src/checked/fetch/fetch.test.ts @@ -132,13 +132,15 @@ test("Signatures from multiple selection strategies", async () => { }); assert.equal(check_signatures_bob.length, 2); + assert.deepEqual(check_signatures_bob[0].author, alice.agentPubKey); assert.deepEqual(check_signatures_bob[0].reason, { Pinned: { - author: alice.agentPubKey, key_name: "test", key_collection: "bob collection", }, }); + // TODO should self signatures be returned? Probably yes but with its own reason + assert.deepEqual(check_signatures_bob[1].author, bob.agentPubKey); assert.deepEqual(check_signatures_bob[1].reason, { RandomHistorical: null, }); diff --git a/types/checked_types/src/lib.rs b/types/checked_types/src/lib.rs index 54b6562..7fa05bf 100644 --- a/types/checked_types/src/lib.rs +++ b/types/checked_types/src/lib.rs @@ -22,14 +22,13 @@ pub struct PrepareFetchRequest { pub fetch_url: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct FetchCheckSignaturePinned { - pub author: AgentPubKey, pub key_collection: String, pub key_name: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub enum FetchCheckSignatureReason { RandomRecent, RandomHistorical, @@ -38,14 +37,18 @@ pub enum FetchCheckSignatureReason { #[derive(Serialize, Deserialize, Debug)] pub struct FetchCheckSignature { - pub signature: Vec, + pub signature: String, + pub key_type: VerificationKeyType, + pub verification_key: String, + pub author: AgentPubKey, + pub key_dist_address: ActionHash, pub reason: FetchCheckSignatureReason, } #[derive(Serialize, Deserialize, Debug)] pub struct CreateAssetSignature { pub fetch_url: String, - pub signature: Vec, + pub signature: String, pub key_type: VerificationKeyType, pub verification_key: String, } @@ -56,7 +59,7 @@ pub struct AssetSignatureResponse { pub fetch_url: String, /// The signature of the asset. - pub signature: Vec, + pub signature: String, /// The address of the public key that signed this asset. pub key_dist_address: ActionHash, diff --git a/types/fetch/src/lib.rs b/types/fetch/src/lib.rs index 03761e7..a41ecbc 100644 --- a/types/fetch/src/lib.rs +++ b/types/fetch/src/lib.rs @@ -10,7 +10,7 @@ pub struct AssetSignature { /// The signature of the asset. Detached from the asset itself since we don't want to store that /// on the DHT. - pub signature: Vec, + pub signature: String, /// The address of the public key that signed this asset. ///