leotaku / web-push-native

Pure rust implementation of web-push and related standards
Apache License 2.0
6 stars 1 forks source link

Trying to compile for webassembly #9

Open kryptoniancode opened 8 months ago

kryptoniancode commented 8 months ago

I have created a hello_wasm project using https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm

This is Cargo.toml

[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"
rust-crypto-wasm = "0.3.1"
aes-gcm = "0.10.3"
getrandom = { version = "0.2", features = ["js"] }
ece-native = "0.4.0"
once_cell = "1.19.0"
base64ct = { version = "1.6.0", features = ["alloc", "std"] }
web-push-native = "0.4.0"

This is html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="module">
        import init, { test_encryption_decryption_with_serialization } from "./pkg/hello_wasm.js";
        init().then(() => {
            test_encryption_decryption_with_serialization();
        });
    </script>
</head>

<body>

</body>

</html>

This is src/lib.rs

extern crate crypto;

use crypto::{digest::Digest, sha2::Sha256};
use wasm_bindgen::prelude::*;

use base64ct::{Base64UrlUnpadded, Encoding};

use web_push_native::{
    decrypt, encrypt,
    jwt_simple::{self, reexports::serde_json, algorithms::ES256KeyPair},
    p256::{self, PublicKey}, Auth, WebPushBuilder,
};

use web_push_native::p256::elliptic_curve::sec1::ToEncodedPoint;

#[wasm_bindgen]
pub fn test_encryption_decryption_with_serialization() {
    // Placeholders for variables provided by individual clients. In most cases,
    // these will be retrieved in-browser by calling `pushManager.subscribe` on
    // a service worker registration object.
    const ENDPOINT: &str = "";
    const P256DH: &str = "";
    const AUTH: &str = "";

    // Placeholder for your private VAPID key. Keep this private and out of your
    // source tree in real projects!
    const VAPID: &str = "";

    let key_pair = ES256KeyPair::from_bytes(&Base64UrlUnpadded::decode_vec(VAPID).unwrap()).unwrap();
    let builder = WebPushBuilder::new(
        ENDPOINT.parse().unwrap(),
        PublicKey::from_sec1_bytes(&Base64UrlUnpadded::decode_vec(P256DH).unwrap()).unwrap(),
        Auth::clone_from_slice(&Base64UrlUnpadded::decode_vec(AUTH).unwrap()),
    )
    .with_vapid(&key_pair, "mailto:john.doe@example.com");

    let len = builder.build("content");   
}

compile, run and open in browser

wasm-pack build --target web --debug
python -m http.server

error in console log in chrome dev tools

Uncaught (in promise) RuntimeError: unreachable
    at __rust_start_panic (hello_wasm_bg.wasm:0x17ab2b)
    at rust_panic (hello_wasm_bg.wasm:0x17a7bc)
    at std::panicking::rust_panic_with_hook::h62090d3fc0630473 (hello_wasm_bg.wasm:0x13ddaa)
    at std::panicking::begin_panic_handler::{{closure}}::h823c80ef219a166d (hello_wasm_bg.wasm:0x14d820)
    at std::sys_common::backtrace::__rust_end_short_backtrace::hcc7222b556723586 (hello_wasm_bg.wasm:0x17aad1)
    at rust_begin_unwind (hello_wasm_bg.wasm:0x170a9f)
    at core::panicking::panic_fmt::hd79411a297d06dc8 (hello_wasm_bg.wasm:0x174955)
    at core::panicking::assert_failed_inner::h003d4b5b03930769 (hello_wasm_bg.wasm:0x110a73)
    at core::panicking::assert_failed::h985a4a1c1b7563ab (hello_wasm_bg.wasm:0x160a2b)
    at <&generic_array::GenericArray<T,N> as core::convert::From<&[T]>>::from::h68a9a2e49c3eb98d (hello_wasm_bg.wasm:0x1344df)
leotaku commented 8 months ago

Hi! I am personally not a Rust WASM expert, but from what I can tell this is simply a result of you not providing values for the placeholder variables in the example. You can read the more fully-fledged example at ./example to find how to populate them correctly.

Also, I don't know what you are trying to do exactly, but usually one would use the Push Web API in the browser and a library like this on the server. Compiling this library to WASM should not be necessary most of the time.

kryptoniancode commented 8 months ago

Thanks for reply. I want to use this for getting request header and body parts. I have tried many implementation nodejs, other c project ompiled using emiscripten and none of those working.

I have tried your library and it works for this example.

use aes_gcm::{
    aead::{Aead, AeadCore, KeyInit, OsRng, rand_core::RngCore},
    Aes256Gcm,
    Key, // Or `Aes128Gcm`
};

#[wasm_bindgen]
extern "C" {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    // alert(&format!("Hello, {}!", name));
}

#[wasm_bindgen]
pub fn test_crypto(str: &str) {
    let mut sha = Sha256::new();
    sha.input_str(str);
    // alert(&format!("Sha256, {}!", sha.result_str()));
}

#[wasm_bindgen]
pub fn aes_gcm_128_encrypt() {
    // The encryption key can be generated randomly:
    let key = Aes256Gcm::generate_key(OsRng);

    // Transformed from a byte array:
    let key: &[u8; 32] = &[42; 32];
    let key: &Key<Aes256Gcm> = key.into();

    // Note that you can get byte array from slice using the `TryInto` trait:
    let key: &[u8] = &[42; 32];
    let key: [u8; 32] = key.try_into().unwrap();

    // Alternatively, the key can be transformed directly from a byte slice
    // (panicks on length mismatch):
    let key = Key::<Aes256Gcm>::from_slice(&key);

    let cipher = Aes256Gcm::new(&key);
    let nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96-bits; unique per message
    let ciphertext = cipher
        .encrypt(&nonce, b"plaintext message".as_ref())
        .unwrap();
    // alert(&format!(
    //     "encrypted, {:?}!",
    //     str::from_utf8(ciphertext.as_ref())
    // ));
    let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref()).unwrap();
    // alert(&format!("decrypted, {:?}!", str::from_utf8(&plaintext)));
}

So, I want to use your library to get encrypted and send using fetch in browser. It is very convenient without using any server. Just running a static a web page like github pages, fetch subscription data and use your library to encrypt message and then send to subsriber using fetch.

If possible can you create a library just for get request header with options and encrypted data.

kryptoniancode commented 8 months ago

My hypothesis in above error is that plaintext encrypted is not able stored because of limited space, may be?

leotaku commented 8 months ago

Hmmm, so you want to use the Web Push API entirely on the client side, without a server? This seems unnecessary and not like what the API is designed to do. If you just want to send desktop notifications from a static web page, maybe instead use the Notifications API.

Maybe I'm misunderstanding what you want to achieve. Do you want to send push messages from one web browser to a different one? That's currently the only use-case I can think of where this approach might make sense.

kryptoniancode commented 8 months ago

Do you want to send push messages from one web browser to a different one?

No

I have created a static blog website and I have used google sheet to get subscription data on permission granted. Currently not planning to get hosting service. I will fetch the subscription data from google sheet and use my other "private" static page to send notification any user on new blog post.

leotaku commented 8 months ago

You do understand that you will have to store the notification endpoint, secret, etc. for every user you want to send a Web Push notification to, yes? It is unclear to me how you intend to achieve this without a server.

leotaku commented 8 months ago

Ok, if you simply want to use my library to generate the encrypted message to send to the subscription endpoint, I would recommend you use the web_push_native::encrypt function. You only need to give it the message, user public-key and user auth and it should return the encrypted message.

kryptoniancode commented 8 months ago

Thanks for reply. I will try and update.