Client-side noise protocol
/ build (push) Successful in 4m32s Details

This commit is contained in:
Tony Garnock-Jones 2024-06-08 02:18:03 +02:00
parent 46f4071d4f
commit 92cc57d2cd
3 changed files with 302 additions and 127 deletions

View File

@ -38,6 +38,9 @@ pub fn start(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("sturdy_ref_step")), t.spawn(Some(AnyValue::symbol("sturdy_ref_step")),
enclose!((ds) move |t| super::sturdy::handle_sturdy_path_steps(t, ds))); enclose!((ds) move |t| super::sturdy::handle_sturdy_path_steps(t, ds)));
t.spawn(Some(AnyValue::symbol("noise_ref_step")),
enclose!((ds) move |t| super::noise::handle_noise_path_steps(t, ds)));
} }
fn run(t: &mut Activation, ds: Arc<Cap>, route: G::Route) -> ActorResult { fn run(t: &mut Activation, ds: Arc<Cap>, route: G::Route) -> ActorResult {

View File

@ -13,17 +13,21 @@ use preserves_schema::Codec;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::relay::Mutex; use syndicate::relay::Mutex;
use syndicate::relay::TunnelRelay; use syndicate::relay::TunnelRelay;
use syndicate::rpc;
use syndicate::trace::TurnCause; use syndicate::trace::TurnCause;
use syndicate::value::NestedValue; use syndicate::value::NestedValue;
use syndicate::value::NoEmbeddedDomainCodec; use syndicate::value::NoEmbeddedDomainCodec;
use syndicate::value::PackedWriter; use syndicate::value::PackedWriter;
use syndicate::enclose;
use syndicate_macros::during; use syndicate_macros::during;
use syndicate_macros::pattern; use syndicate_macros::pattern;
use syndicate::schemas::dataspace; use syndicate::schemas::dataspace;
use syndicate::schemas::gatekeeper; use syndicate::schemas::gatekeeper;
use syndicate::schemas::noise; use syndicate::schemas::noise;
use syndicate::schemas::rpc as R;
use syndicate::schemas::sturdy;
use crate::language; use crate::language;
@ -37,7 +41,7 @@ pub fn handle_noise_binds(t: &mut Activation, ds: &Arc<Cap>) -> ActorResult {
target.value().to_embedded()?; target.value().to_embedded()?;
let observer = language().parse::<gatekeeper::BindObserver>(&observer)?; let observer = language().parse::<gatekeeper::BindObserver>(&observer)?;
let spec = language().parse::<noise::NoiseDescriptionDetail<AnyValue>>(&desc)?.0; let spec = language().parse::<noise::NoiseDescriptionDetail<AnyValue>>(&desc)?.0;
match validate_noise_spec(spec) { match validate_noise_service_spec(spec) {
Ok(spec) => if let gatekeeper::BindObserver::Present(o) = observer { Ok(spec) => if let gatekeeper::BindObserver::Present(o) = observer {
o.assert(t, language(), &gatekeeper::Bound::Bound { o.assert(t, language(), &gatekeeper::Bound::Bound {
path_step: Box::new(gatekeeper::PathStep { path_step: Box::new(gatekeeper::PathStep {
@ -108,9 +112,9 @@ fn default_noise_protocol() -> String {
} }
fn validate_noise_spec( fn validate_noise_spec(
spec: noise::NoiseServiceSpec<AnyValue>, spec: noise::NoiseSpec<AnyValue>,
) -> Result<ValidatedNoiseSpec, ActorError> { ) -> Result<ValidatedNoiseSpec, ActorError> {
let protocol = match spec.base.protocol { let protocol = match spec.protocol {
noise::NoiseProtocol::Present { protocol } => protocol, noise::NoiseProtocol::Present { protocol } => protocol,
noise::NoiseProtocol::Invalid { protocol } => noise::NoiseProtocol::Invalid { protocol } =>
Err(format!("Invalid noise protocol {:?}", protocol))?, Err(format!("Invalid noise protocol {:?}", protocol))?,
@ -127,28 +131,35 @@ fn validate_noise_spec(
let pattern = lookup_pattern(pattern_name).ok_or_else::<ActorError, _>( let pattern = lookup_pattern(pattern_name).ok_or_else::<ActorError, _>(
|| format!("Unsupported handshake pattern {:?}", pattern_name).into())?; || format!("Unsupported handshake pattern {:?}", pattern_name).into())?;
let psks = match spec.base.pre_shared_keys { let psks = match spec.pre_shared_keys {
noise::NoisePreSharedKeys::Present { pre_shared_keys } => pre_shared_keys, noise::NoisePreSharedKeys::Present { pre_shared_keys } => pre_shared_keys,
noise::NoisePreSharedKeys::Invalid { pre_shared_keys } => noise::NoisePreSharedKeys::Invalid { pre_shared_keys } =>
Err(format!("Invalid pre-shared-keys {:?}", pre_shared_keys))?, Err(format!("Invalid pre-shared-keys {:?}", pre_shared_keys))?,
noise::NoisePreSharedKeys::Absent => vec![], noise::NoisePreSharedKeys::Absent => vec![],
}; };
let secret_key = match spec.secret_key { Ok(ValidatedNoiseSpec {
service: spec.service.0,
protocol,
pattern,
psks,
secret_key: None,
public_key: spec.key,
})
}
fn validate_noise_service_spec(
spec: noise::NoiseServiceSpec<AnyValue>,
) -> Result<ValidatedNoiseSpec, ActorError> {
let noise::NoiseServiceSpec { base, secret_key } = spec;
let v = validate_noise_spec(base)?;
let secret_key = match secret_key {
noise::SecretKeyField::Present { secret_key } => Some(secret_key), noise::SecretKeyField::Present { secret_key } => Some(secret_key),
noise::SecretKeyField::Invalid { secret_key } => noise::SecretKeyField::Invalid { secret_key } =>
Err(format!("Invalid secret key {:?}", secret_key))?, Err(format!("Invalid secret key {:?}", secret_key))?,
noise::SecretKeyField::Absent => None, noise::SecretKeyField::Absent => None,
}; };
Ok(ValidatedNoiseSpec { secret_key, .. v })
Ok(ValidatedNoiseSpec {
service: spec.base.service.0,
protocol,
pattern,
psks,
secret_key,
public_key: spec.base.key,
})
} }
fn await_bind_noise( fn await_bind_noise(
@ -164,9 +175,14 @@ fn await_bind_noise(
let observer = Arc::clone(&observer); let observer = Arc::clone(&observer);
t.spawn_link(None, move |t| { t.spawn_link(None, move |t| {
let bindings = a.value().to_sequence()?; let bindings = a.value().to_sequence()?;
let spec = validate_noise_spec(language().parse(&bindings[0])?)?; let spec = validate_noise_service_spec(language().parse(&bindings[0])?)?;
let service = bindings[1].value().to_embedded()?; let service = bindings[1].value().to_embedded()?.clone();
run_noise_responder(t, spec, observer, Arc::clone(service)) let hs = make_handshake(&spec, false)?;
let responder_session = Cap::guard(crate::Language::arc(), t.create(
ResponderState::Introduction{ service, hs }));
observer.assert(
t, language(), &gatekeeper::Resolved::Accepted { responder_session });
Ok(())
}); });
Ok(()) Ok(())
}) })
@ -181,23 +197,28 @@ fn await_bind_noise(
Ok(()) Ok(())
} }
type HandshakeState = noise_protocol::HandshakeState<X25519, ChaCha20Poly1305, Blake2s>; type NoiseHandshakeState = noise_protocol::HandshakeState<X25519, ChaCha20Poly1305, Blake2s>;
struct HandshakeState {
peer: Arc<Cap>,
hs: NoiseHandshakeState,
initial_ref: Option<Arc<Cap>>,
initial_oid: Option<sturdy::Oid>,
}
struct TransportState {
relay_input: Arc<Mutex<Option<TunnelRelay>>>,
c_recv: CipherState<ChaCha20Poly1305>,
}
enum ResponderState { enum ResponderState {
Invalid, // used during state transitions Invalid, // used during state transitions
Introduction { Introduction {
service: Arc<Cap>, service: Arc<Cap>,
hs: HandshakeState, hs: NoiseHandshakeState,
},
Handshake {
initiator_session: Arc<Cap>,
service: Arc<Cap>,
hs: HandshakeState,
},
Transport {
relay_input: Arc<Mutex<Option<TunnelRelay>>>,
c_recv: CipherState<ChaCha20Poly1305>,
}, },
Handshake(HandshakeState),
Transport(TransportState),
} }
impl Entity<noise::SessionItem> for ResponderState { impl Entity<noise::SessionItem> for ResponderState {
@ -208,7 +229,12 @@ impl Entity<noise::SessionItem> for ResponderState {
}; };
match std::mem::replace(self, ResponderState::Invalid) { match std::mem::replace(self, ResponderState::Invalid) {
ResponderState::Introduction { service, hs } => { ResponderState::Introduction { service, hs } => {
*self = ResponderState::Handshake { initiator_session, service, hs }; *self = ResponderState::Handshake(HandshakeState {
peer: initiator_session,
hs,
initial_ref: Some(service.clone()),
initial_oid: None,
});
Ok(()) Ok(())
} }
_ => _ =>
@ -224,24 +250,70 @@ impl Entity<noise::SessionItem> for ResponderState {
match self { match self {
ResponderState::Invalid | ResponderState::Introduction { .. } => ResponderState::Invalid | ResponderState::Introduction { .. } =>
Err("Received Packet in invalid ResponderState")?, Err("Received Packet in invalid ResponderState")?,
ResponderState::Handshake { initiator_session, service, hs } => match p { ResponderState::Handshake(hss) => {
if let Some((None, ts)) = hss.handle_packet(t, p)? {
*self = ResponderState::Transport(ts);
}
}
ResponderState::Transport(ts) => ts.handle_packet(t, p)?,
}
Ok(())
}
}
impl HandshakeState {
fn handle_packet(
&mut self,
t: &mut Activation,
p: noise::Packet,
) -> Result<Option<(Option<Arc<Cap>>, TransportState)>, ActorError> {
match p {
noise::Packet::Complete(bs) => { noise::Packet::Complete(bs) => {
if bs.len() < hs.get_next_message_overhead() { if bs.len() < self.hs.get_next_message_overhead() {
Err("Invalid handshake message for pattern")?; Err("Invalid handshake message for pattern")?;
} }
if bs.len() > hs.get_next_message_overhead() { if bs.len() > self.hs.get_next_message_overhead() {
Err("Cannot accept payload during handshake")?; Err("Cannot accept payload during handshake")?;
} }
hs.read_message(&bs, &mut [])?; self.hs.read_message(&bs, &mut [])?;
let mut reply = vec![0u8; hs.get_next_message_overhead()]; if self.hs.completed() {
hs.write_message(&[], &mut reply[..])?; self.complete_handshake(t)
initiator_session.message(t, language(), &noise::Packet::Complete(reply.into())); } else {
if hs.completed() { self.send_handshake_packet(t)
let (c_recv, mut c_send) = hs.get_ciphers(); }
let (_, relay_input, mut relay_output) = }
TunnelRelay::_run(t, Some(Arc::clone(service)), None, false); _ => Err("Fragmented handshake is not allowed")?,
}
}
fn send_handshake_packet(
&mut self,
t: &mut Activation,
) -> Result<Option<(Option<Arc<Cap>>, TransportState)>, ActorError> {
let mut reply = vec![0u8; self.hs.get_next_message_overhead()];
self.hs.write_message(&[], &mut reply[..])?;
self.peer.message(t, language(), &noise::Packet::Complete(reply.into()));
if self.hs.completed() {
self.complete_handshake(t)
} else {
Ok(None)
}
}
fn complete_handshake(
&mut self,
t: &mut Activation,
) -> Result<Option<(Option<Arc<Cap>>, TransportState)>, ActorError> {
let (c_i_to_r, c_r_to_i) = self.hs.get_ciphers();
let (c_recv, mut c_send) = if self.hs.get_is_initiator() {
(c_r_to_i, c_i_to_r)
} else {
(c_i_to_r, c_r_to_i)
};
let (peer_service, relay_input, mut relay_output) =
TunnelRelay::_run(t, self.initial_ref.clone(), self.initial_oid.clone(), false);
let trace_collector = t.trace_collector(); let trace_collector = t.trace_collector();
let initiator_session = Arc::clone(initiator_session); let peer = self.peer.clone();
let relay_output_name = Some(AnyValue::symbol("relay_output")); let relay_output_name = Some(AnyValue::symbol("relay_output"));
let transport_facet = t.facet_ref(); let transport_facet = t.facet_ref();
t.linked_task(relay_output_name.clone(), async move { t.linked_task(relay_output_name.clone(), async move {
@ -262,7 +334,7 @@ impl Entity<noise::SessionItem> for ResponderState {
noise::Packet::Complete(c_send.encrypt_vec(&loaned_item.item)) noise::Packet::Complete(c_send.encrypt_vec(&loaned_item.item))
}; };
if !transport_facet.activate(&account, Some(cause.clone()), |t| { if !transport_facet.activate(&account, Some(cause.clone()), |t| {
initiator_session.message(t, language(), &p); peer.message(t, language(), &p);
Ok(()) Ok(())
}) { }) {
break; break;
@ -272,29 +344,31 @@ impl Entity<noise::SessionItem> for ResponderState {
} }
Ok(LinkedTaskTermination::Normal) Ok(LinkedTaskTermination::Normal)
}); });
*self = ResponderState::Transport { relay_input, c_recv }; Ok(Some((peer_service, TransportState { relay_input, c_recv })))
} }
} }
_ => Err("Fragmented handshake is not allowed")?,
}, impl TransportState {
ResponderState::Transport { relay_input, c_recv } => { fn handle_packet(
&mut self,
t: &mut Activation,
p: noise::Packet,
) -> ActorResult {
let bs = match p { let bs = match p {
noise::Packet::Complete(bs) => noise::Packet::Complete(bs) =>
c_recv.decrypt_vec(&bs[..]).map_err(|_| "Cannot decrypt packet")?, self.c_recv.decrypt_vec(&bs[..]).map_err(|_| "Cannot decrypt packet")?,
noise::Packet::Fragmented(pieces) => { noise::Packet::Fragmented(pieces) => {
let mut result = Vec::with_capacity(1024); let mut result = Vec::with_capacity(1024);
for piece in pieces { for piece in pieces {
result.extend(c_recv.decrypt_vec(&piece[..]) result.extend(self.c_recv.decrypt_vec(&piece[..])
.map_err(|_| "Cannot decrypt packet fragment")?); .map_err(|_| "Cannot decrypt packet fragment")?);
} }
result result
} }
}; };
let mut g = relay_input.lock(); let mut g = self.relay_input.lock();
let tr = g.as_mut().expect("initialized"); let tr = g.as_mut().expect("initialized");
tr.handle_inbound_datagram(t, &bs[..])?; tr.handle_inbound_datagram(t, &bs[..])?;
}
}
Ok(()) Ok(())
} }
} }
@ -355,34 +429,133 @@ fn lookup_pattern(name: &str) -> Option<HandshakePattern> {
}) })
} }
fn run_noise_responder( fn make_handshake(
t: &mut Activation, spec: &ValidatedNoiseSpec,
spec: ValidatedNoiseSpec, is_initiator: bool,
observer: Arc<Cap>, ) -> Result<NoiseHandshakeState, ActorError> {
service: Arc<Cap>,
) -> ActorResult {
let hs = {
let mut builder = noise_protocol::HandshakeStateBuilder::new(); let mut builder = noise_protocol::HandshakeStateBuilder::new();
builder.set_pattern(spec.pattern); builder.set_pattern(spec.pattern.clone());
builder.set_is_initiator(false); builder.set_is_initiator(is_initiator);
let prologue = PackedWriter::encode(&mut NoEmbeddedDomainCodec, &spec.service)?; let prologue = PackedWriter::encode(&mut NoEmbeddedDomainCodec, &spec.service)?;
builder.set_prologue(&prologue); builder.set_prologue(&prologue);
match spec.secret_key { match spec.secret_key.clone() {
None => (), None => (),
Some(sk) => { Some(sk) => {
let sk: [u8; 32] = sk.try_into().map_err(|_| "Bad secret key length")?; let sk: [u8; 32] = sk.try_into().map_err(|_| "Bad secret key length")?;
builder.set_s(U8Array::from_slice(&sk)); builder.set_s(U8Array::from_slice(&sk));
}, },
} }
builder.set_rs(U8Array::from_slice(&spec.public_key));
let mut hs = builder.build_handshake_state(); let mut hs = builder.build_handshake_state();
for psk in spec.psks.into_iter() { for psk in spec.psks.iter() {
hs.push_psk(&psk); hs.push_psk(psk);
}
Ok(hs)
} }
hs
};
let responder_session = pub fn handle_noise_path_steps(t: &mut Activation, ds: Arc<Cap>) -> ActorResult {
Cap::guard(crate::Language::arc(), t.create(ResponderState::Introduction{ service, hs })); during!(t, ds, language(),
observer.assert(t, language(), &gatekeeper::Resolved::Accepted { responder_session }); <q <resolve-path-step $origin <noise $spec0>>>,
enclose!((ds) move |t: &mut Activation| {
if let Ok(spec) = language().parse::<noise::NoiseSpec>(&spec0) {
if let Some(origin) = origin.value().as_embedded().cloned() {
t.spawn_link(None, move |t| run_noise_initiator(t, ds, origin, spec));
}
}
Ok(())
}));
Ok(()) Ok(())
} }
fn run_noise_initiator(
t: &mut Activation,
ds: Arc<Cap>,
origin: Arc<Cap>,
spec: noise::NoiseSpec,
) -> ActorResult {
let q = language().unparse(&gatekeeper::ResolvePathStep {
origin: origin.clone(),
path_step: gatekeeper::PathStep {
step_type: "noise".to_string(),
detail: language().unparse(&spec),
}
});
let service = spec.service.clone();
let validated = validate_noise_spec(spec)?;
let observer = Cap::guard(&language().syndicate, t.create(
syndicate::entity(()).on_asserted_facet(
enclose!((ds, q) move |_, t, r: gatekeeper::Resolved| {
match r {
gatekeeper::Resolved::Rejected(b) => {
ds.assert(t, language(), &rpc::answer(
language(), q.clone(), R::Result::Error {
error: b.detail }));
}
gatekeeper::Resolved::Accepted { responder_session } =>
run_initiator_session(
t, ds.clone(), q.clone(), &validated, responder_session)?,
}
Ok(())
}))));
origin.assert(t, language(), &gatekeeper::Resolve {
step: gatekeeper::Step {
step_type: "noise".to_string(),
detail: language().unparse(&service),
},
observer,
});
Ok(())
}
fn run_initiator_session(
t: &mut Activation,
ds: Arc<Cap>,
question: AnyValue,
spec: &ValidatedNoiseSpec,
responder_session: Arc<Cap>,
) -> ActorResult {
let initiator_session_ref = t.create_inert();
let initiator_session = Cap::guard(crate::Language::arc(), initiator_session_ref.clone());
responder_session.assert(t, language(), &noise::Initiator { initiator_session });
let mut hss = HandshakeState {
peer: responder_session.clone(),
hs: make_handshake(spec, true)?,
initial_ref: None,
initial_oid: Some(sturdy::Oid(0.into())),
};
if !hss.hs.completed() {
if hss.send_handshake_packet(t)?.is_some() {
// TODO: this might be a valid pattern, check
panic!("Unexpected complete handshake after no messages");
}
}
initiator_session_ref.become_entity(InitiatorState::Handshake { ds, question, hss });
Ok(())
}
enum InitiatorState {
Handshake {
ds: Arc<Cap>,
question: AnyValue,
hss: HandshakeState,
},
Transport(TransportState),
}
impl Entity<noise::Packet> for InitiatorState {
fn message(&mut self, t: &mut Activation, p: noise::Packet) -> ActorResult {
match self {
InitiatorState::Handshake { hss, ds, question } => {
if let Some((Some(peer_service), ts)) = hss.handle_packet(t, p)? {
let ds = ds.clone();
let question = question.clone();
*self = InitiatorState::Transport(ts);
ds.assert(t, language(), &rpc::answer(language(), question, R::Result::Ok {
value: AnyValue::domain(peer_service) }));
}
}
InitiatorState::Transport(ts) => ts.handle_packet(t, p)?,
}
Ok(())
}
}

View File

@ -3,7 +3,6 @@ use std::sync::Arc;
use preserves_schema::Codec; use preserves_schema::Codec;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::during;
use syndicate::rpc; use syndicate::rpc;
use syndicate::value::NestedValue; use syndicate::value::NestedValue;
@ -107,7 +106,7 @@ pub fn handle_sturdy_path_steps(t: &mut Activation, ds: Arc<Cap>) -> ActorResult
enclose!((ds) move |t: &mut Activation| { enclose!((ds) move |t: &mut Activation| {
if let Some(origin) = origin.value().as_embedded().cloned() { if let Some(origin) = origin.value().as_embedded().cloned() {
let observer = Cap::guard(&language().syndicate, t.create( let observer = Cap::guard(&language().syndicate, t.create(
during::entity(()).on_asserted_facet( syndicate::entity(()).on_asserted_facet(
enclose!((origin, parameters) move |_, t, r: gatekeeper::Resolved| { enclose!((origin, parameters) move |_, t, r: gatekeeper::Resolved| {
ds.assert(t, language(), &rpc::answer( ds.assert(t, language(), &rpc::answer(
language(), language(),