use crate::{KeyAgreement, SecioError};
use futures::prelude::*;
use tetsy_send_wrapper::SendWrapper;
use std::{io, pin::Pin, task::Context, task::Poll};
use wasm_bindgen::prelude::*;
pub type AgreementPrivateKey = SendSyncHack<(JsValue, web_sys::SubtleCrypto)>;
pub struct SendSyncHack<T>(SendWrapper<T>);
impl<T> Future for SendSyncHack<T>
where T: Future + Unpin {
type Output = T::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
self.0.poll_unpin(cx)
}
}
pub fn generate_agreement(algorithm: KeyAgreement)
-> impl Future<Output = Result<(AgreementPrivateKey, Vec<u8>), SecioError>>
{
let future = async move {
let crypto = build_crypto_future().await?;
let key_pair = {
let obj = build_curve_obj(algorithm);
let usages = js_sys::Array::new();
usages.push(&JsValue::from_str("deriveKey"));
usages.push(&JsValue::from_str("deriveBits"));
let promise = crypto.generate_key_with_object(&obj, true, usages.as_ref())?;
wasm_bindgen_futures::JsFuture::from(promise).await?
};
let (private, public) = {
let private = js_sys::Reflect::get(&key_pair, &JsValue::from_str("privateKey"));
let public = js_sys::Reflect::get(&key_pair, &JsValue::from_str("publicKey"));
match (private, public) {
(Ok(pr), Ok(pu)) => (pr, pu),
(Err(err), _) => return Err(err),
(_, Err(err)) => return Err(err),
}
};
let public = {
let promise = crypto.export_key("raw", &public.into())?;
wasm_bindgen_futures::JsFuture::from(promise).await?
};
let public = js_sys::Uint8Array::new(&public);
let mut public_buf = vec![0; public.length() as usize];
public.copy_to(&mut public_buf);
Ok((SendSyncHack(SendWrapper::new((private, crypto))), public_buf))
};
let future = future
.map_err(|err| {
SecioError::IoError(io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))
});
SendSyncHack(SendWrapper::new(Box::pin(future)))
}
pub fn agree(algorithm: KeyAgreement, key: AgreementPrivateKey, other_public_key: &[u8], out_size: usize)
-> impl Future<Output = Result<Vec<u8>, SecioError>>
{
let other_public_key = {
let tmp_view = unsafe { js_sys::Uint8Array::view(other_public_key) };
js_sys::Uint8Array::new(tmp_view.as_ref())
};
let future = async move {
let (private_key, crypto) = key.0.take();
let public_key = {
let promise = crypto
.import_key_with_object(
"raw", &js_sys::Object::from(other_public_key.buffer()),
&build_curve_obj(algorithm), false, &js_sys::Array::new()
)?;
wasm_bindgen_futures::JsFuture::from(promise).await?
};
let bytes = {
let derive_params = build_curve_obj(algorithm);
let _ = js_sys::Reflect::set(derive_params.as_ref(), &JsValue::from_str("public"), &public_key);
let promise = crypto
.derive_bits_with_object(
&derive_params,
&web_sys::CryptoKey::from(private_key),
8 * out_size as u32
)?;
wasm_bindgen_futures::JsFuture::from(promise).await?
};
let bytes = js_sys::Uint8Array::new(&bytes);
let mut buf = vec![0; bytes.length() as usize];
bytes.copy_to(&mut buf);
Ok(buf)
};
let future = future
.map_err(|err: JsValue| {
SecioError::IoError(io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))
});
SendSyncHack(SendWrapper::new(Box::pin(future)))
}
async fn build_crypto_future() -> Result<web_sys::SubtleCrypto, JsValue> {
web_sys::window()
.ok_or_else(|| JsValue::from_str("Window object not available"))
.and_then(|window| window.crypto())
.map(|crypto| crypto.subtle())
}
fn build_curve_obj(algorithm: KeyAgreement) -> js_sys::Object {
let obj = js_sys::Object::new();
let _ = js_sys::Reflect::set(obj.as_ref(), &JsValue::from_str("name"), &JsValue::from_str("ECDH"));
let _ = js_sys::Reflect::set(obj.as_ref(), &JsValue::from_str("namedCurve"), &JsValue::from_str(match algorithm {
KeyAgreement::EcdhP256 => "P-256",
KeyAgreement::EcdhP384 => "P-384",
}));
obj
}