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}