use notochord::{
AdmittedPrincipal, DenyReason, HandshakeError, LocalNetworkPolicy, NetworkId, ProfileRef,
ProofBinding, RequestedAction, RevocationLedger, SessionFacts, SessionHello, TrafficClass,
admit,
};
use personae::IdentityProvider;
use personae::delegation::SignedDelegationCertificate;
pub const GRAPHSHELL_DOMAIN: &str = "mere.graphshell";
pub const PROJECTION_SERVICE: &str = "/services/projection";
pub const CONNECT_ACTION: &str = "connect";
pub const PROJECTION_PROTOCOL: &[u8] = b"mere/graphshell/v1";
pub fn connect_action() -> RequestedAction {
RequestedAction {
domain: GRAPHSHELL_DOMAIN.to_string(),
path: PROJECTION_SERVICE.to_string(),
action: CONNECT_ACTION.to_string(),
}
}
pub fn open_session<P: IdentityProvider>(
identity: &P,
network: NetworkId,
profile: ProfileRef,
class: TrafficClass,
nonce: [u8; 32],
binding: &ProofBinding,
delegations: Vec<SignedDelegationCertificate>,
) -> Result<SessionHello, HandshakeError> {
SessionHello::issue(
identity,
network,
profile,
connect_action(),
class,
nonce,
binding,
delegations,
)
}
pub fn serves_action(principal: &AdmittedPrincipal) -> bool {
principal.action == connect_action()
}
pub fn admit_session(
policy: &LocalNetworkPolicy,
ledger: &RevocationLedger,
hello_bytes: &[u8],
facts: &SessionFacts,
now_ms: u64,
active_sessions: u32,
) -> (Vec<u8>, Result<AdmittedPrincipal, DenyReason>) {
let (reply, outcome) = admit(policy, ledger, hello_bytes, facts, now_ms, active_sessions);
let outcome = outcome.and_then(|principal| {
if serves_action(&principal) {
Ok(principal)
} else {
Err(DenyReason::ActionNotOffered)
}
});
(reply, outcome)
}
#[cfg(test)]
mod tests {
use super::*;
use notochord::{CarrierKind, ServiceAccess, ServiceRule, TrustedRoot};
use personae::InMemoryProvider;
use personae::delegation::{
CapabilityScope, DelegationCertificate, DelegationParent, SignedDelegationCertificate,
};
use std::collections::BTreeMap;
const NETWORK: NetworkId = NetworkId([3; 32]);
const ROOT_AUTHORITY: [u8; 32] = [7; 32];
const NOW_MS: u64 = 50;
fn owner() -> InMemoryProvider {
InMemoryProvider::from_seed([1; 32])
}
fn viewer() -> InMemoryProvider {
InMemoryProvider::from_seed([4; 32])
}
fn stranger() -> InMemoryProvider {
InMemoryProvider::from_seed([11; 32])
}
fn profile_ref() -> ProfileRef {
ProfileRef {
id: "mere.base".into(),
revision: 1,
}
}
fn projection_grant(
subject: [u8; 32],
path: &str,
domain: &str,
) -> SignedDelegationCertificate {
SignedDelegationCertificate::issue(
&owner(),
DelegationCertificate::new(
DelegationParent::Root(ROOT_AUTHORITY),
owner().master_public_key().to_bytes(),
subject,
CapabilityScope {
domain: domain.into(),
resource: NETWORK.0.to_vec(),
path_prefix: path.into(),
actions: [CONNECT_ACTION.to_string()].into_iter().collect(),
},
5,
10,
Some(100),
1,
[1; 32],
),
)
.expect("issue certificate")
}
fn policy() -> LocalNetworkPolicy {
let mut policy = LocalNetworkPolicy::closed(NETWORK);
policy.accepted_profiles = vec![profile_ref()];
policy.trusted_roots = vec![TrustedRoot {
authority: ROOT_AUTHORITY,
issuer: owner().master_public_key().to_bytes(),
}];
policy.services = BTreeMap::from([(
PROJECTION_SERVICE.to_string(),
ServiceRule::new(
ServiceAccess::MemberOnly,
GRAPHSHELL_DOMAIN,
[CONNECT_ACTION],
false,
None,
),
)]);
policy
}
fn binding() -> ProofBinding {
ProofBinding::initiator(PROJECTION_PROTOCOL, None, None)
}
fn facts() -> SessionFacts {
SessionFacts::new(PROJECTION_PROTOCOL, CarrierKind::P2panda)
}
fn hello_from<P: IdentityProvider>(
identity: &P,
binding: &ProofBinding,
delegations: Vec<SignedDelegationCertificate>,
) -> Vec<u8> {
open_session(
identity,
NETWORK,
profile_ref(),
TrafficClass::Interactive,
[5; 32],
binding,
delegations,
)
.expect("issue hello")
.encode(&LocalNetworkPolicy::closed(NETWORK).limits.clamped())
.expect("encode hello")
}
#[test]
fn a_granted_viewer_is_admitted_and_named() {
let viewer = viewer();
let subject = viewer.master_public_key().to_bytes();
let hello = hello_from(
&viewer,
&binding(),
vec![projection_grant(
subject,
PROJECTION_SERVICE,
GRAPHSHELL_DOMAIN,
)],
);
let (_, outcome) = admit_session(
&policy(),
&RevocationLedger::default(),
&hello,
&facts(),
NOW_MS,
0,
);
let principal = outcome.expect("a granted viewer opens a projection session");
assert_eq!(
principal.subject, subject,
"the admitted principal is the peer, established by the handshake"
);
assert_eq!(principal.action, connect_action());
}
#[test]
fn an_ungranted_stranger_is_refused() {
let hello = hello_from(&stranger(), &binding(), Vec::new());
let (_, outcome) = admit_session(
&policy(),
&RevocationLedger::default(),
&hello,
&facts(),
NOW_MS,
0,
);
assert!(
outcome.is_err(),
"no chain, no session: MemberOnly means a delegation is required"
);
}
#[test]
fn a_grant_for_another_service_does_not_open_projections() {
let viewer = viewer();
let subject = viewer.master_public_key().to_bytes();
let hello = hello_from(
&viewer,
&binding(),
vec![projection_grant(subject, "/services/murm", "mere.network")],
);
let (_, outcome) = admit_session(
&policy(),
&RevocationLedger::default(),
&hello,
&facts(),
NOW_MS,
0,
);
assert!(
outcome.is_err(),
"a Murm grant is not a Graphshell grant, even for the same subject"
);
}
#[test]
fn a_captured_hello_does_not_open_a_different_connection() {
let viewer = viewer();
let subject = viewer.master_public_key().to_bytes();
let hello = hello_from(
&viewer,
&binding(),
vec![projection_grant(
subject,
PROJECTION_SERVICE,
GRAPHSHELL_DOMAIN,
)],
);
let elsewhere =
SessionFacts::authenticated(PROJECTION_PROTOCOL, CarrierKind::P2panda, [42; 32]);
let (_, outcome) = admit_session(
&policy(),
&RevocationLedger::default(),
&hello,
&elsewhere,
NOW_MS,
0,
);
assert!(
outcome.is_err(),
"a proof minted for one connection is worthless on another"
);
}
}