use wasm_bindgen::prelude::*;
use aes::{
self,
cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit},
};
use base64::{engine::general_purpose::STANDARD as b64, Engine};
fn string_keys_to_shared_secret(pub_key: &str, priv_key: &str) -> Result<[u8; 32], String> {
let pub_key_as_bytes: [u8; 32] = match match b64.decode(pub_key) {
Ok(k) => k,
Err(_) => return Err("failed to decode pub key".to_string()),
}
.try_into()
{
Ok(k) => k,
Err(_) => return Err("failed to convert decoded pub key to fixed bytes".to_string()),
};
let priv_key_as_bytes: [u8; 32] = match match b64.decode(priv_key) {
Ok(k) => k,
Err(_) => return Err("failed to decode priv key".to_string()),
}
.try_into()
{
Ok(k) => k,
Err(_) => return Err("failed to convert decoded priv key to fixed bytes".to_string()),
};
let pub_key = x25519_dalek::PublicKey::from(pub_key_as_bytes);
let priv_key = x25519_dalek::StaticSecret::from(priv_key_as_bytes);
Ok(priv_key.diffie_hellman(&pub_key).to_bytes())
}
#[wasm_bindgen]
pub fn extract_key_from_bio(bio: String) -> Option<String> {
let mut key: String = "".to_string();
let mut bio_chars = bio.chars();
for (i, window) in bio_chars.clone().collect::<Vec<_>>().windows(3).enumerate() {
if window == &['🐘', '🔑', ':'] {
let mut j = i + 3;
if let Some(first) = bio_chars.nth(j) {
key.push(first);
while let Some(c) = bio_chars.next() {
if j < i + 46 {
key.push(c);
j += 1;
} else {
break;
}
}
} else {
break;
}
}
}
if key.len() == 44 {
return Some(key);
}
None
}
#[wasm_bindgen]
pub struct Keys {
public: String,
private: String,
}
#[wasm_bindgen]
impl Keys {
#[wasm_bindgen(getter)]
pub fn public(&self) -> String {
self.public.clone()
}
#[wasm_bindgen(getter)]
pub fn private(&self) -> String {
self.private.clone()
}
}
#[wasm_bindgen]
pub fn generate_keys() -> Keys {
let priv_key = x25519_dalek::StaticSecret::random_from_rng(rand::rngs::OsRng);
let pub_key = x25519_dalek::PublicKey::from(&priv_key);
Keys {
public: b64.encode(pub_key.as_bytes()),
private: b64.encode(priv_key.as_bytes()),
}
}
#[wasm_bindgen]
pub fn encrypt(status: String, pub_key: String, priv_key: String) -> Result<String, String> {
let shared_secret = string_keys_to_shared_secret(&pub_key, &priv_key)?;
let cipher = aes::Aes256Enc::new(GenericArray::from_slice(&shared_secret));
let status_as_bytes = status.as_bytes();
let pad = (16 - (status_as_bytes.len() % 16)) as u8;
let mut encrypted = vec![];
for chunk in status_as_bytes.chunks(16) {
let mut block = [0u8; 16];
for (i, &v) in chunk.iter().enumerate() {
block[i] = v;
}
let mut block = GenericArray::from(block);
cipher.encrypt_block(&mut block);
encrypted.extend(block.as_slice());
}
encrypted.push(pad);
Ok(b64.encode(encrypted))
}
#[wasm_bindgen]
pub fn decrypt(status: String, pub_key: String, priv_key: String) -> Result<String, String> {
let shared_secret = string_keys_to_shared_secret(&pub_key, &priv_key)?;
let cipher = aes::Aes256Dec::new(GenericArray::from_slice(&shared_secret));
let mut status_as_bytes = match b64.decode(status) {
Ok(s) => s,
Err(_) => return Err("failed to decode base64 status".to_string()),
};
let pad = match status_as_bytes.pop() {
Some(p) => p,
None => return Err("decoded vec is empty".to_string()),
};
let mut decrypted = vec![];
for chunk in status_as_bytes.chunks(16) {
let mut block = [0u8; 16];
for (i, &v) in chunk.iter().enumerate() {
block[i] = v;
}
let mut block = GenericArray::from(block);
cipher.decrypt_block(&mut block);
decrypted.extend(block.as_slice());
}
if pad < 16 {
decrypted.truncate(decrypted.len() - (pad as usize));
}
Ok(String::from_utf8_lossy(&decrypted).to_string())
}