Skip to main content

deaddrop_core/protocol/
handshake.rs

1use super::codec::{decode_cbor, encode_cbor, read_frame_async, write_frame_async};
2use super::messages::Message;
3use crate::crypto::{
4    CryptoProvider, DefaultProvider, PrivateIdentity, X25519Public, X25519Secret, random_bytes,
5    session_keys, verify_identity, x25519_ephemeral,
6};
7use crate::{CapabilityDoc, DdError, ErrorCode, NegotiatedCaps, PublicIdentity, Result};
8use tokio::io::{AsyncRead, AsyncWrite};
9
10pub struct Session {
11    pub peer: crate::PeerId,
12    pub peer_identity: PublicIdentity,
13    pub caps: NegotiatedCaps,
14    pub send_key: [u8; 32],
15    pub recv_key: [u8; 32],
16    pub send_nonce: u64,
17    pub recv_nonce: u64,
18}
19
20pub async fn handshake_initiator<S>(identity: &PrivateIdentity, stream: &mut S) -> Result<Session>
21where
22    S: AsyncRead + AsyncWrite + Unpin,
23{
24    let (eph_sk, eph_pk) = x25519_ephemeral();
25    let nonce = u64::from_le_bytes(random_bytes::<8>());
26    let mut proof_msg = Vec::from(&b"ddp-ch-v2"[..]);
27    proof_msg.extend_from_slice(&eph_pk.to_bytes());
28    proof_msg.extend_from_slice(&nonce.to_be_bytes());
29    let identity_proof = DefaultProvider.sign(&identity.signing_key(), &proof_msg);
30    let hello = Message::ClientHello {
31        protocol: crate::PROTOCOL_VERSION,
32        identity: identity.public.clone(),
33        eph_pk: eph_pk.to_bytes(),
34        nonce,
35        caps: CapabilityDoc::local_v2(),
36        identity_proof,
37    };
38    write_plain(stream, &hello).await?;
39    let reply = read_plain(stream).await?;
40    match reply {
41        Message::ServerHello {
42            protocol,
43            identity: peer_ident,
44            eph_pk: remote_eph,
45            nonce: snonce,
46            caps,
47            transcript_sig,
48        } => finish(
49            true,
50            protocol,
51            peer_ident,
52            &eph_sk,
53            &X25519Public::from(remote_eph),
54            nonce,
55            snonce,
56            caps,
57            &transcript_sig,
58            Some(&identity.public),
59        ),
60        other => Err(DdError::invalid_frame(format!(
61            "expected server_hello, got {}",
62            other.name()
63        ))),
64    }
65}
66
67pub async fn handshake_responder<S>(identity: &PrivateIdentity, stream: &mut S) -> Result<Session>
68where
69    S: AsyncRead + AsyncWrite + Unpin,
70{
71    let hello = read_plain(stream).await?;
72    let Message::ClientHello {
73        protocol,
74        identity: peer_ident,
75        eph_pk: remote_eph,
76        nonce: cnonce,
77        caps,
78        identity_proof,
79    } = hello
80    else {
81        return Err(DdError::protocol(
82            ErrorCode::Dda3001AuthFailed,
83            "expected client_hello",
84        ));
85    };
86    let mut proof_msg = Vec::from(&b"ddp-ch-v2"[..]);
87    proof_msg.extend_from_slice(&remote_eph);
88    proof_msg.extend_from_slice(&cnonce.to_be_bytes());
89    DefaultProvider
90        .verify(&peer_ident.ed25519_pk, &proof_msg, &identity_proof)
91        .map_err(|_| DdError::protocol(ErrorCode::Dda3001AuthFailed, "client proof"))?;
92    let (eph_sk, eph_pk) = x25519_ephemeral();
93    let snonce = u64::from_le_bytes(random_bytes::<8>());
94    let remote = X25519Public::from(remote_eph);
95    let transcript = transcript_bytes(
96        &peer_ident,
97        &identity.public,
98        &remote_eph,
99        &eph_pk.to_bytes(),
100        cnonce,
101        snonce,
102    );
103    let sig = DefaultProvider.sign(&identity.signing_key(), &transcript);
104    let reply = Message::ServerHello {
105        protocol: crate::PROTOCOL_VERSION,
106        identity: identity.public.clone(),
107        eph_pk: eph_pk.to_bytes(),
108        nonce: snonce,
109        caps: CapabilityDoc::local_v2(),
110        transcript_sig: sig,
111    };
112    write_plain(stream, &reply).await?;
113    finish(
114        false,
115        protocol,
116        peer_ident,
117        &eph_sk,
118        &remote,
119        cnonce,
120        snonce,
121        caps,
122        &[0u8; 64],
123        Some(&identity.public),
124    )
125}
126
127#[allow(clippy::too_many_arguments)]
128fn finish(
129    initiator: bool,
130    protocol: u16,
131    peer_ident: PublicIdentity,
132    local_eph: &X25519Secret,
133    remote_eph: &X25519Public,
134    cnonce: u64,
135    snonce: u64,
136    peer_caps: CapabilityDoc,
137    transcript_sig: &[u8; 64],
138    local_public: Option<&PublicIdentity>,
139) -> Result<Session> {
140    if protocol != crate::PROTOCOL_VERSION {
141        return Err(DdError::protocol(
142            ErrorCode::Ddp1002UnsupportedVersion,
143            format!("DDP/{protocol}"),
144        ));
145    }
146    let peer = verify_identity(&peer_ident)?;
147    let local_eph_pk = X25519Public::from(local_eph);
148    let (client_ident, server_ident, client_eph, server_eph) = if initiator {
149        (
150            local_public.expect("local"),
151            &peer_ident,
152            local_eph_pk.to_bytes(),
153            remote_eph.to_bytes(),
154        )
155    } else {
156        (
157            &peer_ident,
158            local_public.expect("local"),
159            remote_eph.to_bytes(),
160            local_eph_pk.to_bytes(),
161        )
162    };
163    let transcript = transcript_bytes(
164        client_ident,
165        server_ident,
166        &client_eph,
167        &server_eph,
168        cnonce,
169        snonce,
170    );
171    if initiator {
172        DefaultProvider.verify(&peer_ident.ed25519_pk, &transcript, transcript_sig)?;
173    }
174    let keys = session_keys(initiator, local_eph, remote_eph, &transcript)?;
175    let caps = CapabilityDoc::local_v2().negotiate(&peer_caps)?;
176    Ok(Session {
177        peer,
178        peer_identity: peer_ident,
179        caps,
180        send_key: keys.send,
181        recv_key: keys.recv,
182        send_nonce: 0,
183        recv_nonce: 0,
184    })
185}
186
187fn transcript_bytes(
188    client: &PublicIdentity,
189    server: &PublicIdentity,
190    client_eph: &[u8; 32],
191    server_eph: &[u8; 32],
192    cnonce: u64,
193    snonce: u64,
194) -> Vec<u8> {
195    let mut t = Vec::from(&b"ddp-hs-v2"[..]);
196    t.extend_from_slice(&client.ed25519_pk);
197    t.extend_from_slice(&client.x25519_pk);
198    t.extend_from_slice(&server.ed25519_pk);
199    t.extend_from_slice(&server.x25519_pk);
200    t.extend_from_slice(client_eph);
201    t.extend_from_slice(server_eph);
202    t.extend_from_slice(&cnonce.to_be_bytes());
203    t.extend_from_slice(&snonce.to_be_bytes());
204    t
205}
206
207async fn write_plain<S: AsyncWrite + Unpin>(s: &mut S, msg: &Message) -> Result<()> {
208    write_frame_async(s, &encode_cbor(msg)?).await
209}
210
211async fn read_plain<S: AsyncRead + Unpin>(s: &mut S) -> Result<Message> {
212    let bytes = read_frame_async(s).await?;
213    decode_cbor(&bytes)
214}
215
216pub async fn write_msg<S: AsyncWrite + Unpin>(s: &mut S, msg: &Message) -> Result<()> {
217    write_plain(s, msg).await
218}
219
220pub async fn read_msg<S: AsyncRead + Unpin>(s: &mut S) -> Result<Message> {
221    read_plain(s).await
222}