Phase B: identifier+key on the wire, JCS envelope signing (FRX/0.5), registry binding
This commit is contained in:
+22
-13
@@ -121,20 +121,19 @@ pub fn verify_signature(public_hex: &str, message: &[u8], sig_hex: &str) -> Resu
|
||||
}
|
||||
|
||||
pub fn signing_bytes(envelope: &Envelope) -> Vec<u8> {
|
||||
format!(
|
||||
"{}\n{}\n{}\n{}\n{}\n{}",
|
||||
PROTOCOL,
|
||||
envelope.msg_type,
|
||||
envelope.from,
|
||||
envelope.ts,
|
||||
envelope.nonce,
|
||||
canonical_json(&envelope.body)
|
||||
)
|
||||
.into_bytes()
|
||||
let unsigned = serde_json::json!({
|
||||
"type": envelope.msg_type,
|
||||
"from": envelope.from,
|
||||
"key": envelope.key,
|
||||
"ts": envelope.ts,
|
||||
"nonce": envelope.nonce,
|
||||
"body": envelope.body,
|
||||
});
|
||||
format!("{}\n{}", PROTOCOL, canonical_json(&unsigned)).into_bytes()
|
||||
}
|
||||
|
||||
pub fn verify_envelope(envelope: &Envelope) -> Result<()> {
|
||||
verify_signature(&envelope.from, &signing_bytes(envelope), &envelope.sig)
|
||||
verify_signature(&envelope.key, &signing_bytes(envelope), &envelope.sig)
|
||||
}
|
||||
|
||||
pub fn now_ts() -> u64 {
|
||||
@@ -160,14 +159,24 @@ mod tests {
|
||||
#[test]
|
||||
fn envelope_sign_verify_roundtrip() {
|
||||
let key = Keypair::generate();
|
||||
let env = Envelope::new(&key, crate::message::TYPE_QUERY, json!({"text": "hello"}));
|
||||
let env = Envelope::new(
|
||||
&key,
|
||||
"alice.example",
|
||||
crate::message::TYPE_QUERY,
|
||||
json!({"text": "hello"}),
|
||||
);
|
||||
verify_envelope(&env).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tampered_body_fails_verification() {
|
||||
let key = Keypair::generate();
|
||||
let mut env = Envelope::new(&key, crate::message::TYPE_QUERY, json!({"text": "hello"}));
|
||||
let mut env = Envelope::new(
|
||||
&key,
|
||||
"alice.example",
|
||||
crate::message::TYPE_QUERY,
|
||||
json!({"text": "hello"}),
|
||||
);
|
||||
env.body = json!({"text": "hello", "extra": true});
|
||||
assert!(verify_envelope(&env).is_err());
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,4 +10,4 @@ pub mod relay;
|
||||
pub mod render;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const PROTOCOL: &str = "FRX/0.4";
|
||||
pub const PROTOCOL: &str = "FRX/0.5";
|
||||
|
||||
+5
-3
@@ -22,6 +22,7 @@ pub struct Envelope {
|
||||
#[serde(rename = "type")]
|
||||
pub msg_type: String,
|
||||
pub from: String,
|
||||
pub key: String,
|
||||
pub ts: u64,
|
||||
pub nonce: String,
|
||||
pub body: Value,
|
||||
@@ -29,10 +30,11 @@ pub struct Envelope {
|
||||
}
|
||||
|
||||
impl Envelope {
|
||||
pub fn new(key: &Keypair, msg_type: &str, body: Value) -> Self {
|
||||
pub fn new(key: &Keypair, identifier: &str, msg_type: &str, body: Value) -> Self {
|
||||
let mut envelope = Self {
|
||||
msg_type: msg_type.to_string(),
|
||||
from: key.public_hex(),
|
||||
from: identifier.to_string(),
|
||||
key: key.public_hex(),
|
||||
ts: now_ts(),
|
||||
nonce: random_nonce(),
|
||||
body,
|
||||
@@ -183,7 +185,7 @@ mod tests {
|
||||
#[test]
|
||||
fn envelope_rejects_wrong_type() {
|
||||
let key = Keypair::generate();
|
||||
let envelope = Envelope::new(&key, TYPE_RESPONSE, json!({}));
|
||||
let envelope = Envelope::new(&key, "bob.example", TYPE_RESPONSE, json!({}));
|
||||
assert!(require_type(&envelope, TYPE_QUERY).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
+48
-17
@@ -284,6 +284,14 @@ impl Node {
|
||||
self.index.doc_count()
|
||||
}
|
||||
|
||||
pub fn identifier(&self) -> String {
|
||||
self.config
|
||||
.node
|
||||
.id
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.key.public_hex())
|
||||
}
|
||||
|
||||
pub fn sent(&self) -> u64 {
|
||||
self.sent.load(Ordering::SeqCst)
|
||||
}
|
||||
@@ -359,7 +367,12 @@ impl Node {
|
||||
let mut aggregates = self.aggregates.lock().expect("aggregates lock");
|
||||
*aggregates.sent.entry(current_period()).or_default() += 1;
|
||||
}
|
||||
let envelope = Envelope::new(&self.key, TYPE_QUERY, serde_json::to_value(&query)?);
|
||||
let envelope = Envelope::new(
|
||||
&self.key,
|
||||
&self.identifier(),
|
||||
TYPE_QUERY,
|
||||
serde_json::to_value(&query)?,
|
||||
);
|
||||
self.publish(&envelope).await;
|
||||
let timeout = Duration::from_millis(timeout_ms.unwrap_or(self.config.query.timeout_ms));
|
||||
let deadline = Instant::now() + timeout;
|
||||
@@ -450,19 +463,19 @@ impl Node {
|
||||
.as_ref()
|
||||
.map(|signed| authorized_keys(signed, now_ts()));
|
||||
match authorized {
|
||||
Some(map) => {
|
||||
let entry = map.get(&envelope.from);
|
||||
(entry.map(|(_, class)| class.clone()), entry.is_some())
|
||||
}
|
||||
Some(map) => match map.get(&envelope.key) {
|
||||
Some((id, class)) if id == &envelope.from => (Some(class.clone()), true),
|
||||
_ => (None, false),
|
||||
},
|
||||
None => (None, false),
|
||||
}
|
||||
} else {
|
||||
let members = self.members.read().expect("members lock");
|
||||
let class = self.member_class(&members, &envelope.from);
|
||||
let class = self.member_class(&members, &envelope.key);
|
||||
let listed = if members.is_empty() {
|
||||
self.config.node.dev_bootstrap
|
||||
} else {
|
||||
class.is_some()
|
||||
class.is_some() && envelope.from == envelope.key
|
||||
};
|
||||
(class, listed)
|
||||
};
|
||||
@@ -471,7 +484,7 @@ impl Node {
|
||||
}
|
||||
match envelope.msg_type.as_str() {
|
||||
TYPE_QUERY => {
|
||||
if envelope.from == self.key.public_hex() || !self.config.node.responder {
|
||||
if envelope.from == self.identifier() || !self.config.node.responder {
|
||||
return;
|
||||
}
|
||||
let Ok(query) = envelope.parse_body::<QueryBody>() else {
|
||||
@@ -491,7 +504,7 @@ impl Node {
|
||||
}
|
||||
let node = self.clone();
|
||||
let relay = relay.to_string();
|
||||
let querier = envelope.from.clone();
|
||||
let querier = envelope.key.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(error) = node.respond(&query, &querier, &relay).await {
|
||||
eprintln!("responder failed: {error}");
|
||||
@@ -535,10 +548,12 @@ impl Node {
|
||||
}
|
||||
let node = self.clone();
|
||||
let period = period.to_string();
|
||||
let requester = envelope.from.clone();
|
||||
let requester_id = envelope.from.clone();
|
||||
let requester_key = envelope.key.clone();
|
||||
let relay = relay.to_string();
|
||||
tokio::spawn(async move {
|
||||
node.serve_aggregate(&period, &requester, &relay).await;
|
||||
node.serve_aggregate(&period, &requester_id, &requester_key, &relay)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@@ -552,7 +567,12 @@ impl Node {
|
||||
return Ok(());
|
||||
}
|
||||
let body = build_response(&query.qid, response_items(&hits), total, max);
|
||||
let envelope = Envelope::new(&self.key, TYPE_RESPONSE, serde_json::to_value(&body)?);
|
||||
let envelope = Envelope::new(
|
||||
&self.key,
|
||||
&self.identifier(),
|
||||
TYPE_RESPONSE,
|
||||
serde_json::to_value(&body)?,
|
||||
);
|
||||
self.send_unicast(querier, envelope, relay).await
|
||||
}
|
||||
|
||||
@@ -572,7 +592,12 @@ impl Node {
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("no relays configured"))?
|
||||
.clone();
|
||||
let envelope = Envelope::new(&self.key, TYPE_AGGREGATE, json!({ "period": period }));
|
||||
let envelope = Envelope::new(
|
||||
&self.key,
|
||||
&self.identifier(),
|
||||
TYPE_AGGREGATE,
|
||||
json!({ "period": period }),
|
||||
);
|
||||
self.send_unicast(to, envelope, &relay).await?;
|
||||
let key = (to.to_string(), period.to_string());
|
||||
let deadline = Instant::now()
|
||||
@@ -594,13 +619,19 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_aggregate(&self, period: &str, requester: &str, relay: &str) {
|
||||
let body = self.aggregate_for(period, Some(requester));
|
||||
async fn serve_aggregate(
|
||||
&self,
|
||||
period: &str,
|
||||
requester_id: &str,
|
||||
requester_key: &str,
|
||||
relay: &str,
|
||||
) {
|
||||
let body = self.aggregate_for(period, Some(requester_id));
|
||||
let Ok(value) = serde_json::to_value(&body) else {
|
||||
return;
|
||||
};
|
||||
let envelope = Envelope::new(&self.key, TYPE_AGGREGATE, value);
|
||||
if let Err(error) = self.send_unicast(requester, envelope, relay).await {
|
||||
let envelope = Envelope::new(&self.key, &self.identifier(), TYPE_AGGREGATE, value);
|
||||
if let Err(error) = self.send_unicast(requester_key, envelope, relay).await {
|
||||
eprintln!("aggregate reply failed: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ impl Relay {
|
||||
Ok(1)
|
||||
}
|
||||
None => {
|
||||
let from = envelope.from.clone();
|
||||
let from = envelope.key.clone();
|
||||
inner.members.entry(from).or_default();
|
||||
let mut delivered = 0;
|
||||
for queue in inner.members.values_mut() {
|
||||
|
||||
Reference in New Issue
Block a user