diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/access/envelope.rs | 25 | ||||
| -rw-r--r-- | src/access/rules.rs | 1 | ||||
| -rw-r--r-- | src/access/screen.rs | 1 | ||||
| -rw-r--r-- | src/access/sealed.rs | 3 | ||||
| -rw-r--r-- | src/api/login.rs | 2 | ||||
| -rw-r--r-- | src/api/store.rs | 2 | ||||
| -rw-r--r-- | src/document/store.rs | 3 | ||||
| -rw-r--r-- | src/simtest/policy.rs | 6 | ||||
| -rw-r--r-- | src/transport/wire.rs | 2 |
9 files changed, 38 insertions, 7 deletions
diff --git a/src/access/envelope.rs b/src/access/envelope.rs index 1804b98..e0c8909 100644 --- a/src/access/envelope.rs +++ b/src/access/envelope.rs @@ -108,6 +108,9 @@ fn agree(pattern: &[String], path: &[String]) -> bool { /// A signed value's payload. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub(crate) struct Payload { + /// The swarm it is for: one user, with the same name and key in two swarms, signs + /// for one only. + pub(crate) swarm: String, pub(crate) path: Vec<String>, pub(crate) value: JsonValue, pub(crate) user: String, @@ -170,7 +173,7 @@ pub(crate) fn read(envelope: &str) -> Result<Payload, String> { /// Checks `envelope` as the value at `path`, replacing `previous` (the envelope there /// before, if any), against the user registry `doc` holds (as the writer saw it): for -/// that path, naming what it replaces, signed by one of its user's keys, a user who may +/// its swarm, that path, naming what it replaces, signed by one of its user's keys, a user who may /// write there. pub(crate) fn check( doc: &impl ReadDoc, @@ -179,6 +182,15 @@ pub(crate) fn check( previous: Option<&str>, ) -> Result<Signed, String> { let (bytes, payload, signature) = open(envelope)?; + let swarm = store::read_path( + doc, + &crate::document::meta::path(&[crate::document::meta::SWARM]), + ) + .ok() + .flatten(); + if swarm.as_ref().and_then(JsonValue::as_str) != Some(payload.swarm.as_str()) { + return Err(format!("signed for swarm {}, not this one", payload.swarm)); + } if payload.path != path { return Err(format!( "signed for /{}, not /{}", @@ -223,6 +235,7 @@ mod tests { let line = sshsig::PublicKey(key.verifying_key().to_bytes()).line(); let mut doc = Document::new(); change(&mut doc, |doc| { + store::write_path(doc, &path("_continuum/swarm"), &json!("s")).unwrap(); let mut alice = registry::entry("root", 1, &[(path("config"), &[Right::Write][..])]); alice["keys"] = json!({ "laptop": line }); store::write_path(doc, ®istry::user_path("alice"), &alice).unwrap(); @@ -240,6 +253,7 @@ mod tests { fn payload(user: &str, at: &str, value: JsonValue, prev: &str) -> Payload { Payload { + swarm: "s".to_owned(), path: path(at), value, user: user.to_owned(), @@ -282,6 +296,15 @@ mod tests { let tampered = first.replacen("cv1.", "cv1.e30", 1); assert!(check(&doc, &tampered, &path("config"), None).is_err()); assert!(check(&doc, "plain", &path("config"), None).is_err()); + // For another swarm. + let elsewhere = signed( + &key, + &Payload { + swarm: "t".to_owned(), + ..payload("alice", "config", json!({"mode": "fast"}), "") + }, + ); + assert!(check(&doc, &elsewhere, &path("config"), None).is_err()); } #[test] diff --git a/src/access/rules.rs b/src/access/rules.rs index a56e5ae..3eca1c9 100644 --- a/src/access/rules.rs +++ b/src/access/rules.rs @@ -888,6 +888,7 @@ mod tests { ); let sealed = |at: &str, prev: &str| { let payload = envelope::Payload { + swarm: "s".to_owned(), path: path(at), value: json!({"mode": "fast"}), user: "alice".to_owned(), diff --git a/src/access/screen.rs b/src/access/screen.rs index 4c640b6..aa16b42 100644 --- a/src/access/screen.rs +++ b/src/access/screen.rs @@ -761,6 +761,7 @@ mod tests { write(&mut theirs, "a", None, "_continuum/members/a", json!({})); let sealed = |prev: &str, value: serde_json::Value| { let payload = envelope::Payload { + swarm: "s".to_owned(), path: path("config"), value, user: "alice".to_owned(), diff --git a/src/access/sealed.rs b/src/access/sealed.rs index 7a8896f..ed5024c 100644 --- a/src/access/sealed.rs +++ b/src/access/sealed.rs @@ -347,8 +347,7 @@ mod tests { assert!(check(&doc, notes, &json!("plain")).is_err()); // Signed: holding a sealed value, or null. let signed = |value: JsonValue| { - let payload = - json!({"path": ["notes"], "value": value, "user": "alice", "at": 1, "prev": ""}); + let payload = json!({"swarm": "s", "path": ["notes"], "value": value, "user": "alice", "at": 1, "prev": ""}); format!("cv1.{}.c2ln", B64URL.encode(payload.to_string())) }; assert!(check(&doc, notes, &json!(signed(json!(sealed_for(&[key(9)]))))).is_ok()); diff --git a/src/api/login.rs b/src/api/login.rs index 31a5d7f..1faadf7 100644 --- a/src/api/login.rs +++ b/src/api/login.rs @@ -502,6 +502,7 @@ pub(crate) async fn whoami( Extension(via): Extension<Via>, token: Option<Extension<Token>>, ) -> Json<JsonValue> { + let swarm = state.swarm_id().await; let replica = state.doc.read().await; let entry = registry::user(&replica.doc, &user.0); let token = token.map(|Extension(token)| { @@ -516,6 +517,7 @@ pub(crate) async fn whoami( }) }); Json(json!({ + "swarm": swarm, "user": user.0, "via": via.0, "registry": registry::exists(&replica.doc), diff --git a/src/api/store.rs b/src/api/store.rs index 101957a..81d0046 100644 --- a/src/api/store.rs +++ b/src/api/store.rs @@ -1315,7 +1315,7 @@ mod tests { fn envelope_of(value: JsonValue) -> String { use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; - let payload = json!({"path": ["x"], "value": value, "user": "alice", "at": 1, "prev": ""}); + let payload = json!({"swarm": "s", "path": ["x"], "value": value, "user": "alice", "at": 1, "prev": ""}); format!("cv1.{}.c2ln", URL_SAFE_NO_PAD.encode(payload.to_string())) } diff --git a/src/document/store.rs b/src/document/store.rs index d323928..e04ef41 100644 --- a/src/document/store.rs +++ b/src/document/store.rs @@ -1707,8 +1707,7 @@ mod tests { fn plain_reads_open_envelopes() { use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; let sealed = |value: JsonValue| { - let payload = - json!({"path": ["a"], "value": value, "user": "alice", "at": 1, "prev": ""}); + let payload = json!({"swarm": "s", "path": ["a"], "value": value, "user": "alice", "at": 1, "prev": ""}); format!("cv1.{}.c2ln", URL_SAFE_NO_PAD.encode(payload.to_string())) }; let mut doc = crate::document::Document::new(); diff --git a/src/simtest/policy.rs b/src/simtest/policy.rs index 4873b38..ba32615 100644 --- a/src/simtest/policy.rs +++ b/src/simtest/policy.rs @@ -217,7 +217,13 @@ async fn write_signed( Ok(Some(serde_json::Value::String(text))) if text.starts_with("cv1.") => Some(text), _ => None, }; + let swarm = store::read_path(&node.doc.read().await.doc, &path("_continuum/swarm")) + .ok() + .flatten() + .and_then(|swarm| swarm.as_str().map(str::to_owned)) + .unwrap_or_default(); let payload = envelope::Payload { + swarm, path: at.clone(), value, user: user.to_owned(), diff --git a/src/transport/wire.rs b/src/transport/wire.rs index e7bec62..2ab39e5 100644 --- a/src/transport/wire.rs +++ b/src/transport/wire.rs @@ -14,7 +14,7 @@ use super::{EncodedMessage, MessageKind, ParsedMessage}; /// The wire format's version, the first byte of every payload. Bump it with every change /// to a message type or a codec (`golden_bytes` fails then). -pub(crate) const VERSION: u8 = 43; +pub(crate) const VERSION: u8 = 44; /// What the second byte of a payload says. fn tag(kind: MessageKind) -> u8 { |
