zoe_wire_protocol/challenge/
client.rs1use super::MAX_PACKAGE_SIZE;
2use crate::{
3 keys::*, KeyChallenge, KeyProof, KeyResponse, ZoeChallenge, ZoeChallengeResult,
4 ZoeChallengeWarning,
5};
6use anyhow::Result;
7use quinn::{RecvStream, SendStream};
8use tokio::io::{AsyncReadExt, AsyncWriteExt};
9use tracing::{debug, warn};
10
11pub async fn perform_client_challenge_handshake(
51 mut send: SendStream,
52 mut recv: RecvStream,
53 server_public_key: &VerifyingKey,
54 key_pairs: &[&KeyPair],
55) -> Result<(usize, Vec<ZoeChallengeWarning>)> {
56 debug!("🔐 Starting client-side multi-challenge handshake");
57
58 if key_pairs.is_empty() {
59 return Err(anyhow::anyhow!("No keys provided for handshake"));
60 }
61
62 debug!("Proving possession of {} keys", key_pairs.len());
63
64 let verified_count = key_pairs.len();
65 let mut warnings = Vec::new();
66
67 loop {
68 let result = receive_result(&mut recv).await?;
70
71 match result {
72 ZoeChallengeResult::Accepted => {
73 debug!("✅ All challenges completed successfully");
74 break;
75 }
76 ZoeChallengeResult::Warning(warning) => {
77 warn!("🔔 Warning received: {warning:?}");
78 warnings.push(warning);
79 continue; }
81 ZoeChallengeResult::Next => {
82 debug!("➡️ Challenge accepted, waiting for next challenge");
83 }
85 ZoeChallengeResult::Rejected(rejection) => {
86 return Err(anyhow::anyhow!("Challenge rejected: {rejection:?}"));
87 }
88 ZoeChallengeResult::Error(error) => {
89 return Err(anyhow::anyhow!("Server error: {error}"));
90 }
91 ZoeChallengeResult::Unknown { discriminant, .. } => {
92 return Err(anyhow::anyhow!("Unsupported result type: {discriminant}"));
93 }
94 }
95 debug!("📥 Waiting to receive challenge from server...");
97 let challenge = receive_challenge(&mut recv).await?;
98 debug!("✅ Received challenge from server");
99
100 match challenge {
102 ZoeChallenge::Key(key_challenge) => {
103 debug!("📝 Received key challenge");
104
105 let nonce = key_challenge.nonce;
107 let signature = &key_challenge.signature;
108 debug!("🔍 Verifying server signature on challenge nonce...");
109 if server_public_key.verify(&nonce, signature).is_err() {
110 return Err(anyhow::anyhow!(
111 "Invalid signature in challenge. Person-in-the-middle attack?"
112 ));
113 }
114 debug!("✅ Server signature verified");
115
116 debug!("🔧 Creating key proofs for {} keys...", key_pairs.len());
118 let response = create_key_proofs(&key_challenge, key_pairs)?;
119 debug!("✅ Created {} key proofs", response.key_proofs.len());
120
121 debug!("📤 Sending key response to server...");
123 send_key_response(&mut send, &response).await?;
124 debug!("✅ Key response sent");
125 }
126 ZoeChallenge::Unknown { discriminant, .. } => {
127 return Err(anyhow::anyhow!(
128 "Unsupported challenge type: {discriminant}"
129 ));
130 }
131 }
132 }
133
134 debug!(
135 "✅ Client-side multi-challenge handshake completed. {} keys verified",
136 verified_count
137 );
138
139 Ok((verified_count, warnings))
140}
141
142async fn receive_challenge(recv: &mut RecvStream) -> Result<ZoeChallenge> {
154 let challenge_len = recv.read_u32().await? as usize;
156
157 if challenge_len > MAX_PACKAGE_SIZE {
158 return Err(anyhow::anyhow!(
159 "Challenge too large: {} bytes (max: {})",
160 challenge_len,
161 MAX_PACKAGE_SIZE
162 ));
163 }
164
165 debug!("Receiving challenge ({} bytes)", challenge_len);
166
167 let mut challenge_buf = vec![0u8; challenge_len];
169 recv.read_exact(&mut challenge_buf).await?;
170
171 let challenge: ZoeChallenge = postcard::from_bytes(&challenge_buf)?;
173
174 debug!("Received challenge from server");
175 Ok(challenge)
176}
177
178pub fn create_key_proofs(challenge: &KeyChallenge, key_pairs: &[&KeyPair]) -> Result<KeyResponse> {
192 let challenge_data = challenge;
193
194 let signature_data = challenge_data.nonce.to_vec();
196
197 debug!("Creating proofs for {} keys", key_pairs.len());
198
199 let mut key_proofs = Vec::new();
200
201 for (index, keypair) in key_pairs.iter().enumerate() {
202 let signature = keypair.sign(&signature_data);
204 let verifying_key = keypair.public_key();
205
206 let key_proof = KeyProof {
208 public_key: verifying_key,
209 signature,
210 };
211
212 key_proofs.push(key_proof);
213 debug!("Created proof for key {}", index);
214 }
215
216 let response = KeyResponse { key_proofs };
217 Ok(response)
218}
219
220async fn send_key_response(send: &mut SendStream, response: &KeyResponse) -> Result<()> {
229 let response_bytes = postcard::to_stdvec(response)?;
230
231 debug!("Sending response ({} bytes)", response_bytes.len());
232
233 send.write_u32(response_bytes.len() as u32).await?;
235
236 send.write_all(&response_bytes).await?;
238
239 Ok(())
240}
241
242async fn receive_result(recv: &mut RecvStream) -> Result<ZoeChallengeResult> {
254 let result_len = recv.read_u32().await? as usize;
256
257 if result_len > MAX_PACKAGE_SIZE {
258 return Err(anyhow::anyhow!(
259 "Result too large: {} bytes (max: {})",
260 result_len,
261 MAX_PACKAGE_SIZE
262 ));
263 }
264
265 debug!("Receiving result ({} bytes)", result_len);
266
267 let mut result_buf = vec![0u8; result_len];
269 recv.read_exact(&mut result_buf).await?;
270
271 let result: ZoeChallengeResult = postcard::from_bytes(&result_buf)?;
273
274 debug!("Received verification result from server");
275 Ok(result)
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281 use crate::{KeyPair, ZoeChallengeRejection};
282 use anyhow::Result;
283
284 #[allow(dead_code)]
286 fn process_result(result: &ZoeChallengeResult, expected_count: usize) -> Result<usize> {
287 match result {
288 ZoeChallengeResult::Accepted => Ok(expected_count),
289 ZoeChallengeResult::Next => Ok(expected_count),
290 ZoeChallengeResult::Warning(warning) => {
291 Err(anyhow::anyhow!(format!("Warning received: {warning:?}")))
292 }
293 ZoeChallengeResult::Rejected(rejection) => Err(anyhow::anyhow!(format!(
294 "Challenge rejected: {rejection:?}"
295 ))),
296 ZoeChallengeResult::Error(error) => {
297 Err(anyhow::anyhow!(format!("Challenge error: {error}")))
298 }
299 ZoeChallengeResult::Unknown { discriminant, .. } => Err(anyhow::anyhow!(format!(
300 "Unknown challenge result: {discriminant}"
301 ))),
302 }
303 }
304
305 #[test]
306 fn test_create_key_proofs() {
307 let keypair1 = KeyPair::generate(&mut rand::thread_rng());
309 let keypair2 = KeyPair::generate(&mut rand::thread_rng());
310
311 let server_keypair = KeyPair::generate_ed25519(&mut rand::thread_rng());
313 let nonce = [42u8; 32];
314 let server_signature = server_keypair.sign(&nonce);
315
316 let challenge_data = KeyChallenge {
317 nonce,
318 signature: server_signature,
319 expires_at: 1234567890,
320 };
321
322 let keys = vec![&keypair1, &keypair2];
324 let response = create_key_proofs(&challenge_data, &keys).unwrap();
325
326 assert_eq!(response.key_proofs.len(), 2);
328
329 for (i, _proof) in response.key_proofs.iter().enumerate() {
331 debug!("Key {} proof created successfully", i);
334
335 let test_data = b"test";
337 let test_sig = keys[i].sign(test_data);
338 let pub_key = keys[i].public_key();
339
340 pub_key.verify(test_data, &test_sig).unwrap();
342 }
343 }
344
345 #[test]
346 fn test_process_result_accepted() {
347 let result = ZoeChallengeResult::Accepted;
348 let verified_count = process_result(&result, 3).unwrap();
349 assert_eq!(verified_count, 3);
350 }
351
352 #[test]
353 fn test_process_result_next() {
354 let result = ZoeChallengeResult::Next;
355 let verified_count = process_result(&result, 3).unwrap();
356 assert_eq!(verified_count, 3);
357 }
358
359 #[test]
360 fn test_process_result_rejected() {
361 let result = ZoeChallengeResult::Rejected(ZoeChallengeRejection::ChallengeFailed);
362 let result = process_result(&result, 3);
363 assert!(result.is_err());
364 }
365
366 #[test]
367 fn test_create_key_proofs_client() {
368 let server_keypair = KeyPair::generate_ed25519(&mut rand::thread_rng());
369 let client_keypair1 = KeyPair::generate(&mut rand::thread_rng());
370 let client_keypair2 = KeyPair::generate(&mut rand::thread_rng());
371
372 let nonce = [42u8; 32];
374 let signature = server_keypair.sign(&nonce);
375 let challenge = KeyChallenge {
376 nonce,
377 signature,
378 expires_at: std::time::SystemTime::now()
379 .duration_since(std::time::UNIX_EPOCH)
380 .unwrap()
381 .as_secs()
382 + 300,
383 };
384
385 let client_keys = vec![&client_keypair1, &client_keypair2];
386 let response = create_key_proofs(&challenge, &client_keys).unwrap();
387
388 assert_eq!(response.key_proofs.len(), 2);
390
391 for (i, proof) in response.key_proofs.iter().enumerate() {
393 let expected_key = &client_keys[i];
394 assert_eq!(
395 proof.public_key.encode(),
396 expected_key.public_key().encode()
397 );
398
399 assert!(proof
401 .public_key
402 .verify(&challenge.nonce, &proof.signature)
403 .is_ok());
404 }
405 }
406
407 #[test]
408 fn test_send_key_response_serialization() {
409 let client_keypair = KeyPair::generate(&mut rand::thread_rng());
410 let signature = client_keypair.sign(b"test data");
411
412 let response = KeyResponse {
413 key_proofs: vec![KeyProof {
414 public_key: client_keypair.public_key(),
415 signature,
416 }],
417 };
418
419 let serialized = postcard::to_stdvec(&response).unwrap();
421 assert!(!serialized.is_empty());
422
423 let deserialized: KeyResponse = postcard::from_bytes(&serialized).unwrap();
425 assert_eq!(response.key_proofs.len(), deserialized.key_proofs.len());
426 }
427
428 #[test]
429 fn test_challenge_signature_verification() {
430 let server_keypair = KeyPair::generate_ed25519(&mut rand::thread_rng());
431 let server_public_key = server_keypair.public_key();
432
433 let nonce = [42u8; 32];
435 let signature = server_keypair.sign(&nonce);
436 let challenge = KeyChallenge {
437 nonce,
438 signature,
439 expires_at: std::time::SystemTime::now()
440 .duration_since(std::time::UNIX_EPOCH)
441 .unwrap()
442 .as_secs()
443 + 300,
444 };
445
446 assert!(server_public_key
448 .verify(&challenge.nonce, &challenge.signature)
449 .is_ok());
450
451 let wrong_signature = server_keypair.sign(b"wrong data");
453 let bad_challenge = KeyChallenge {
454 nonce,
455 signature: wrong_signature,
456 expires_at: challenge.expires_at,
457 };
458
459 assert!(server_public_key
460 .verify(&bad_challenge.nonce, &bad_challenge.signature)
461 .is_err());
462 }
463
464 #[test]
465 fn test_empty_key_list() {
466 let server_keypair = KeyPair::generate_ed25519(&mut rand::thread_rng());
467
468 let nonce = [42u8; 32];
469 let signature = server_keypair.sign(&nonce);
470 let challenge = KeyChallenge {
471 nonce,
472 signature,
473 expires_at: std::time::SystemTime::now()
474 .duration_since(std::time::UNIX_EPOCH)
475 .unwrap()
476 .as_secs()
477 + 300,
478 };
479
480 let empty_keys: Vec<&KeyPair> = vec![];
481 let response = create_key_proofs(&challenge, &empty_keys).unwrap();
482
483 assert_eq!(response.key_proofs.len(), 0);
485 }
486}