linkspace/commons/
handshake.rs

1// Copyright Anton Sol
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6/// basic response-reply proving each side can sign with a key
7use std::time::Duration;
8
9use anyhow::ensure;
10use linkspace_core::prelude::*;
11
12use super::lkc_our_group;
13pub const HANDSHAKE_D: Domain = abf(b"handshake");
14pub static ANONYMOUSE_PATH: &LkPath = lkpath(&[b"anonymous"]).as_path();
15pub static ID_SENTINAL_PATH: &LkPath = lkpath(&[b"sentinal"]).as_path();
16
17fn valid_stamp_range(stamp: Stamp, max_diff_sec: usize) -> anyhow::Result<()> {
18    let dur = Duration::from_secs(max_diff_sec as u64);
19    match stamp_age(stamp) {
20        Ok(d) => ensure!(
21            d < dur,
22            "check your clocks - packet is too old  {d:?} >= {dur:?}"
23        ),
24        Err(d) => ensure!(
25            d < dur,
26            "check your clocks - packet is too new {d:?} >= {dur:?}"
27        ),
28    };
29    Ok(())
30}
31
32pub fn lkc_handshake_p0(id: &LkIdentity) -> PointParts {
33    tracing::trace!("Build phase0");
34    let now = now();
35    let mut kpr = try_keypoint_ref(
36        id.pubkey(),
37        HANDSHAKE_D,
38        ID_SENTINAL_PATH,
39        &[],
40        &[],
41        now,
42        id,
43    )
44    .unwrap();
45    kpr.update_xheader(&XFlags::DISPOSABLE);
46    kpr
47}
48pub fn lkc_handshake_p1<A>(
49    theirs: impl Point,
50    id: &LkIdentity,
51    max_diff_sec: usize,
52    into: impl FnOnce(PointParts) -> A,
53) -> anyhow::Result<A> {
54    tracing::trace!("Build Phase1");
55    assert!(
56        theirs.xheader().xhop.get() > 0,
57        "the rx channel is not calling net_header.hop() {:?}",
58        theirs.xheader()
59    );
60    let their_key = match theirs.pubkey() {
61        Some(their_key) => *their_key,
62        None => anyhow::bail!("not signed"),
63    };
64    valid_stamp_range(*theirs.get_stamp(), max_diff_sec)?;
65    let our_group = super::lkc_our_group(their_key, id.pubkey());
66    ensure!(
67        our_group != PRIVATE,
68        "Connecting to yourself (using the same key) is currently not supported"
69    );
70    let links = [Link::new("auth", *theirs.hash())];
71    let mut kpr = try_keypoint_ref(
72        our_group,
73        HANDSHAKE_D,
74        ID_SENTINAL_PATH,
75        &links,
76        &[],
77        now(),
78        id,
79    )?;
80    kpr.update_xheader(&XFlags::DISPOSABLE);
81    Ok(into(kpr))
82}
83pub fn lkc_handshake_p2<A>(
84    my_phase0: impl Point,
85    server_reply: impl Point,
86    id: &LkIdentity,
87    max_diff_sec: usize,
88    into: impl FnOnce(PointParts) -> A,
89) -> anyhow::Result<(A, PubKey)> {
90    tracing::trace!("Build Phase2");
91    ensure!(
92        my_phase0.pubkey() == Some(&id.pubkey()),
93        "identity mismatch"
94    );
95    let mine_hash = my_phase0.hash();
96    let theirs = &server_reply;
97    assert!(
98        theirs.xheader().xhop.get() > 0,
99        "the rx channel is not calling net_header.hop() {:?}",
100        theirs.xheader()
101    );
102    let their_key = match theirs.pubkey() {
103        Some(p) => *p,
104        None => anyhow::bail!("hello point not signed"),
105    };
106    valid_stamp_range(*theirs.get_stamp(), max_diff_sec)?;
107    ensure!(
108        theirs.get_links().iter().any(|r| r.ptr == mine_hash),
109        "did not validate my hash {}",
110        mine_hash
111    );
112    ensure!(
113        theirs.domain() == Some(&HANDSHAKE_D),
114        "not in the session domain"
115    );
116    let our_group = super::lkc_our_group(id.pubkey(), their_key);
117    ensure!(
118        theirs.group() == Some(&our_group),
119        "not in the right group "
120    );
121    ensure!(theirs.get_path() == ID_SENTINAL_PATH, "wrong path");
122    let links = [Link::new("signed", *theirs.hash())];
123    let mut kpr = try_keypoint_ref(
124        our_group,
125        HANDSHAKE_D,
126        ID_SENTINAL_PATH,
127        &links,
128        &[],
129        now(),
130        id,
131    )?;
132    kpr.update_xheader(&XFlags::DISPOSABLE);
133    Ok((into(kpr), their_key))
134}
135pub fn lkc_handshake_p3(
136    their_init: impl Point,
137    my_phase1: impl Point,
138    theirs: impl Point,
139    id: &LkIdentity,
140) -> anyhow::Result<PubKey> {
141    ensure!(
142        my_phase1.pubkey() == Some(&id.pubkey()),
143        "your identity mismatch"
144    );
145    let theirs = theirs.as_box();
146    let mine_hash = my_phase1.hash();
147    let their_key = match theirs.pubkey() {
148        Some(p) => {
149            ensure!(theirs.pubkey() == their_init.pubkey(), "switched keys");
150            *p
151        }
152        None => anyhow::bail!("hello point not signed"),
153    };
154    let our_group = lkc_our_group(id.pubkey(), their_key);
155    ensure!(
156        theirs.get_links().iter().any(|r| r.ptr == mine_hash),
157        "did not validate my hash {}",
158        mine_hash
159    );
160    ensure!(theirs.get_path() == ID_SENTINAL_PATH, "wrong path");
161    ensure!(
162        theirs.domain() == Some(&HANDSHAKE_D),
163        "not in the session domain"
164    );
165    ensure!(theirs.group() == Some(&our_group), "not in the right group");
166    Ok(their_key)
167}