1use crate::{OpenADPError, Result};
8use crate::keygen::{generate_encryption_key, recover_encryption_key};
9use crate::client::{ServerInfo, get_servers};
10use serde::{Deserialize, Serialize};
11use aes_gcm::{Aes256Gcm, Key, Nonce, KeyInit};
12use aes_gcm::aead::Aead;
13use rand::Rng;
14use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
15use sha2::{Sha256, Digest};
16use hex;
17
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct WrappedSecret {
22 pub nonce: String,
23 pub ciphertext: String,
24 pub tag: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct OcryptMetadata {
30 pub servers: Vec<String>,
32 pub threshold: usize,
33 pub version: String,
34 pub auth_code: String,
35 pub user_id: String,
36
37 pub wrapped_long_term_secret: WrappedSecret,
39 pub backup_id: String,
40 pub app_id: String,
41 pub max_guesses: i32,
42 pub ocrypt_version: String,
43}
44
45pub async fn register(
86 user_id: &str,
87 app_id: &str,
88 long_term_secret: &[u8],
89 pin: &str,
90 max_guesses: i32,
91 servers_url: &str,
92) -> Result<Vec<u8>> {
93 register_with_bid(user_id, app_id, long_term_secret, pin, max_guesses, "even", servers_url).await
94}
95
96pub async fn recover(
135 metadata_bytes: &[u8],
136 pin: &str,
137 servers_url: &str,
138) -> Result<(Vec<u8>, u32, Vec<u8>)> {
139 eprintln!("📋 Step 1: Recovering with existing backup...");
141 let (secret, remaining) = recover_without_refresh(metadata_bytes, pin, servers_url).await?;
142
143 let updated_metadata = match attempt_backup_refresh(&secret, metadata_bytes, pin, servers_url).await {
145 Ok(new_metadata) => {
146 eprintln!("✅ Backup refresh successful");
147 new_metadata
148 }
149 Err(e) => {
150 eprintln!("⚠️ Backup refresh failed (using original): {}", e);
151 metadata_bytes.to_vec()
152 }
153 };
154
155 Ok((secret, remaining, updated_metadata))
156}
157
158pub async fn recover_and_reregister(
194 metadata_bytes: &[u8],
195 pin: &str,
196 servers_url: &str,
197) -> Result<(Vec<u8>, Vec<u8>)> {
198 if metadata_bytes.is_empty() {
200 return Err(OpenADPError::InvalidInput("metadata cannot be empty".to_string()));
201 }
202 if pin.is_empty() {
203 return Err(OpenADPError::InvalidInput("pin cannot be empty".to_string()));
204 }
205
206 eprintln!("🔄 Starting recovery and re-registration...");
207
208 eprintln!("📋 Step 1: Recovering with existing metadata...");
210 let (secret, remaining) = recover_without_refresh(metadata_bytes, pin, servers_url).await?;
211
212 let metadata_text = String::from_utf8_lossy(metadata_bytes);
214 let metadata: OcryptMetadata = serde_json::from_str(&metadata_text)
215 .map_err(|e| OpenADPError::InvalidInput(format!("Invalid metadata format: {}", e)))?;
216
217 let user_id = &metadata.user_id;
219 let app_id = &metadata.app_id;
220 let max_guesses = metadata.max_guesses;
221
222 eprintln!(" ✅ Secret recovered successfully ({} guesses remaining)", remaining);
223 eprintln!(" 🔑 User: {}, App: {}", user_id, app_id);
224
225 eprintln!("📋 Step 2: Fresh registration with new cryptographic material...");
227
228 let old_backup_id = &metadata.backup_id;
230 let new_backup_id = generate_next_backup_id(old_backup_id);
231 eprintln!("🔄 Backup ID alternation: {} → {}", old_backup_id, new_backup_id);
232
233 let new_metadata = register_with_bid(user_id, app_id, &secret, pin, max_guesses, &new_backup_id, servers_url).await?;
234
235 eprintln!("✅ Recovery and re-registration complete!");
236 eprintln!(" 📝 New metadata contains completely fresh cryptographic material");
237
238 Ok((secret, new_metadata))
239}
240
241async fn register_with_bid(
243 user_id: &str,
244 app_id: &str,
245 long_term_secret: &[u8],
246 pin: &str,
247 max_guesses: i32,
248 backup_id: &str,
249 servers_url: &str,
250) -> Result<Vec<u8>> {
251 validate_inputs(user_id, app_id, long_term_secret, pin, max_guesses)?;
253
254 eprintln!("🔐 Protecting secret for user: {}", user_id);
255 eprintln!("📱 Application: {}", app_id);
256 eprintln!("🔑 Secret length: {} bytes", long_term_secret.len());
257
258 eprintln!("🌐 Discovering OpenADP servers...");
260 let server_infos = discover_servers(servers_url).await?;
261
262 if server_infos.is_empty() {
263 return Err(OpenADPError::NoServers);
264 }
265
266 let selected_servers = if server_infos.len() > 15 { use rand::seq::SliceRandom;
269 let mut servers = server_infos;
270 servers.shuffle(&mut rand::thread_rng());
271 servers.into_iter().take(15).collect()
272 } else {
273 server_infos
274 };
275
276 eprintln!("📋 Using {} servers for registration", selected_servers.len());
277
278 eprintln!("🔄 Using backup ID: {}", backup_id);
280 eprintln!("🔑 Generating encryption key using OpenADP servers...");
281
282 let identity = crate::keygen::Identity::new(
284 user_id.to_string(), app_id.to_string(), backup_id.to_string() );
288
289 let key_result = generate_encryption_key(&identity, pin, max_guesses, 0, selected_servers).await?;
290
291 if key_result.encryption_key.is_none() {
292 return Err(OpenADPError::Server(
293 key_result.error.unwrap_or_else(|| "Key generation failed".to_string())
294 ));
295 }
296
297 let encryption_key = key_result.encryption_key.unwrap();
298 let auth_codes = key_result.auth_codes.unwrap();
299 let server_infos = key_result.server_infos.unwrap();
300 let threshold = key_result.threshold.unwrap();
301
302 eprintln!("✅ Generated encryption key with {} servers", server_infos.len());
303
304 eprintln!("🔐 Wrapping long-term secret...");
306 let wrapped_secret = wrap_secret(long_term_secret, &encryption_key)?;
307
308 let server_urls: Vec<String> = server_infos.iter().map(|s| s.url.clone()).collect();
310
311 let metadata = OcryptMetadata {
312 servers: server_urls,
313 threshold,
314 version: "1.0".to_string(),
315 auth_code: auth_codes.base_auth_code,
316 user_id: user_id.to_string(),
317 wrapped_long_term_secret: wrapped_secret,
318 backup_id: backup_id.to_string(),
319 app_id: app_id.to_string(),
320 max_guesses,
321 ocrypt_version: "1.0".to_string(),
322 };
323
324 let metadata_bytes = serde_json::to_vec(&metadata)?;
325 eprintln!("📦 Created metadata ({} bytes)", metadata_bytes.len());
326 eprintln!("🎯 Threshold: {}-of-{} recovery", metadata.threshold, metadata.servers.len());
327
328 Ok(metadata_bytes)
329}
330
331async fn recover_without_refresh(
333 metadata_bytes: &[u8],
334 pin: &str,
335 servers_url: &str,
336) -> Result<(Vec<u8>, u32)> {
337 let metadata: OcryptMetadata = serde_json::from_slice(metadata_bytes)?;
339
340 let identity = crate::keygen::Identity::new(
343 metadata.user_id.clone(), metadata.app_id.clone(), metadata.backup_id.clone() );
347
348 let server_infos = if servers_url.is_empty() {
350 metadata.servers.iter().map(|url| ServerInfo {
352 url: url.clone(),
353 public_key: String::new(),
354 country: String::new(),
355 remaining_guesses: None,
356 }).collect()
357 } else {
358 discover_servers(servers_url).await?
360 };
361
362 let mut server_auth_codes = std::collections::HashMap::new();
364
365 for server_url in &metadata.servers {
367 let combined = format!("{}:{}", metadata.auth_code, server_url);
368 let mut hasher = Sha256::new();
369 hasher.update(combined.as_bytes());
370 let hash = hasher.finalize();
371 let server_code = hex::encode(&hash);
372 server_auth_codes.insert(server_url.clone(), server_code);
373 }
374
375 let auth_codes = crate::keygen::AuthCodes {
376 base_auth_code: metadata.auth_code.clone(),
377 server_auth_codes,
378 };
379
380 let recovery_result = recover_encryption_key(
381 &identity,
382 pin,
383 server_infos,
384 metadata.threshold,
385 auth_codes
386 ).await?;
387
388 if recovery_result.encryption_key.is_none() {
389 return Err(OpenADPError::Server(
390 recovery_result.error.unwrap_or_else(|| "Key recovery failed".to_string())
391 ));
392 }
393
394 let encryption_key = recovery_result.encryption_key.unwrap();
395
396 let secret = unwrap_secret(&metadata.wrapped_long_term_secret, &encryption_key, recovery_result.max_guesses, recovery_result.num_guesses)?;
398
399 Ok((secret, 0)) }
401
402async fn attempt_backup_refresh(
404 secret: &[u8],
405 metadata_bytes: &[u8],
406 pin: &str,
407 servers_url: &str,
408) -> Result<Vec<u8>> {
409 let metadata: OcryptMetadata = serde_json::from_slice(metadata_bytes)?;
410
411 let new_backup_id = generate_next_backup_id(&metadata.backup_id);
413
414 eprintln!("🔄 Attempting backup refresh: {} → {}", metadata.backup_id, new_backup_id);
415
416 let new_metadata = register_with_bid(
418 &metadata.user_id,
419 &metadata.app_id,
420 secret,
421 pin,
422 metadata.max_guesses,
423 &new_backup_id,
424 servers_url,
425 ).await?;
426
427 let (recovered_secret, _) = recover_without_refresh(&new_metadata, pin, servers_url).await?;
429
430 if recovered_secret == secret {
431 eprintln!("✅ Two-phase commit verification successful");
432 Ok(new_metadata)
433 } else {
434 Err(OpenADPError::Server("Two-phase commit verification failed".to_string()))
435 }
436}
437
438async fn discover_servers(servers_url: &str) -> Result<Vec<ServerInfo>> {
440 let registry_url = if servers_url.is_empty() {
441 crate::DEFAULT_REGISTRY_URL
442 } else {
443 servers_url
444 };
445
446 eprintln!("🌐 Discovering servers from registry: {}", registry_url);
447
448 let servers = get_servers(registry_url).await?;
449
450 eprintln!(" ✅ Successfully fetched {} servers from registry", servers.len());
451 eprintln!(" 📋 {} servers are live and ready", servers.len());
452
453 Ok(servers)
454}
455
456fn wrap_secret(secret: &[u8], key: &[u8]) -> Result<WrappedSecret> {
458 if key.len() != 32 {
459 return Err(OpenADPError::Crypto("Key must be 32 bytes".to_string()));
460 }
461
462 let key = Key::<Aes256Gcm>::from_slice(key);
463 let cipher = Aes256Gcm::new(key);
464
465 let nonce_bytes: [u8; 12] = rand::thread_rng().gen();
466 let nonce = Nonce::from_slice(&nonce_bytes);
467
468 let ciphertext = cipher.encrypt(nonce, secret)
469 .map_err(|e| OpenADPError::Crypto(format!("AES-GCM encryption failed: {}", e)))?;
470
471 let (encrypted_data, tag) = ciphertext.split_at(ciphertext.len() - 16);
473
474 Ok(WrappedSecret {
475 nonce: BASE64.encode(&nonce_bytes),
476 ciphertext: BASE64.encode(encrypted_data),
477 tag: BASE64.encode(tag),
478 })
479}
480
481fn unwrap_secret(wrapped: &WrappedSecret, key: &[u8], max_guesses: i32, num_guesses: i32) -> Result<Vec<u8>> {
483 if key.len() != 32 {
484 return Err(OpenADPError::Crypto("Key must be 32 bytes".to_string()));
485 }
486
487 let key = Key::<Aes256Gcm>::from_slice(key);
488 let cipher = Aes256Gcm::new(key);
489
490 let nonce_bytes = BASE64.decode(&wrapped.nonce)
491 .map_err(|e| OpenADPError::Crypto(format!("Invalid nonce: {}", e)))?;
492 let nonce = Nonce::from_slice(&nonce_bytes);
493
494 let encrypted_data = BASE64.decode(&wrapped.ciphertext)
495 .map_err(|e| OpenADPError::Crypto(format!("Invalid ciphertext: {}", e)))?;
496 let tag = BASE64.decode(&wrapped.tag)
497 .map_err(|e| OpenADPError::Crypto(format!("Invalid tag: {}", e)))?;
498
499 let mut ciphertext_with_tag = encrypted_data;
501 ciphertext_with_tag.extend_from_slice(&tag);
502
503 let plaintext = cipher.decrypt(nonce, ciphertext_with_tag.as_slice())
504 .map_err(|_| {
505 if max_guesses > 0 && num_guesses > 0 {
507 let remaining = max_guesses - num_guesses;
508 if remaining > 0 {
509 eprintln!("❌ Invalid PIN! You have {} guesses remaining.", remaining);
510 } else {
511 eprintln!("❌ Invalid PIN! No more guesses remaining - account may be locked.");
512 }
513 } else {
514 eprintln!("❌ Invalid PIN! Check your password and try again.");
515 }
516 OpenADPError::Authentication("Invalid PIN or corrupted data".to_string())
517 })?;
518
519 Ok(plaintext)
520}
521
522fn generate_next_backup_id(current_backup_id: &str) -> String {
524 match current_backup_id {
525 "even" => "odd".to_string(),
526 "odd" => "even".to_string(),
527 _ => {
528 if current_backup_id.starts_with('v') {
530 let version_num: u32 = current_backup_id[1..].parse().unwrap_or(1);
531 format!("v{}", version_num + 1)
532 } else {
533 use std::time::{SystemTime, UNIX_EPOCH};
535 let timestamp = SystemTime::now()
536 .duration_since(UNIX_EPOCH)
537 .unwrap()
538 .as_secs();
539 format!("{}_v{}", current_backup_id, timestamp)
540 }
541 }
542 }
543}
544
545fn validate_inputs(
547 user_id: &str,
548 app_id: &str,
549 long_term_secret: &[u8],
550 pin: &str,
551 max_guesses: i32,
552) -> Result<()> {
553 if user_id.is_empty() {
554 return Err(OpenADPError::InvalidInput("user_id cannot be empty".to_string()));
555 }
556
557 if app_id.is_empty() {
558 return Err(OpenADPError::InvalidInput("app_id cannot be empty".to_string()));
559 }
560
561 if long_term_secret.is_empty() {
562 return Err(OpenADPError::InvalidInput("long_term_secret cannot be empty".to_string()));
563 }
564
565 if pin.is_empty() {
566 return Err(OpenADPError::InvalidInput("pin cannot be empty".to_string()));
567 }
568
569 if max_guesses <= 0 {
570 return Err(OpenADPError::InvalidInput("max_guesses must be at least 1".to_string()));
571 }
572
573 Ok(())
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_backup_id_generation() {
582 assert_eq!(generate_next_backup_id("even"), "odd");
583 assert_eq!(generate_next_backup_id("odd"), "even");
584 assert_eq!(generate_next_backup_id("v1"), "v2");
585 assert_eq!(generate_next_backup_id("v42"), "v43");
586
587 let timestamped = generate_next_backup_id("production");
588 assert!(timestamped.starts_with("production_v"));
589 }
590
591 #[test]
592 fn test_secret_wrapping() {
593 let secret = b"test_secret";
594 let key = [42u8; 32];
595
596 let wrapped = wrap_secret(secret, &key).unwrap();
597 let unwrapped = unwrap_secret(&wrapped, &key, 10, 0).unwrap();
598
599 assert_eq!(secret, unwrapped.as_slice());
600 }
601
602 #[test]
603 fn test_input_validation() {
604 assert!(validate_inputs("", "app", b"secret", "pin", 10).is_err());
605 assert!(validate_inputs("user", "", b"secret", "pin", 10).is_err());
606 assert!(validate_inputs("user", "app", b"", "pin", 10).is_err());
607 assert!(validate_inputs("user", "app", b"secret", "", 10).is_err());
608 assert!(validate_inputs("user", "app", b"secret", "pin", 0).is_err());
609 assert!(validate_inputs("user", "app", b"secret", "pin", 10).is_ok());
610 }
611
612 #[test]
613 fn test_metadata_serialization() {
614 let wrapped_secret = WrappedSecret {
615 nonce: "test_nonce".to_string(),
616 ciphertext: "test_ciphertext".to_string(),
617 tag: "test_tag".to_string(),
618 };
619
620 let metadata = OcryptMetadata {
621 servers: vec!["server1".to_string(), "server2".to_string()],
622 threshold: 2,
623 version: "1.0".to_string(),
624 auth_code: "auth123".to_string(),
625 user_id: "user@example.com".to_string(),
626 wrapped_long_term_secret: wrapped_secret,
627 backup_id: "even".to_string(),
628 app_id: "test_app".to_string(),
629 max_guesses: 10,
630 ocrypt_version: "1.0".to_string(),
631 };
632
633 let serialized = serde_json::to_vec(&metadata).unwrap();
634 let deserialized: OcryptMetadata = serde_json::from_slice(&serialized).unwrap();
635
636 assert_eq!(metadata.user_id, deserialized.user_id);
637 assert_eq!(metadata.app_id, deserialized.app_id);
638 assert_eq!(metadata.threshold, deserialized.threshold);
639 }
640}