Rust - Sequoia OpenPGP with Botan backend - wasm-bindgen fails

I'm trying to check if a Rust application making use of cryptography (i.e. Sequoia OpenPGP using crypto-botan as backend) can be turned into WebAssembly to be used in a frontend application.

This is my trivial lib.rs file, where I'm just trying to expose a function to return a public key:

use sequoia_openpgp::{cert::{CertBuilder, CipherSuite}, serialize::MarshalInto};
use wasm_bindgen::prelude::wasm_bindgen;

#[wasm_bindgen]
pub fn generate_certificate(email: &str) -> String {
    let certificate = CertBuilder::general_purpose(CipherSuite::RSA4k, Some(email))
        .generate()
        .unwrap()
        .0;
    let armored = &certificate.armored();
    String::from_utf8(armored.to_vec().unwrap()).unwrap()
}

And this is my Cargo.toml

[package]
name = "wasmtest"
version = "0.1.0"
edition = "2021"
resolver = "2"

[dependencies]
sequoia-openpgp = { version = "1.21", default-features = false, features = [
    "crypto-botan",
] }
wasm-bindgen = "0.2.92"

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

[build-dependencies]
num_cpus = "1.16.0"
pkg-config = "0.3.30"

Since Sequoia needs Botan as backend my project uses it as a git submodule (using version 3.5.0 as of now) and builds it in build.rs as follows:

use core::panic;
use std::{cmp::max, env, path::Path, process::Command};

fn main() {
    env::set_var("RUST_BACKTRACE", "full");
    build_botan();
}

fn build_botan() {
    println!("cargo:rerun-if-changed=./botan/src/");
    println!("cargo:rerun-if-changed=./botan/configure.py");

    let out_dir = env::var("OUT_DIR").unwrap();
    let botan_path = Path::new(&out_dir).join("botan_output");
    let lib_path = botan_path.join("lib");
    println!(
        "cargo:rustc-link-search=native={}",
        lib_path.to_string_lossy()
    );
    configure_botan(&botan_path);

    let num_jobs = max(num_cpus::get() - 2, 1);
    make_botan(num_jobs);
    make_install_botan();
}

fn configure_botan(botan_path: &Path) {
    let mut command = Command::new("python3");
    let botan_prefix = botan_path.to_string_lossy().into_owned();

    // Prepare configuration arguments
    let mut config_args = vec![
        "configure.py".to_string(),
        // // wasm
        // "--cc=emcc".to_string(),
        "--cpu=wasm".to_string(),
        "--os=emscripten".to_string(),
        "--prefix".to_string(),
        botan_prefix,
    ];

    command.args(&config_args);

    let status = command.current_dir("./botan").status().unwrap();
    if !status.success() {
        panic!();
    }
}

fn make_botan(num_jobs: usize) {
    #[cfg(unix)]
    let status = Command::new("make")
        .args(["--directory", "./botan", "-j", &num_jobs.to_string()])
        .status()
        .unwrap();

    if !status.success() {
        panic!();
    }
}

fn make_install_botan() {
    #[cfg(unix)]
    let status = Command::new("make")
        .args(["--directory", "./botan", "install"])
        .status()
        .unwrap();

    if !status.success() {
        panic!();
    }
}

In addition I also configured the default target in .cargo/config.toml as:

[build]
target = "wasm32-unknown-unknown"

If I try to run wasm-pack build --target web the build finishes correctly. But then when trying out in a HTML page:

<!DOCTYPE html> 
<html>
<head>
    <title>Home</title>
    <body>
        <h1>Wasm Test</h1>
        <span id="result"></span>
    </body>
    <script type="module">
        import init, {add, generate_certificate} from './pkg/wasmtest.js';
        await init();
        let result = generate_certificate("[email protected]");
        document.getElementById("result").innerText = result;

    </script>   
</html>

The console shows error: Uncaught TypeError: Failed to resolve module specifier "env". I've been able to pinpoint this thanks to this comment which clearly identifies the culprit as Botan since there are a number of entries such as (import "env" "botan_mp_init" (func (;4;) (type 6))) in the wat file. As far as I understood this means that Botan functions are undefined (i.e. could not be bound to the wasm somehow). This led me to try and modify the build.rs so that Botan is built for wasm. Following the instructions in the Botan handbook I changed the option passed to the configure.py script:

let mut config_args = vec![
        "configure.py".to_string(),
        "--cpu=wasm".to_string(),
        "--os=emscripten".to_string(),
        "--prefix".to_string(),
        botan_prefix,
    ];

However this makes me wonder whether Botan can be build for target wasm32-unknown-unknown. Running again wasm-pack build --target web fails this time with error:

thread 'main' panicked at /Users/antonioumbertoaramini/.cargo/registry/src/index.crates.io-6f17d22bba15001f/wasm-bindgen-wasm-interpreter-0.2.92/src/lib.rs:266:22:
non-i32 constant
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: Running the wasm-bindgen CLI
Caused by: Running the wasm-bindgen CLI
Caused by: failed to execute `wasm-bindgen`: exited with exit status: 101
full command: "/Users/antonioumbertoaramini/.cargo/bin/wasm-bindgen" "/Users/antonioumbertoaramini/Documents/Projects/wasmtest/target/wasm32-unknown-unknown/release/wasmtest.wasm" "--out-dir" "/Users/antonioumbertoaramini/Documents/Projects/wasmtest/pkg" "--typescript" "--target" "web"

which is really not saying much on what went wrong.

Any clue or hints to solve this would be welcome.

Upvotes: 3

Views: 47

Answers (0)

Related Questions