From 027f5093de581c32017ecf888d1123fc53784601 Mon Sep 17 00:00:00 2001 From: Taylor Thomas Date: Thu, 3 Feb 2022 17:40:24 -0700 Subject: [PATCH] feat(*): Enforce signatures on server This completes the change to full enforcement of signing and verification on both client and server. This PR specifically is for the server. It will no longer accept bindles that do not have a signature. All signature rules are enforced according to the verification strategy. Please note that most of the changes in this PR were to update the tests, which was a good exercise. This is a breaking behavior change that should be well-documented at the next release. Closes #106 --- src/cache/lru.rs | 11 +- src/invoice/mod.rs | 9 +- src/invoice/verification.rs | 9 +- src/provider/file/mod.rs | 61 ++--------- src/server/mod.rs | 98 +++++++----------- src/testing/mod.rs | 35 ++++++- test/data/keyring.toml | 7 ++ test/data/secret_keys.toml | 6 ++ ...49ecb271321530cc5881fcd069ca8372dcd.tar.gz | Bin 510 -> 845 bytes .../invoice.toml | 18 +++- tests/client.rs | 12 ++- tests/scaffolds/README.md | 8 ++ tests/scaffolds/incomplete/invoice.toml | 18 +++- tests/scaffolds/invalid/invoice.toml | 18 +++- tests/scaffolds/keys/keyring.toml | 7 ++ tests/scaffolds/keys/secret_keys.toml | 6 ++ tests/scaffolds/lotsa_parcels/invoice.toml | 35 ++++--- tests/scaffolds/valid_v1/invoice.toml | 18 +++- tests/scaffolds/valid_v2/invoice.toml | 27 +++-- tests/test_util.rs | 25 ++++- 20 files changed, 249 insertions(+), 179 deletions(-) create mode 100644 test/data/keyring.toml create mode 100644 test/data/secret_keys.toml create mode 100644 tests/scaffolds/keys/keyring.toml create mode 100644 tests/scaffolds/keys/secret_keys.toml diff --git a/src/cache/lru.rs b/src/cache/lru.rs index 969afbb..b9c9d65 100644 --- a/src/cache/lru.rs +++ b/src/cache/lru.rs @@ -212,10 +212,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::{ - provider::Provider, signature::KeyRing, testing, SecretKeyEntry, SignatureRole, - VerificationStrategy, - }; + use crate::{provider::Provider, testing}; use std::{convert::TryFrom, sync::Arc}; use tokio::sync::Mutex; @@ -411,13 +408,9 @@ mod test { // Make sure all the create operations pass through let provider = TestProvider::default(); let cache = LruCache::new(10, provider.clone()); - let sk = SecretKeyEntry::new("TEST", vec![SignatureRole::Proxy]); let scaffold = testing::Scaffold::load("valid_v1").await; - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); + let signed = NoopSigned(NoopVerified(scaffold.invoice.clone())); cache .create_invoice(signed) .await diff --git a/src/invoice/mod.rs b/src/invoice/mod.rs index 790053f..8581200 100644 --- a/src/invoice/mod.rs +++ b/src/invoice/mod.rs @@ -447,13 +447,6 @@ mod test { let invoice: crate::Invoice = toml::from_str(invoice).expect("a nice clean parse"); - // Base case: No signature, no keyring should pass. - assert!(invoice.signature.is_none()); - let nokeys = KeyRing::default(); - let verified = VerificationStrategy::default() - .verify(invoice, &nokeys) - .expect("If no signature, then this should verify fine"); - // Create two signing keys. let signer_name1 = "Matt Butcher "; let signer_name2 = "Not Matt Butcher "; @@ -467,7 +460,7 @@ mod test { // Add two signatures let signed = sign( - verified, + invoice, vec![ (SignatureRole::Creator, &keypair1), (SignatureRole::Proxy, &keypair2), diff --git a/src/invoice/verification.rs b/src/invoice/verification.rs index 93dbe8c..12e9581 100644 --- a/src/invoice/verification.rs +++ b/src/invoice/verification.rs @@ -3,7 +3,7 @@ use crate::invoice::Signed; use super::signature::KeyRing; use super::{Invoice, Signature, SignatureError, SignatureRole}; use ed25519_dalek::{PublicKey, Signature as EdSignature}; -use tracing::{debug, info}; +use tracing::debug; use std::borrow::{Borrow, BorrowMut}; use std::fmt::Debug; @@ -193,8 +193,11 @@ impl VerificationStrategy { // Either the Creator or an Approver must be in the keyring match inv.signature.as_ref() { None => { - info!(id = %inv.bindle.id, "No signatures on invoice"); - Ok(VerifiedInvoice(invoice)) + debug!(id = %inv.bindle.id, "No signatures on invoice"); + Err(SignatureError::Unverified( + "No signatures found on invoice. At least one signature is required" + .to_string(), + )) } Some(signatures) => { let mut known_key = false; diff --git a/src/provider/file/mod.rs b/src/provider/file/mod.rs index 96b069f..1f554a7 100644 --- a/src/provider/file/mod.rs +++ b/src/provider/file/mod.rs @@ -647,9 +647,8 @@ impl Drop for PartFile { #[cfg(test)] mod test { use super::*; - use crate::invoice::signature::{KeyRing, SecretKeyEntry, SignatureRole}; - use crate::testing; - use crate::VerificationStrategy; + use crate::verification::NoopVerified; + use crate::{testing, NoopSigned}; use tempfile::tempdir; use tokio::io::AsyncReadExt; @@ -668,17 +667,6 @@ mod test { ); } - fn mock_secret_key() -> SecretKeyEntry { - SecretKeyEntry::new( - "Bogo Key", - vec![ - SignatureRole::Host, - SignatureRole::Proxy, - SignatureRole::Creator, - ], - ) - } - #[tokio::test] async fn test_should_create_yank_invoice() { // Create a temporary directory @@ -691,11 +679,7 @@ mod test { .await; let inv_name = scaffold.invoice.canonical_name(); - let sk = mock_secret_key(); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); + let signed = NoopSigned(NoopVerified(scaffold.invoice.clone())); // Create an invoice let (_, missing) = store.create_invoice(signed).await.unwrap(); assert_eq!(1, missing.len()); @@ -732,11 +716,7 @@ mod test { ) .await; - let sk = mock_secret_key(); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); + let signed = NoopSigned(NoopVerified(scaffold.invoice.clone())); assert!(store.create_invoice(signed).await.is_err()); } @@ -751,11 +731,7 @@ mod test { ) .await; - let sk = mock_secret_key(); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); + let signed = NoopSigned(NoopVerified(scaffold.invoice.clone())); // Create the invoice so we can create a parcel store .create_invoice(signed) @@ -807,11 +783,7 @@ mod test { let scaffold = testing::Scaffold::load("valid_v1").await; - let sk = mock_secret_key(); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); + let signed = NoopSigned(NoopVerified(scaffold.invoice.clone())); // Store an invoice first and then create the parcel for it store .create_invoice(signed) @@ -858,11 +830,7 @@ mod test { ) .await; - let sk = mock_secret_key(); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); + let signed = NoopSigned(NoopVerified(scaffold.invoice.clone())); store .create_invoice(signed) .await @@ -895,18 +863,9 @@ mod test { ) .await; - let sk = mock_secret_key(); - - // We want two copies, since they will each get signed, and we don't want - // an error that they are already signed. - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed1 = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed2 = crate::invoice::sign(verified, vec![(SignatureRole::Creator, &sk)]).unwrap(); + // We want two copies to try and write at the same time + let signed1 = NoopSigned(NoopVerified(scaffold.invoice.clone())); + let signed2 = NoopSigned(NoopVerified(scaffold.invoice.clone())); let (first, second) = tokio::join!(store.create_invoice(signed1), store.create_invoice(signed2)); diff --git a/src/server/mod.rs b/src/server/mod.rs index 1728482..bd2da17 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -98,13 +98,13 @@ mod test { use crate::authn::always::AlwaysAuthenticate; use crate::authz::always::AlwaysAuthorize; - use crate::invoice::{ - signature::{KeyRing, SecretKeyEntry}, - SignatureRole, VerificationStrategy, - }; + use crate::invoice::{signature::KeyRing, VerificationStrategy}; use crate::provider::Provider; use crate::search::StrictEngine; use crate::testing::{self, MockKeyStore}; + use crate::verification::NoopVerified; + use crate::NoopSigned; + use crate::{signature::SecretKeyStorage, SignatureRole}; use rstest::rstest; use testing::Scaffold; @@ -122,6 +122,8 @@ mod test { let bindles = testing::load_all_files().await; let (store, index, ks) = provider_setup.await; + let valid_v1 = bindles.get("valid_v1").expect("Missing scaffold"); + let api = super::routes::api( store, index, @@ -129,12 +131,9 @@ mod test { AlwaysAuthorize, ks, VerificationStrategy::default(), - KeyRing::default(), + valid_v1.keyring.clone(), ); - // Now that we can't upload parcels before invoices exist, we need to create a bindle that shares some parcels - - let valid_v1 = bindles.get("valid_v1").expect("Missing scaffold"); // Create an invoice pointing to those parcels and make sure the correct response is returned let res = warp::test::request() .method("POST") @@ -183,6 +182,15 @@ mod test { let mut inv = Scaffold::from(valid_v1.to_owned()).invoice; inv.bindle.id = "another.com/bindle/1.0.0".try_into().unwrap(); + inv.signature = None; + inv.sign( + SignatureRole::Creator, + valid_v1 + .keys + .get_first_matching(&SignatureRole::Creator) + .unwrap(), + ) + .unwrap(); let inv = toml::to_vec(&inv).expect("serialization shouldn't fail"); let res = warp::test::request() @@ -291,6 +299,7 @@ mod test { T: Provider + Clone + Send + Sync + 'static, { let (store, index, ks) = provider_setup.await; + let scaffold = testing::Scaffold::load("incomplete").await; let api = super::routes::api( store.clone(), @@ -299,19 +308,12 @@ mod test { AlwaysAuthorize, ks, VerificationStrategy::default(), - KeyRing::default(), + scaffold.keyring.clone(), ); - let sk = SecretKeyEntry::new("test", vec![SignatureRole::Host]); - // Insert an invoice - let scaffold = testing::Scaffold::load("incomplete").await; - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::sign(verified, vec![(SignatureRole::Host, &sk)]).unwrap(); store - .create_invoice(signed) + .create_invoice(NoopSigned(NoopVerified(scaffold.invoice.clone()))) .await .expect("Should be able to insert invoice"); @@ -369,6 +371,9 @@ mod test { let bindles = testing::load_all_files().await; let (store, index, ks) = provider_setup.await; + let valid_raw = bindles.get("valid_v1").expect("Missing scaffold"); + let valid = testing::Scaffold::from(valid_raw.clone()); + let api = super::routes::api( store.clone(), index, @@ -376,17 +381,11 @@ mod test { AlwaysAuthorize, ks, VerificationStrategy::default(), - KeyRing::default(), + valid.keyring.clone(), ); - let valid_raw = bindles.get("valid_v1").expect("Missing scaffold"); - let valid = testing::Scaffold::from(valid_raw.clone()); - let sk = SecretKeyEntry::new("test", vec![SignatureRole::Host]); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(valid.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::sign(verified, vec![(SignatureRole::Host, &sk)]).unwrap(); + store - .create_invoice(signed) + .create_invoice(NoopSigned(NoopVerified(valid.invoice.clone()))) .await .expect("Invoice create failure"); @@ -418,6 +417,7 @@ mod test { T: Provider + Clone + Send + Sync + 'static, { let (store, index, keystore) = provider_setup.await; + let scaffold = testing::Scaffold::load("valid_v1").await; let api = super::routes::api( store.clone(), @@ -426,19 +426,13 @@ mod test { AlwaysAuthorize, keystore.clone(), VerificationStrategy::default(), - KeyRing::default(), + scaffold.keyring.clone(), ); // Insert a parcel - let scaffold = testing::Scaffold::load("valid_v1").await; let parcel = scaffold.parcel_files.get("parcel").expect("Missing parcel"); let data = std::io::Cursor::new(parcel.data.clone()); - let sk = SecretKeyEntry::new("test", vec![SignatureRole::Host]); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::sign(verified, vec![(SignatureRole::Host, &sk)]).unwrap(); store - .create_invoice(signed) + .create_invoice(NoopSigned(NoopVerified(scaffold.invoice.clone()))) .await .expect("Unable to insert invoice into store"); store @@ -469,15 +463,9 @@ mod test { let scaffold = testing::Scaffold::load("invalid").await; - let sk = SecretKeyEntry::new("test", vec![SignatureRole::Host]); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::sign(verified, vec![(SignatureRole::Host, &sk)]).unwrap(); - // Create invoice first store - .create_invoice(signed) + .create_invoice(NoopSigned(NoopVerified(scaffold.invoice.clone()))) .await .expect("Unable to create invoice"); @@ -528,16 +516,10 @@ mod test { ); let bindles_to_insert = vec!["incomplete", "valid_v1", "valid_v2"]; - let sk = SecretKeyEntry::new("test", vec![SignatureRole::Host]); - for b in bindles_to_insert.into_iter() { let current = testing::Scaffold::load(b).await; - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(current.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::sign(verified, vec![(SignatureRole::Host, &sk)]).unwrap(); store - .create_invoice(signed) + .create_invoice(NoopSigned(NoopVerified(current.invoice.clone()))) .await .expect("Unable to create invoice"); } @@ -630,6 +612,7 @@ mod test { T: Provider + Clone + Send + Sync + 'static, { let (store, index, ks) = provider_setup.await; + let scaffold = testing::Scaffold::load("lotsa_parcels").await; let api = super::routes::api( store.clone(), @@ -638,17 +621,11 @@ mod test { AlwaysAuthorize, ks, VerificationStrategy::default(), - KeyRing::default(), + scaffold.keyring.clone(), ); - let scaffold = testing::Scaffold::load("lotsa_parcels").await; - let sk = SecretKeyEntry::new("test", vec![SignatureRole::Host]); - let verified = VerificationStrategy::MultipleAttestation(vec![]) - .verify(scaffold.invoice.clone(), &KeyRing::default()) - .unwrap(); - let signed = crate::sign(verified, vec![(SignatureRole::Host, &sk)]).unwrap(); store - .create_invoice(signed) + .create_invoice(NoopSigned(NoopVerified(scaffold.invoice.clone()))) .await .expect("Unable to load in invoice"); let parcel = scaffold @@ -710,6 +687,7 @@ mod test { T: Provider + Clone + Send + Sync + 'static, { let (store, index, ks) = provider_setup.await; + let scaffold = testing::RawScaffold::load("valid_v1").await; let api = super::routes::api( store, @@ -718,10 +696,9 @@ mod test { AlwaysAuthorize, ks, VerificationStrategy::default(), - KeyRing::default(), + scaffold.keyring.clone(), ); - let scaffold = testing::RawScaffold::load("valid_v1").await; // Create a valid invoice and make sure the returned invoice is signed let res = warp::test::request() .method("POST") @@ -761,6 +738,7 @@ mod test { T: Provider + Clone + Send + Sync + 'static, { let (store, index, ks) = provider_setup.await; + let scaffold = testing::RawScaffold::load("valid_v1").await; let api = super::routes::api( store, @@ -771,11 +749,9 @@ mod test { crate::authz::anonymous_get::AnonymousGet, ks, VerificationStrategy::default(), - KeyRing::default(), + scaffold.keyring.clone(), ); - let scaffold = testing::RawScaffold::load("valid_v1").await; - // Creating the invoice without a token should fail let res = warp::test::request() .method("POST") diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 3073c65..6dfe1b2 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -11,10 +11,13 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use crate::invoice::signature::{SecretKeyEntry, SecretKeyStorage, SignatureRole}; +use crate::invoice::signature::{ + KeyRing, SecretKeyEntry, SecretKeyFile, SecretKeyStorage, SignatureRole, +}; use crate::provider::embedded::EmbeddedProvider; use crate::provider::file::FileProvider; use crate::search::StrictEngine; +use crate::signature::KeyRingLoader; use sha2::{Digest, Sha256}; use tempfile::tempdir; @@ -23,6 +26,9 @@ const SCAFFOLD_DIR: &str = "tests/scaffolds"; const INVOICE_FILE: &str = "invoice.toml"; const PARCEL_DIR: &str = "parcels"; const PARCEL_EXTENSION: &str = "dat"; +const KEYRING_FILE: &str = "keyring.toml"; +const SECRETS_FILE: &str = "secret_keys.toml"; +const KEYS_DIR: &str = "keys"; /// The environment variable name used for setting the scaffolds directory pub const SCAFFOLD_DIR_ENV: &str = "BINDLE_SCAFFOLD_DIR"; @@ -53,6 +59,8 @@ pub struct ParcelInfo { pub struct RawScaffold { pub invoice: Vec, pub parcel_files: HashMap, + pub keys: SecretKeyFile, + pub keyring: KeyRing, } impl RawScaffold { @@ -69,12 +77,29 @@ impl RawScaffold { .await .expect("unable to read invoice file"); + let keys_dir = scaffold_dir().join(KEYS_DIR); + + tokio::fs::metadata(&keys_dir) + .await + .expect("Unable to find keys directory"); + + let keys = SecretKeyFile::load_file(keys_dir.join(SECRETS_FILE)) + .await + .expect("Unable to load secret keys file"); + let keyring = keys_dir + .join(KEYRING_FILE) + .load() + .await + .expect("Unable to load keyring file"); + let files = match filter_files(&dir).await { Some(s) => s, None => { return RawScaffold { invoice, parcel_files: HashMap::new(), + keys, + keyring, } } }; @@ -106,6 +131,8 @@ impl RawScaffold { .await .into_iter() .collect(), + keys, + keyring, } } } @@ -118,6 +145,8 @@ impl From for RawScaffold { RawScaffold { invoice, parcel_files: s.parcel_files, + keys: s.keys, + keyring: s.keyring, } } } @@ -128,6 +157,8 @@ impl From for RawScaffold { pub struct Scaffold { pub invoice: crate::Invoice, pub parcel_files: HashMap, + pub keys: SecretKeyFile, + pub keyring: KeyRing, } impl Scaffold { @@ -148,6 +179,8 @@ impl From for Scaffold { Scaffold { invoice, parcel_files: raw.parcel_files, + keys: raw.keys, + keyring: raw.keyring, } } } diff --git a/test/data/keyring.toml b/test/data/keyring.toml new file mode 100644 index 0000000..ae26a29 --- /dev/null +++ b/test/data/keyring.toml @@ -0,0 +1,7 @@ +version = "1.0" + +[[key]] +label = "Starfleet Command " +roles = ["creator"] +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +labelSignature = "zDVdD8J/RiW9pCWyEzEhgSehjnRIQk9kYbn+O79UwEaxFPQ/TP4HlxtojS0idmVbpziqeb9I5Y8kozuA8kbhAQ==" diff --git a/test/data/secret_keys.toml b/test/data/secret_keys.toml new file mode 100644 index 0000000..da6d1cb --- /dev/null +++ b/test/data/secret_keys.toml @@ -0,0 +1,6 @@ +version = "1.0" + +[[key]] +label = "Starfleet Command " +keypair = "Ii3Ou5nAZWaLaOsY9duTsVuTpOqvbrcd837ELsxPWhspyzVNIUOQ350QPRjo2nR88Gqa9TPQQBQsiVSyeVTWzA==" +roles = ["creator"] diff --git a/test/data/standalone/1927aefa8fdc8327499e918300e2e49ecb271321530cc5881fcd069ca8372dcd.tar.gz b/test/data/standalone/1927aefa8fdc8327499e918300e2e49ecb271321530cc5881fcd069ca8372dcd.tar.gz index 01c7e773449868cd527228ee7242e323f02839ac..f04833a7596032782d2876096bcb26a62e694b60 100644 GIT binary patch literal 845 zcmV-T1G4-diwFP!00000|J2XR3B)iAMNyZM4o2TdD#s(+1jN)UrLwe*gleItyU?Dv+!$1qIDO zmBr?r<|7v^7x=zE^|fu~tYgU0oA?0$0RR8wRNrsfFc99e|B5KDmB2|LKcq5^71$~o z)(+?o5UL#K5-c1$wGD;-^>bKRAErJ{+Dl(7pYP81-S?gC{j2^=>?EVmGP2_D)W9Cb zVW;z+{+<_kZ~6yeqrY#>PydC)53Ya4M9$$Blx9*mJ;(K0xP@JWmcKnKBuE8~1B3xp zE2g1EWz~Krs-{wbi*|n{=b))#wRt_gv!PBCYnQ5)!rr7^M7Cxvm9_y(_Zvu+Gv|sp zBdJPo&Z`5JlJA(7>dUsMn~I;^l|@B_kcKoj>lFe~G6A5NC?C0`)IbGj3fIBIy49iOhXJb@$GDqIP2gr=p1eQ-oaMs-eh4QuF0Q{mRlE5M9`@%~mwc`TpL_b*CMn5}u&4rJq_T1w zYkFZPNc`B3L-Y#(0RR8&m9Yr`FbD**Qu0CubAM2=7eNpUZ-KA=?kp21``?en(MGcW zk3Mt<=Ko9B{&ye%00960)Rj#N01yyH=R`BHVCw*L38f^(#{H9pVr?kz2;WZ+3CVFS zkyFWVhmpth{~FOOUn{e1Va&u@4Ga&E>i<)u{!gbO#o!xs00030|Lm8s4FCWL!d~$S z4>HjQ{C^Nv7dIyQ0tpnshLQi{|N8)L|2qal{!bvRD-BLQ00030|Lnj40RR91f}sAd XZip}dN)7-3|NjF3*(~i{02TlMJ>Zd5 literal 510 zcmVA$D{(9V;sh6t2o6=Xa_mWKDX}BR zEn9v)PCqy-bQe@nSdn+gNb&OAq(VWf>elF*QNg7INos~MP~Z}DLj_@!lniTK z)-`Fh!IjjiW`Y`R7I)7BV=T%N9b$EinDS#h0TE6p7cAvr2vaIBxkKeIXkU%(7*$9| z@oiN5&WB69L~Gk8bWZp99$yH^wf+w(Xz0eL-ofwt&l#h$|F^)^{#&>6Rznee-(AR& z>QQli+5d`=NB) z5en`5#TOL@?E~c5?uX548#g|T>CZBM4?Y-+J}C6ghc%#A{a$Qm(pe4nGsJh{}Vq7+|Hiij<>rx0q1e3x* znV%GUFjjrs4~Ggxc!-OkJ0w3YpKF&c+ohALaIZIZDB?prGe-M$V#w?-`F|eY*A}EO zDqhO*T>s<8{m)7wsu$k>*ACV{aMhndp#TP@{<=}V88c?gnDKAm8wgU39sm#k0NKs= AH2?qr diff --git a/test/data/standalone/1927aefa8fdc8327499e918300e2e49ecb271321530cc5881fcd069ca8372dcd/invoice.toml b/test/data/standalone/1927aefa8fdc8327499e918300e2e49ecb271321530cc5881fcd069ca8372dcd/invoice.toml index d259ea9..0b5efef 100644 --- a/test/data/standalone/1927aefa8fdc8327499e918300e2e49ecb271321530cc5881fcd069ca8372dcd/invoice.toml +++ b/test/data/standalone/1927aefa8fdc8327499e918300e2e49ecb271321530cc5881fcd069ca8372dcd/invoice.toml @@ -3,14 +3,22 @@ bindleVersion = "1.0.0" [bindle] name = "enterprise.com/warpcore" version = "1.0.0" -authors = ["Geordi La Forge "] description = "Warp core components" +authors = ["Geordi La Forge "] [annotations] engineering_location = "main" [[parcel]] -label.sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" -label.mediaType = "text/plain" -label.name = "isolinear_chip.txt" -label.size = 9 +[parcel.label] +sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" +mediaType = "text/plain" +name = "isolinear_chip.txt" +size = 9 + +[[signature]] +by = "Starfleet Command " +signature = "1oKW+2RXsE/lSXy3y+0MKKZDhnPModgTxfKuMSMMnVEZlgENIE9qOMTUrQga0QxSiAZiFH15fYkcveC8up8FDA==" +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +role = "creator" +at = 1643928286 diff --git a/tests/client.rs b/tests/client.rs index d07c6ac..fa5be9a 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -6,7 +6,7 @@ use test_util::TestController; use std::convert::TryInto; -use bindle::testing; +use bindle::{signature::SecretKeyStorage, testing, SignatureRole}; use tokio_stream::StreamExt; @@ -164,6 +164,16 @@ async fn test_already_created() { // Make sure we can create an invoice where all parcels already exist let mut other_inv = scaffold.invoice.clone(); other_inv.bindle.id = "another.com/bindle/1.0.0".try_into().unwrap(); + other_inv.signature = None; + other_inv + .sign( + SignatureRole::Creator, + scaffold + .keys + .get_first_matching(&SignatureRole::Creator) + .unwrap(), + ) + .unwrap(); controller .client .create_invoice(other_inv) diff --git a/tests/scaffolds/README.md b/tests/scaffolds/README.md index 07a8712..0cf51d7 100644 --- a/tests/scaffolds/README.md +++ b/tests/scaffolds/README.md @@ -16,3 +16,11 @@ The `invoice.toml` file should contain the invoice, and the parcels directory sh the parcels you want to create that are connected to that invoice. Each parcel should have an opaque `.dat` file that contains the actual data to be uploaded for the parcel. If the `parcels` directory is non-existent, it will assume there are no parcels to upload. + +## Key management + +As Bindle requires signing and keyrings, you will need to sign your scaffolding invoices. A +`KeyRing` will be automatically loaded if the `keys` directory exists in the `scaffolds` directory +and that directory contains a `keyring.toml` file. Likewise, secret keys for signing will be loaded +automatically if the `keys` directory exists in the `scaffolds` directory and that directory +contains a `secret_keys.toml` file diff --git a/tests/scaffolds/incomplete/invoice.toml b/tests/scaffolds/incomplete/invoice.toml index 42d4a5e..2e06ca7 100644 --- a/tests/scaffolds/incomplete/invoice.toml +++ b/tests/scaffolds/incomplete/invoice.toml @@ -3,11 +3,19 @@ bindleVersion = "1.0.0" [bindle] name = "enterprise.com/bridge" version = "1.0.0" -authors = ["Jean-Luc Picard "] description = "Command deck" +authors = ["Jean-Luc Picard "] [[parcel]] -label.sha256 = "e1706ab0a39ac88094b6d54a3f5cdba41fe5a901" -label.mediaType = "text/plain" -label.name = "make_it_so.txt" -label.size = 11 +[parcel.label] +sha256 = "e1706ab0a39ac88094b6d54a3f5cdba41fe5a901" +mediaType = "text/plain" +name = "make_it_so.txt" +size = 11 + +[[signature]] +by = "Starfleet Command " +signature = "uDSLYZS/7Tbg5yb5WTj7m3Wm/QS/U7U4ncfg0u+bUaA2zViRHJskCjMyceQ4fpk43bGCCwQDKSIXLN5cVRLqDw==" +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +role = "creator" +at = 1643927146 diff --git a/tests/scaffolds/invalid/invoice.toml b/tests/scaffolds/invalid/invoice.toml index d28dff2..8a0200f 100644 --- a/tests/scaffolds/invalid/invoice.toml +++ b/tests/scaffolds/invalid/invoice.toml @@ -3,14 +3,22 @@ bindleVersion = "1.0.0" [bindle] name = "enterprise.com/holodeck" version = "1.0.0" -authors = ["Enterprise Engineering "] description = "A diversionary room for those long voyages" +authors = ["Enterprise Engineering "] [annotations] broken = "yes" [[parcel]] -label.sha256 = "e1706ab0a39ac88094b6d54a3f5cdba41fe5a901" -label.mediaType = "text/plain" -label.name = "moriarty.txt" -label.size = 12345 +[parcel.label] +sha256 = "e1706ab0a39ac88094b6d54a3f5cdba41fe5a901" +mediaType = "text/plain" +name = "moriarty.txt" +size = 12345 + +[[signature]] +by = "Starfleet Command " +signature = "AS+BOQ4o1Dd9wxwX8hFfPsosplGvj7DpLq2lP0H1as1gcqmUpEm1/lD0ItOhcD/S+vPKUPes917+D0YpIsOpDQ==" +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +role = "creator" +at = 1643927146 diff --git a/tests/scaffolds/keys/keyring.toml b/tests/scaffolds/keys/keyring.toml new file mode 100644 index 0000000..ae26a29 --- /dev/null +++ b/tests/scaffolds/keys/keyring.toml @@ -0,0 +1,7 @@ +version = "1.0" + +[[key]] +label = "Starfleet Command " +roles = ["creator"] +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +labelSignature = "zDVdD8J/RiW9pCWyEzEhgSehjnRIQk9kYbn+O79UwEaxFPQ/TP4HlxtojS0idmVbpziqeb9I5Y8kozuA8kbhAQ==" diff --git a/tests/scaffolds/keys/secret_keys.toml b/tests/scaffolds/keys/secret_keys.toml new file mode 100644 index 0000000..da6d1cb --- /dev/null +++ b/tests/scaffolds/keys/secret_keys.toml @@ -0,0 +1,6 @@ +version = "1.0" + +[[key]] +label = "Starfleet Command " +keypair = "Ii3Ou5nAZWaLaOsY9duTsVuTpOqvbrcd837ELsxPWhspyzVNIUOQ350QPRjo2nR88Gqa9TPQQBQsiVSyeVTWzA==" +roles = ["creator"] diff --git a/tests/scaffolds/lotsa_parcels/invoice.toml b/tests/scaffolds/lotsa_parcels/invoice.toml index 4f4b979..bb3bcf5 100644 --- a/tests/scaffolds/lotsa_parcels/invoice.toml +++ b/tests/scaffolds/lotsa_parcels/invoice.toml @@ -3,27 +3,36 @@ bindleVersion = "1.0.0" [bindle] name = "enterprise.com/cargobay" version = "1.0.0" -authors = ["Miles O'Brien "] description = "The cargo bay manifest" +authors = ["Miles O'Brien "] [annotations] engineering_location = "main" [[parcel]] -label.sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" -label.mediaType = "text/plain" -label.name = "isolinear_chip.txt" -label.size = 9 +[parcel.label] +sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" +mediaType = "text/plain" +name = "isolinear_chip.txt" +size = 9 [[parcel]] -label.sha256 = "51534027079925942fdea13d4d088c7126f3e456364525b67d6ca0858d6587bc" -label.mediaType = "text/plain" -label.name = "barrel.txt" -label.size = 15 +[parcel.label] +sha256 = "51534027079925942fdea13d4d088c7126f3e456364525b67d6ca0858d6587bc" +mediaType = "text/plain" +name = "barrel.txt" +size = 15 [[parcel]] -label.sha256 = "a6e41416c2bee47e9b97900ba57de696cccc1920e331f5a0d490726a7938d8c6" -label.mediaType = "text/plain" -label.name = "crate.txt" -label.size = 14 +[parcel.label] +sha256 = "a6e41416c2bee47e9b97900ba57de696cccc1920e331f5a0d490726a7938d8c6" +mediaType = "text/plain" +name = "crate.txt" +size = 14 +[[signature]] +by = "Starfleet Command " +signature = "Nw+pLr5BHaGW5INKYYSr2q8lx6KQ91H8coFPvSZZrtI9OcmiXFiRSIfqhCidkFi36NrR4BHAROXfpyONkX4TBA==" +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +role = "creator" +at = 1643927146 diff --git a/tests/scaffolds/valid_v1/invoice.toml b/tests/scaffolds/valid_v1/invoice.toml index d259ea9..8ed39f7 100644 --- a/tests/scaffolds/valid_v1/invoice.toml +++ b/tests/scaffolds/valid_v1/invoice.toml @@ -3,14 +3,22 @@ bindleVersion = "1.0.0" [bindle] name = "enterprise.com/warpcore" version = "1.0.0" -authors = ["Geordi La Forge "] description = "Warp core components" +authors = ["Geordi La Forge "] [annotations] engineering_location = "main" [[parcel]] -label.sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" -label.mediaType = "text/plain" -label.name = "isolinear_chip.txt" -label.size = 9 +[parcel.label] +sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" +mediaType = "text/plain" +name = "isolinear_chip.txt" +size = 9 + +[[signature]] +by = "Starfleet Command " +signature = "1oKW+2RXsE/lSXy3y+0MKKZDhnPModgTxfKuMSMMnVEZlgENIE9qOMTUrQga0QxSiAZiFH15fYkcveC8up8FDA==" +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +role = "creator" +at = 1643927146 diff --git a/tests/scaffolds/valid_v2/invoice.toml b/tests/scaffolds/valid_v2/invoice.toml index aea4b96..bd24c8a 100644 --- a/tests/scaffolds/valid_v2/invoice.toml +++ b/tests/scaffolds/valid_v2/invoice.toml @@ -3,20 +3,29 @@ bindleVersion = "1.0.0" [bindle] name = "enterprise.com/warpcore" version = "2.0.0" -authors = ["Geordi La Forge "] description = "Warp core components" +authors = ["Geordi La Forge "] [annotations] engineering_location = "main" [[parcel]] -label.sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" -label.mediaType = "text/plain" -label.name = "isolinear_chip.txt" -label.size = 9 +[parcel.label] +sha256 = "23f310b54076878fd4c36f0c60ec92011a8b406349b98dd37d08577d17397de5" +mediaType = "text/plain" +name = "isolinear_chip.txt" +size = 9 [[parcel]] -label.sha256 = "5e244f1c791ee45ec9cc02b281e3b58524e757561e9dc39cdd7ec0f4d328a3d3" -label.mediaType = "text/plain" -label.name = "isolinear_chip_v2.txt" -label.size = 11 +[parcel.label] +sha256 = "5e244f1c791ee45ec9cc02b281e3b58524e757561e9dc39cdd7ec0f4d328a3d3" +mediaType = "text/plain" +name = "isolinear_chip_v2.txt" +size = 11 + +[[signature]] +by = "Starfleet Command " +signature = "ux0eZdwmiKxpP/zZEcabbOAVMbMg8AkR7AEjjpdbGGWcrRF2YwYSKURGYIIiHhGduo1dh5KL/5RK9KEVFSDeDg==" +key = "Kcs1TSFDkN+dED0Y6Np0fPBqmvUz0EAULIlUsnlU1sw=" +role = "creator" +at = 1643927146 diff --git a/tests/test_util.rs b/tests/test_util.rs index 59403e3..39e043e 100644 --- a/tests/test_util.rs +++ b/tests/test_util.rs @@ -3,7 +3,9 @@ use std::path::PathBuf; use std::sync::Arc; use bindle::client::{tokens::NoToken, Client}; -use bindle::signature::{KeyRing, KeyRingSaver, SecretKeyEntry, SecretKeyFile, SignatureRole}; +use bindle::signature::{ + KeyRing, KeyRingLoader, KeyRingSaver, SecretKeyEntry, SecretKeyFile, SignatureRole, +}; #[allow(dead_code)] pub const ENV_BINDLE_URL: &str = "BINDLE_URL"; @@ -14,6 +16,9 @@ pub const BINARY_NAME: &str = "bindle-server"; #[cfg(target_family = "windows")] pub const BINARY_NAME: &str = "bindle-server.exe"; +const SECRET_KEY_FILE: &str = "secret_keys.toml"; +const KEYRING_FILE: &str = "keyring.toml"; + pub struct TestController { pub client: Client, pub base_url: String, @@ -49,17 +54,29 @@ impl TestController { let address = format!("127.0.0.1:{}", get_random_port()); let base_url = format!("http://{}/v1/", address); + let test_data = PathBuf::from( + std::env::var("CARGO_MANIFEST_DIR").expect("Unable to get project directory"), + ) + .join("test/data"); + // Load the base keyring and secret key file + let mut secret_file = SecretKeyFile::load_file(test_data.join(SECRET_KEY_FILE)) + .await + .expect("Unable to load secret file"); + let mut keyring = test_data + .join(KEYRING_FILE) + .load() + .await + .expect("Unable to load keyring file"); + // Create the host key let secret_file_path = tempdir.path().join("secret_keys.toml"); let key = SecretKeyEntry::new("test ", vec![SignatureRole::Host]); - let mut secret_file = SecretKeyFile::default(); secret_file.key.push(key.clone()); secret_file .save_file(&secret_file_path) .await .expect("Unable to save host key"); - let mut keyring = KeyRing::default(); keyring.add_entry(key.try_into().unwrap()); let keyring_path = tempdir.path().join("keyring.toml"); @@ -79,6 +96,8 @@ impl TestController { tempdir.path().to_string_lossy().to_string().as_str(), "-i", address.as_str(), + "--keyring", + keyring_path.to_string_lossy().to_string().as_str(), ]) .env("BINDLE_SIGNING_KEYS", secret_file_path) .spawn()