Skip to main content

wowlab_wasm/
crypto.rs

1// #t(file: rust_duplicate_strings) Protocol goldens cannot share constants across this crate dependency boundary.
2
3use std::{error::Error, fmt};
4
5use wasm_bindgen::prelude::*;
6use wowlab_parsers::{
7    CryptoError, NodeKeypair, build_sign_message, keypair_from_base64, sha256_hex,
8    verify_signature_base64,
9};
10use wowlab_types::wasm::{WasmCommonError, js_set};
11
12/// Node-authentication crypto failure converted at the JavaScript boundary.
13#[derive(Debug)]
14#[non_exhaustive]
15pub struct WasmCryptoError {
16    source: CryptoError,
17}
18
19impl fmt::Display for WasmCryptoError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        fmt::Display::fmt(&self.source, f)
22    }
23}
24
25impl Error for WasmCryptoError {
26    fn source(&self) -> Option<&(dyn Error + 'static)> {
27        Some(&self.source)
28    }
29}
30
31impl From<CryptoError> for WasmCryptoError {
32    fn from(source: CryptoError) -> Self {
33        Self { source }
34    }
35}
36
37impl From<WasmCryptoError> for JsValue {
38    fn from(error: WasmCryptoError) -> Self {
39        let js_error = js_sys::Error::new(&error.to_string());
40
41        js_error.set_name("DecodeError");
42
43        js_error.into()
44    }
45}
46
47/// Generate a new node keypair. Returns `{ privateKey, publicKey }`.
48#[wasm_bindgen(js_name = generateNodeKeypair)]
49pub fn wasm_generate_node_keypair() -> Result<JsValue, WasmCommonError> {
50    let keypair = NodeKeypair::generate();
51    let obj = js_sys::Object::new();
52
53    js_set(
54        &obj,
55        "privateKey",
56        &JsValue::from_str(&keypair.private_key_base64()),
57    );
58    js_set(
59        &obj,
60        "publicKey",
61        &JsValue::from_str(&keypair.public_key_base64()),
62    );
63
64    Ok(obj.into())
65}
66
67/// Sign a message with a base64-encoded private key. Returns signature as base64.
68#[wasm_bindgen(js_name = signMessage)]
69pub fn wasm_sign_message(
70    private_key_base64: &str,
71    message: &str,
72) -> Result<String, WasmCryptoError> {
73    let keypair = keypair_from_base64(private_key_base64)?;
74
75    Ok(keypair.sign_base64(message.as_bytes()))
76}
77
78#[wasm_bindgen(js_name = verifySignature)]
79pub fn wasm_verify_signature(
80    public_key_base64: &str,
81    message: &str,
82    signature_base64: &str,
83) -> Result<bool, WasmCryptoError> {
84    match verify_signature_base64(public_key_base64, message.as_bytes(), signature_base64) {
85        Ok(()) => Ok(true),
86        Err(error) if error.is_verification_failed() => Ok(false),
87        Err(error) => Err(error.into()),
88    }
89}
90
91#[wasm_bindgen(js_name = buildSignMessage)]
92#[must_use]
93pub fn wasm_build_sign_message(
94    timestamp: u64,
95    method: &str,
96    host: &str,
97    path: &str,
98    body: &str,
99) -> String {
100    build_sign_message(timestamp, method, host, path, body.as_bytes())
101}
102
103#[wasm_bindgen(js_name = buildSignMessageBytes)]
104#[must_use]
105pub fn wasm_build_sign_message_bytes(
106    timestamp: u64,
107    method: &str,
108    host: &str,
109    path: &str,
110    body: &[u8],
111) -> String {
112    build_sign_message(timestamp, method, host, path, body)
113}
114
115#[wasm_bindgen(js_name = sha256Hex)]
116#[must_use]
117pub fn wasm_sha256_hex(data: &str) -> String {
118    sha256_hex(data.as_bytes())
119}
120
121#[cfg(test)]
122mod tests {
123    use std::error::Error as _;
124
125    use googletest::prelude::*;
126
127    use super::*;
128
129    #[gtest]
130    // #t(fn: rust_duplicate_strings) This protocol golden cannot share a constant across the parser and Wasm dependency boundary.
131    fn crypto_error_retains_parsers_and_base64_sources_until_javascript_conversion() -> Result<()> {
132        let error = wasm_sign_message("!!!", "message").err().or_fail()?;
133
134        let crypto_source = error.source().or_fail()?;
135
136        verify_true!(crypto_source.is::<CryptoError>())?;
137        let base64_source = crypto_source.source().or_fail()?;
138
139        verify_that!(
140            base64_source.to_string(),
141            eq("Invalid symbol 33, offset 0.")
142        )?;
143
144        verify_that!(
145            error.to_string(),
146            // #t(rust_duplicate_strings) This protocol golden is repeated across crates that cannot share a constant without reversing dependencies.
147            eq("invalid private key base64: Invalid symbol 33, offset 0.")
148        )
149    }
150}