use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{window, Crypto, SubtleCrypto, CryptoKey, CryptoKeyPair};
use js_sys::{Array, Object, Uint8Array, Promise};
use crate::error::Result;
pub struct WebCrypto {
subtle_crypto: SubtleCrypto,
our_key_pair: Option<CryptoKeyPair>,
peer_public_keys: std::collections::HashMap<String, CryptoKey>,
}
pub struct WebCryptoConfig {
pub rsa_key_size: u32,
pub aes_key_size: u32,
pub hash_algorithm: String,
}
impl Default for WebCryptoConfig {
fn default() -> Self {
Self {
rsa_key_size: 2048,
aes_key_size: 256,
hash_algorithm: "SHA-256".to_string(),
}
}
}
impl WebCrypto {
pub fn new() -> Self {
let window = window().expect("No window object available");
let crypto = window.crypto().expect("No crypto object available");
let subtle_crypto = crypto.subtle();
web_sys::console::log_1(&"Initialized WebCrypto manager".into());
Self {
subtle_crypto,
our_key_pair: None,
peer_public_keys: std::collections::HashMap::new(),
}
}
pub async fn generate_key_pair(&mut self) -> Result<()> {
web_sys::console::log_1(&"Generating RSA key pair...".into());
let algorithm = Object::new();
js_sys::Reflect::set(&algorithm, &"name".into(), &"RSA-OAEP".into())
.map_err(|e| anyhow::anyhow!("Failed to set algorithm name: {:?}", e))?;
js_sys::Reflect::set(&algorithm, &"modulusLength".into(), &2048u32.into())
.map_err(|e| anyhow::anyhow!("Failed to set modulus length: {:?}", e))?;
js_sys::Reflect::set(&algorithm, &"publicExponent".into(), &Uint8Array::from(&[0x01, 0x00, 0x01][..]))
.map_err(|e| anyhow::anyhow!("Failed to set public exponent: {:?}", e))?;
js_sys::Reflect::set(&algorithm, &"hash".into(), &"SHA-256".into())
.map_err(|e| anyhow::anyhow!("Failed to set hash algorithm: {:?}", e))?;
let key_usages = Array::new();
key_usages.push(&"encrypt".into());
key_usages.push(&"decrypt".into());
let key_pair_promise = self.subtle_crypto.generate_key_with_object(
&algorithm,
false, &key_usages,
).map_err(|e| anyhow::anyhow!("Failed to initiate key generation: {:?}", e))?;
let key_pair_result = JsFuture::from(key_pair_promise).await
.map_err(|e| anyhow::anyhow!("Key generation failed: {:?}", e))?;
let key_pair = key_pair_result.dyn_into::<CryptoKeyPair>()
.map_err(|e| anyhow::anyhow!("Failed to cast to CryptoKeyPair: {:?}", e))?;
self.our_key_pair = Some(key_pair);
web_sys::console::log_1(&"RSA key pair generated successfully".into());
Ok(())
}
pub async fn export_public_key(&self) -> Result<Vec<u8>> {
if let Some(ref key_pair) = self.our_key_pair {
let public_key = key_pair.public_key();
let export_promise = self.subtle_crypto.export_key("spki", &public_key)
.map_err(|e| anyhow::anyhow!("Failed to initiate key export: {:?}", e))?;
let exported_result = JsFuture::from(export_promise).await
.map_err(|e| anyhow::anyhow!("Key export failed: {:?}", e))?;
let array_buffer = exported_result.dyn_into::<js_sys::ArrayBuffer>()
.map_err(|e| anyhow::anyhow!("Failed to cast exported key: {:?}", e))?;
let uint8_array = Uint8Array::new(&array_buffer);
let mut key_bytes = vec![0u8; uint8_array.length() as usize];
uint8_array.copy_to(&mut key_bytes);
web_sys::console::log_1(&format!("Exported public key ({} bytes)", key_bytes.len()).into());
Ok(key_bytes)
} else {
Err(anyhow::anyhow!("No key pair available for export"))
}
}
pub async fn import_peer_public_key(&mut self, peer_id: String, key_data: &[u8]) -> Result<()> {
web_sys::console::log_1(&format!("Importing public key for peer: {}", peer_id).into());
let algorithm = Object::new();
js_sys::Reflect::set(&algorithm, &"name".into(), &"RSA-OAEP".into())
.map_err(|e| anyhow::anyhow!("Failed to set algorithm name: {:?}", e))?;
js_sys::Reflect::set(&algorithm, &"hash".into(), &"SHA-256".into())
.map_err(|e| anyhow::anyhow!("Failed to set hash algorithm: {:?}", e))?;
let key_usages = Array::new();
key_usages.push(&"encrypt".into());
let uint8_array = Uint8Array::from(key_data);
let array_buffer = uint8_array.buffer();
let import_promise = self.subtle_crypto.import_key_with_object(
"spki",
&array_buffer,
&algorithm,
false, &key_usages,
).map_err(|e| anyhow::anyhow!("Failed to initiate key import: {:?}", e))?;
let imported_result = JsFuture::from(import_promise).await
.map_err(|e| anyhow::anyhow!("Key import failed: {:?}", e))?;
let public_key = imported_result.dyn_into::<CryptoKey>()
.map_err(|e| anyhow::anyhow!("Failed to cast imported key: {:?}", e))?;
self.peer_public_keys.insert(peer_id.clone(), public_key);
web_sys::console::log_1(&format!("Successfully imported public key for {}", peer_id).into());
Ok(())
}
pub async fn encrypt_for_peer(&self, peer_id: &str, message: &[u8]) -> Result<Vec<u8>> {
if let Some(public_key) = self.peer_public_keys.get(peer_id) {
web_sys::console::log_1(&format!("Encrypting message for peer: {}", peer_id).into());
let algorithm = Object::new();
js_sys::Reflect::set(&algorithm, &"name".into(), &"RSA-OAEP".into())
.map_err(|e| anyhow::anyhow!("Failed to set algorithm name: {:?}", e))?;
let uint8_array = Uint8Array::from(message);
let array_buffer = uint8_array.buffer();
let encrypt_promise = self.subtle_crypto.encrypt_with_object_and_buffer_source(
&algorithm,
public_key,
&array_buffer,
).map_err(|e| anyhow::anyhow!("Failed to initiate encryption: {:?}", e))?;
let encrypted_result = JsFuture::from(encrypt_promise).await
.map_err(|e| anyhow::anyhow!("Encryption failed: {:?}", e))?;
let encrypted_buffer = encrypted_result.dyn_into::<js_sys::ArrayBuffer>()
.map_err(|e| anyhow::anyhow!("Failed to cast encrypted data: {:?}", e))?;
let encrypted_array = Uint8Array::new(&encrypted_buffer);
let mut encrypted_bytes = vec![0u8; encrypted_array.length() as usize];
encrypted_array.copy_to(&mut encrypted_bytes);
web_sys::console::log_1(&format!("Encrypted {} bytes for {}", encrypted_bytes.len(), peer_id).into());
Ok(encrypted_bytes)
} else {
Err(anyhow::anyhow!("No public key available for peer: {}", peer_id))
}
}
pub async fn decrypt_message(&self, encrypted_data: &[u8]) -> Result<Vec<u8>> {
if let Some(ref key_pair) = self.our_key_pair {
web_sys::console::log_1(&"Decrypting received message...".into());
let private_key = key_pair.private_key();
let algorithm = Object::new();
js_sys::Reflect::set(&algorithm, &"name".into(), &"RSA-OAEP".into())
.map_err(|e| anyhow::anyhow!("Failed to set algorithm name: {:?}", e))?;
let uint8_array = Uint8Array::from(encrypted_data);
let array_buffer = uint8_array.buffer();
let decrypt_promise = self.subtle_crypto.decrypt_with_object_and_buffer_source(
&algorithm,
&private_key,
&array_buffer,
).map_err(|e| anyhow::anyhow!("Failed to initiate decryption: {:?}", e))?;
let decrypted_result = JsFuture::from(decrypt_promise).await
.map_err(|e| anyhow::anyhow!("Decryption failed: {:?}", e))?;
let decrypted_buffer = decrypted_result.dyn_into::<js_sys::ArrayBuffer>()
.map_err(|e| anyhow::anyhow!("Failed to cast decrypted data: {:?}", e))?;
let decrypted_array = Uint8Array::new(&decrypted_buffer);
let mut decrypted_bytes = vec![0u8; decrypted_array.length() as usize];
decrypted_array.copy_to(&mut decrypted_bytes);
web_sys::console::log_1(&format!("Decrypted {} bytes", decrypted_bytes.len()).into());
Ok(decrypted_bytes)
} else {
Err(anyhow::anyhow!("No private key available for decryption"))
}
}
pub async fn hash_data(&self, data: &[u8]) -> Result<Vec<u8>> {
let uint8_array = Uint8Array::from(data);
let array_buffer = uint8_array.buffer();
let hash_promise = self.subtle_crypto.digest_with_str_and_buffer_source(
"SHA-256",
&array_buffer,
).map_err(|e| anyhow::anyhow!("Failed to initiate hashing: {:?}", e))?;
let hash_result = JsFuture::from(hash_promise).await
.map_err(|e| anyhow::anyhow!("Hashing failed: {:?}", e))?;
let hash_buffer = hash_result.dyn_into::<js_sys::ArrayBuffer>()
.map_err(|e| anyhow::anyhow!("Failed to cast hash result: {:?}", e))?;
let hash_array = Uint8Array::new(&hash_buffer);
let mut hash_bytes = vec![0u8; hash_array.length() as usize];
hash_array.copy_to(&mut hash_bytes);
Ok(hash_bytes)
}
pub fn generate_random_bytes(&self, length: usize) -> Result<Vec<u8>> {
let mut bytes = vec![0u8; length];
let uint8_array = Uint8Array::from(&mut bytes[..]);
let window = window().ok_or_else(|| anyhow::anyhow!("No window object"))?;
let crypto = window.crypto().map_err(|e| anyhow::anyhow!("No crypto object: {:?}", e))?;
crypto.get_random_values_with_u8_array(&uint8_array)
.map_err(|e| anyhow::anyhow!("Failed to generate random bytes: {:?}", e))?;
uint8_array.copy_to(&mut bytes);
Ok(bytes)
}
pub async fn generate_aes_key(&self) -> Result<CryptoKey> {
web_sys::console::log_1(&"Generating AES key...".into());
let algorithm = Object::new();
js_sys::Reflect::set(&algorithm, &"name".into(), &"AES-GCM".into())
.map_err(|e| anyhow::anyhow!("Failed to set algorithm name: {:?}", e))?;
js_sys::Reflect::set(&algorithm, &"length".into(), &256u32.into())
.map_err(|e| anyhow::anyhow!("Failed to set key length: {:?}", e))?;
let key_usages = Array::new();
key_usages.push(&"encrypt".into());
key_usages.push(&"decrypt".into());
let key_promise = self.subtle_crypto.generate_key_with_object(
&algorithm,
false, &key_usages,
).map_err(|e| anyhow::anyhow!("Failed to initiate AES key generation: {:?}", e))?;
let key_result = JsFuture::from(key_promise).await
.map_err(|e| anyhow::anyhow!("AES key generation failed: {:?}", e))?;
let aes_key = key_result.dyn_into::<CryptoKey>()
.map_err(|e| anyhow::anyhow!("Failed to cast to CryptoKey: {:?}", e))?;
web_sys::console::log_1(&"AES key generated successfully".into());
Ok(aes_key)
}
pub fn has_key_pair(&self) -> bool {
self.our_key_pair.is_some()
}
pub fn peer_key_count(&self) -> usize {
self.peer_public_keys.len()
}
pub fn remove_peer_key(&mut self, peer_id: &str) -> bool {
self.peer_public_keys.remove(peer_id).is_some()
}
}
pub mod utils {
use super::*;
pub fn is_web_crypto_available() -> bool {
if let Some(window) = window() {
if let Ok(crypto) = window.crypto() {
js_sys::Reflect::has(&crypto, &"subtle".into()).unwrap_or(false)
} else {
false
}
} else {
false
}
}
pub fn get_supported_algorithms() -> Vec<String> {
let mut algorithms = Vec::new();
if is_web_crypto_available() {
algorithms.push("RSA-OAEP".to_string());
algorithms.push("AES-GCM".to_string());
algorithms.push("SHA-256".to_string());
algorithms.push("HMAC".to_string());
algorithms.push("PBKDF2".to_string());
}
algorithms
}
pub fn bytes_to_hex(bytes: &[u8]) -> String {
bytes.iter()
.map(|b| format!("{:02x}", b))
.collect()
}
pub fn hex_to_bytes(hex: &str) -> Result<Vec<u8>> {
if hex.len() % 2 != 0 {
return Err(anyhow::anyhow!("Hex string must have even length"));
}
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16))
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| anyhow::anyhow!("Invalid hex string: {}", e))
}
pub fn base64_encode(bytes: &[u8]) -> String {
let uint8_array = Uint8Array::from(bytes);
let window = window().expect("No window object");
let btoa = js_sys::Reflect::get(&window, &"btoa".into()).expect("No btoa function");
let btoa_fn = btoa.dyn_into::<js_sys::Function>().expect("btoa is not a function");
let binary_string = String::from_utf8_lossy(bytes);
let result = btoa_fn.call1(&window, &binary_string.into()).expect("btoa failed");
result.as_string().expect("btoa result is not a string")
}
pub fn base64_decode(encoded: &str) -> Result<Vec<u8>> {
let window = window().ok_or_else(|| anyhow::anyhow!("No window object"))?;
let atob = js_sys::Reflect::get(&window, &"atob".into())
.map_err(|_| anyhow::anyhow!("No atob function"))?;
let atob_fn = atob.dyn_into::<js_sys::Function>()
.map_err(|_| anyhow::anyhow!("atob is not a function"))?;
let result = atob_fn.call1(&window, &encoded.into())
.map_err(|e| anyhow::anyhow!("atob failed: {:?}", e))?;
let binary_string = result.as_string()
.ok_or_else(|| anyhow::anyhow!("atob result is not a string"))?;
Ok(binary_string.into_bytes())
}
}