main.rs
1 use pqcrypto_kyber::kyber1024; 2 use pqcrypto_traits::kem::{PublicKey, SharedSecret, Ciphertext}; 3 use std::fs; 4 use std::path::PathBuf; 5 use std::io::{self, Write}; 6 use hex; 7 use aes_gcm::{ 8 aead::{Aead, KeyInit, OsRng, Payload}, 9 Aes256Gcm, Nonce 10 }; 11 use sha3::{Shake256, Sha3_256, digest::{ExtendableOutput, Update, XofReader, Digest}}; 12 use rand_core::RngCore; 13 use zeroize::Zeroize; 14 15 fn main() -> Result<(), Box<dyn std::error::Error>> { 16 println!("=== GHOSTLINE OTP DISTRIBUTION (QUANTUM-SAFE MULTI-FRIEND) ==="); 17 18 // 1. GET OTP FILE 19 println!("\n1. š YOUR OTP FILE"); 20 print!("Path to OTP file: "); 21 io::stdout().flush()?; 22 let mut otp_path = String::new(); 23 io::stdin().read_line(&mut otp_path)?; 24 let otp_path = otp_path.trim(); 25 26 let otp_data = fs::read(otp_path) 27 .map_err(|e| format!("Failed to read OTP file '{}': {}", otp_path, e))?; 28 println!("ā Loaded OTP: {} bytes", otp_data.len()); 29 30 // 2. GET FRIENDS' PUBLIC KEYS 31 println!("\n2. š FRIENDS' PUBLIC KEYS"); 32 print!("Directory containing friends' public keys: "); 33 io::stdout().flush()?; 34 let mut keys_dir = String::new(); 35 io::stdin().read_line(&mut keys_dir)?; 36 let keys_dir = keys_dir.trim(); 37 38 let mut friend_public_keys = Vec::new(); 39 let mut friend_ids = Vec::new(); 40 41 for entry in fs::read_dir(keys_dir)? { 42 let entry = entry?; 43 let path = entry.path(); 44 if path.is_file() && path.extension().map_or(false, |ext| ext == "key") { 45 if let Some(filename) = path.file_stem() { 46 let friend_id = filename.to_string_lossy().to_string(); 47 println!(" Found: {} -> {}", friend_id, path.display()); 48 49 let pub_bytes = fs::read(&path) 50 .map_err(|e| format!("Failed to read key file '{}': {}", path.display(), e))?; 51 let pub_key = kyber1024::PublicKey::from_bytes(&pub_bytes) 52 .map_err(|e| format!("Failed to parse key '{}': {}", friend_id, e))?; 53 54 friend_public_keys.push(pub_key); 55 friend_ids.push(friend_id); 56 } 57 } 58 } 59 60 if friend_public_keys.is_empty() { 61 return Err("No public key files found!".into()); 62 } 63 64 println!("ā Loaded {} friend(s)", friend_public_keys.len()); 65 66 // 3. GENERATE RANDOM AES KEY (will be shared with all friends) 67 println!("\n3. š GENERATING SESSION AES KEY"); 68 let mut aes_key = [0u8; 32]; 69 OsRng.fill_bytes(&mut aes_key); 70 println!("ā Generated random AES-256 key: {}...", hex::encode(&aes_key[..8])); 71 72 // 4. ENCRYPT OTP WITH AES KEY (SAME FOR EVERYONE) 73 println!("\n4. š ENCRYPTING OTP (SAME FOR ALL FRIENDS)"); 74 let cipher = Aes256Gcm::new_from_slice(&aes_key)?; 75 76 let mut nonce_bytes = [0u8; 12]; 77 OsRng.fill_bytes(&mut nonce_bytes); 78 let nonce = Nonce::from_slice(&nonce_bytes); 79 80 let payload = Payload { 81 msg: &otp_data, 82 aad: b"", 83 }; 84 85 let encrypted_otp = cipher.encrypt(nonce, payload) 86 .map_err(|e| format!("AES encryption failed: {}", e))?; 87 88 println!("ā OTP encrypted: {} bytes (same for all friends)", encrypted_otp.len()); 89 90 // 5. CREATE PACKAGES FOR EACH FRIEND 91 println!("\n5. š¦ CREATING FRIEND PACKAGES"); 92 93 let base_output_dir = "./distribution/"; 94 fs::create_dir_all(base_output_dir)?; 95 96 let common_dir = PathBuf::from(base_output_dir).join("common"); 97 fs::create_dir_all(&common_dir)?; 98 99 fs::write(common_dir.join("encrypted_otp.bin"), &encrypted_otp)?; 100 fs::write(common_dir.join("aes_nonce.bin"), nonce_bytes)?; 101 102 let metadata = format!( 103 "GhostLine Multi-Friend Distribution\n\ 104 Date: {}\n\ 105 OTP size: {} bytes\n\ 106 Number of friends: {}\n\ 107 Friend IDs: {}\n\ 108 Kyber: 1024\n\ 109 KDF: SHAKE256\n\ 110 AES: 256-GCM\n\ 111 \nā ļø SECURITY WARNING:\n\ 112 ⢠Each OTP distribution creates NEW Kyber encapsulations\n\ 113 ⢠NEVER reuse the same ciphertext for multiple OTPs!\n\ 114 ⢠This package is for ONE-TIME USE only\n", 115 chrono::Local::now(), 116 otp_data.len(), 117 friend_ids.len(), 118 friend_ids.join(", ") 119 ); 120 121 fs::write(common_dir.join("README.txt"), metadata)?; 122 123 let mut friend_packages = Vec::new(); 124 125 for (i, (friend_pub, friend_id)) in friend_public_keys.iter().zip(&friend_ids).enumerate() { 126 println!("\n Processing: {} (friend #{})", friend_id, i + 1); 127 128 let (shared_secret, ciphertext) = kyber1024::encapsulate(friend_pub); 129 let secret_bytes = shared_secret.as_bytes(); 130 let ciphertext_bytes = ciphertext.as_bytes(); 131 132 // KDF: SHAKE256 - FIXED: NO OTP SIZE INCLUDED 133 let mut shake = Shake256::default(); 134 Update::update(&mut shake, secret_bytes); 135 Update::update(&mut shake, b"GHOSTLINE-AES-KEY-WRAP-v1"); 136 Update::update(&mut shake, &ciphertext_bytes[..32]); 137 // NO OTP SIZE HERE - Friend doesn't know it! 138 139 let mut wrap_key = [0u8; 32]; 140 let mut reader = shake.finalize_xof(); 141 reader.read(&mut wrap_key); 142 143 let wrap_cipher = Aes256Gcm::new_from_slice(&wrap_key)?; 144 let mut wrap_nonce = [0u8; 12]; 145 OsRng.fill_bytes(&mut wrap_nonce); 146 147 let payload = Payload { 148 msg: &aes_key[..], 149 aad: b"", 150 }; 151 152 let wrapped_aes_key = wrap_cipher.encrypt( 153 Nonce::from_slice(&wrap_nonce), 154 payload 155 ).map_err(|e| format!("Failed to wrap AES key for {}: {}", friend_id, e))?; 156 157 let friend_dir = PathBuf::from(base_output_dir).join(friend_id); 158 fs::create_dir_all(&friend_dir)?; 159 160 fs::write(friend_dir.join("kyber_ciphertext.bin"), ciphertext_bytes)?; 161 fs::write(friend_dir.join("wrapped_aes_key.bin"), &wrapped_aes_key)?; 162 fs::write(friend_dir.join("wrap_nonce.bin"), wrap_nonce)?; 163 164 let mut hasher = Sha3_256::new(); 165 Update::update(&mut hasher, ciphertext_bytes); 166 let ct_hash = hasher.finalize(); 167 168 let mut hasher = Sha3_256::new(); 169 Update::update(&mut hasher, &wrapped_aes_key); 170 let wrapped_hash = hasher.finalize(); 171 172 let friend_manifest = format!( 173 "Friend: {}\n\ 174 Kyber ciphertext SHA3-256: {}\n\ 175 Wrapped AES key SHA3-256: {}\n\ 176 \nFiles needed:\n\ 177 1. kyber_ciphertext.bin (this file)\n\ 178 2. wrapped_aes_key.bin (this file)\n\ 179 3. wrap_nonce.bin (this file)\n\ 180 4. encrypted_otp.bin (from common/ folder)\n\ 181 5. aes_nonce.bin (from common/ folder)\n", 182 friend_id, 183 hex::encode(ct_hash), 184 hex::encode(wrapped_hash) 185 ); 186 187 fs::write(friend_dir.join("MANIFEST.txt"), friend_manifest)?; 188 189 println!(" ā Package created in: {}", friend_dir.display()); 190 friend_packages.push(friend_id.clone()); 191 } 192 193 aes_key.zeroize(); 194 195 println!("\n6. ā DISTRIBUTION COMPLETE"); 196 println!(" Common files (same for all):"); 197 println!(" ⢠encrypted_otp.bin - Encrypted OTP"); 198 println!(" ⢠aes_nonce.bin - AES nonce"); 199 println!(" ⢠README.txt - Instructions"); 200 201 println!("\n Individual packages for each friend:"); 202 for (i, friend_id) in friend_packages.iter().enumerate() { 203 println!(" {}. {} - Contains their Kyber ciphertext", i + 1, friend_id); 204 } 205 206 println!("\nš¤ DISTRIBUTION INSTRUCTIONS:"); 207 println!("1. Give EACH friend their folder: distribution/{{friend_name}}/"); 208 println!("2. Share COMMON folder with ALL: distribution/common/"); 209 println!("3. Each friend needs: their folder + common folder"); 210 211 let mut hasher = Sha3_256::new(); 212 Update::update(&mut hasher, &encrypted_otp); 213 let otp_hash = hasher.finalize(); 214 215 let overall_manifest = format!( 216 "=== GHOSTLINE DISTRIBUTION MANIFEST ===\n\ 217 Encrypted OTP SHA3-256: {}\n\ 218 Number of recipients: {}\n\ 219 Recipients: {}\n\ 220 Date: {}\n", 221 hex::encode(otp_hash), 222 friend_packages.len(), 223 friend_packages.join(", "), 224 chrono::Local::now() 225 ); 226 227 fs::write(PathBuf::from(base_output_dir).join("OVERALL_MANIFEST.txt"), overall_manifest)?; 228 229 Ok(()) 230 }