Skip to content

Commit d90d6d6

Browse files
committed
Add support for digital signatures
This adds a new cargo feature flag `digital-signatures` that brings support for signature verification, using the the current proposal for WebAssembly modules signatures. (https://github.com/WebAssembly/tool-conventions/blob/main/Signatures.md) No behavior changes unless the `--public-keys` option is used with the `run` command. This options accepts one or more public keys, that the entire module must be signed with in order to run.
1 parent 027dea5 commit d90d6d6

8 files changed

Lines changed: 176 additions & 7 deletions

File tree

.github/workflows/main.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,21 @@ jobs:
350350
env:
351351
RUST_BACKTRACE: 1
352352

353+
# Build and test digital signatures.
354+
test_digital_signatures:
355+
name: Test digital signatures
356+
runs-on: ubuntu-latest
357+
steps:
358+
- uses: actions/checkout@v2
359+
with:
360+
submodules: true
361+
- run: rustup target add wasm32-wasi
362+
- name: Install Rust
363+
run: rustup update stable && rustup default stable
364+
- run: ./ci/run-digital-signatures-test.sh
365+
env:
366+
RUST_BACKTRACE: 1
367+
353368
bench:
354369
name: Run benchmarks
355370
runs-on: ubuntu-latest

Cargo.lock

Lines changed: 71 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ humantime = "2.0.0"
4040
wasmparser = "0.82.0"
4141
lazy_static = "1.4.0"
4242
listenfd = "0.3.5"
43+
wasmsign2 = { version = "0.1.11", optional = true }
4344

4445
[target.'cfg(unix)'.dependencies]
4546
rustix = "0.33.0"
@@ -107,6 +108,7 @@ memfd = ["wasmtime/memfd"]
107108
pooling-allocator = ["wasmtime/pooling-allocator"]
108109
all-arch = ["wasmtime/all-arch"]
109110
posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]
111+
digital-signatures = ["wasmsign2", "wasmtime/digital-signatures"]
110112

111113
# Stub feature that does nothing, for Cargo-features compatibility: the new
112114
# backend is the default now.

ci/run-digital-signatures-test.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#! /bin/bash
2+
3+
set -e
4+
5+
TMP_DIR=$(mktemp -d -t ci-XXXXXXXXXX)
6+
pushd "$TMP_DIR"
7+
8+
WASMSIGN="${TMP_DIR}/bin/wasmsign2"
9+
WASM_MODULE="${TMP_DIR}/bin/test-module.wasm"
10+
PUBLIC_KEY="${TMP_DIR}/public.key"
11+
SECRET_KEY="${TMP_DIR}/secret.key"
12+
13+
cargo install --version 0.1.4 --root "$TMP_DIR" wasmsign2-cli
14+
"$WASMSIGN" keygen -K "$PUBLIC_KEY" -k "$SECRET_KEY"
15+
16+
rm -fr test-module
17+
cargo new test-module
18+
pushd test-module
19+
cargo install --root "$TMP_DIR" --path . --target=wasm32-wasi
20+
popd
21+
22+
popd
23+
24+
cargo run --features digital-signatures -- run "$WASM_MODULE"
25+
26+
if cargo run --features digital-signatures -- run --public-keys "$PUBLIC_KEY" "$WASM_MODULE"; then
27+
echo "Module ran even though signature verification failed" >&2
28+
exit 1
29+
fi
30+
31+
"$WASMSIGN" sign -k "$SECRET_KEY" -i "$WASM_MODULE" -o "$WASM_MODULE"
32+
cargo run --features digital-signatures -- run --public-keys "$PUBLIC_KEY" "$WASM_MODULE"
33+
34+
rm -fr "$TMP_DIR"

crates/wasmtime/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ rayon = { version = "1.0", optional = true }
3838
object = { version = "0.27", default-features = false, features = ['read_core', 'elf'] }
3939
async-trait = { version = "0.1.51", optional = true }
4040
once_cell = "1.9"
41+
wasmsign2 = { version = "0.1.11", optional = true }
4142

4243
[target.'cfg(target_os = "windows")'.dependencies]
4344
winapi = "0.3.7"
@@ -107,3 +108,6 @@ posix-signals-on-macos = ["wasmtime-runtime/posix-signals-on-macos"]
107108
# Enabling this feature has no effect on non-Linux platforms or when the `uffd`
108109
# feature is enabled.
109110
memfd = ["wasmtime-runtime/memfd"]
111+
112+
# Enables support for signatures verification
113+
digital-signatures = ["wasmsign2"]

crates/wasmtime/src/config.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub struct Config {
105105
pub(crate) parallel_compilation: bool,
106106
pub(crate) paged_memory_initialization: bool,
107107
pub(crate) memfd: bool,
108+
#[cfg(feature = "digital-signatures")]
109+
pub(crate) public_keys: Option<Arc<wasmsign2::PublicKeySet>>,
108110
}
109111

110112
impl Config {
@@ -131,6 +133,8 @@ impl Config {
131133
// Default to paged memory initialization when using uffd on linux
132134
paged_memory_initialization: cfg!(all(target_os = "linux", feature = "uffd")),
133135
memfd: false,
136+
#[cfg(feature = "digital-signatures")]
137+
public_keys: None,
134138
};
135139
#[cfg(compiler)]
136140
{
@@ -1063,6 +1067,13 @@ impl Config {
10631067
self
10641068
}
10651069

1070+
/// Configure the set of public keys used for digital signatures verification.
1071+
#[cfg(feature = "digital-signatures")]
1072+
pub fn public_keys(&mut self, public_keys: wasmsign2::PublicKeySet) -> &mut Self {
1073+
self.public_keys = Some(Arc::new(public_keys));
1074+
self
1075+
}
1076+
10661077
/// Configures the size, in bytes, of the extra virtual memory space
10671078
/// reserved after a "dynamic" memory for growing into.
10681079
///
@@ -1269,6 +1280,8 @@ impl Clone for Config {
12691280
parallel_compilation: self.parallel_compilation,
12701281
paged_memory_initialization: self.paged_memory_initialization,
12711282
memfd: self.memfd,
1283+
#[cfg(feature = "digital-signatures")]
1284+
public_keys: self.public_keys.clone(),
12721285
}
12731286
}
12741287
}

crates/wasmtime/src/module.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,12 @@ impl Module {
241241
#[cfg(compiler)]
242242
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
243243
pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Module> {
244-
match Self::new(
245-
engine,
246-
&fs::read(&file).with_context(|| "failed to read input file")?,
247-
) {
244+
let bytes = fs::read(&file).with_context(|| "failed to read input file")?;
245+
#[cfg(feature = "digital-signatures")]
246+
if let Some(public_key_set) = &engine.config().public_keys {
247+
public_key_set.verify(&mut std::io::Cursor::new(&bytes), None)?;
248+
}
249+
match Self::new(engine, &bytes) {
248250
Ok(m) => Ok(m),
249251
Err(e) => {
250252
cfg_if::cfg_if! {

src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ use wasmtime::{Config, ProfilingStrategy};
103103
#[cfg(feature = "pooling-allocator")]
104104
use wasmtime::{InstanceLimits, ModuleLimits, PoolingAllocationStrategy};
105105

106+
#[cfg(feature = "digital-signatures")]
107+
use wasmsign2::PublicKeySet;
108+
#[cfg(not(feature = "digital-signatures"))]
109+
struct PublicKeySet;
110+
106111
fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result<ProfilingStrategy> {
107112
Ok(match (jitdump, vtune) {
108113
(true, false) => ProfilingStrategy::JitDump,
@@ -258,6 +263,12 @@ struct CommonOptions {
258263
#[cfg(feature = "pooling-allocator")]
259264
#[structopt(long)]
260265
pooling_allocator: bool,
266+
267+
/// Require modules to have a embedded signature that can be verified
268+
/// with the given set of public keys.
269+
#[structopt(long, value_name = "FILE,FILE,...", parse(try_from_str = parse_public_keys))]
270+
#[allow(dead_code)]
271+
public_keys: Option<PublicKeySet>,
261272
}
262273

263274
impl CommonOptions {
@@ -350,6 +361,11 @@ impl CommonOptions {
350361
}
351362
}
352363

364+
#[cfg(feature = "digital-signatures")]
365+
if let Some(public_keys) = &self.public_keys {
366+
config.public_keys(public_keys.clone());
367+
}
368+
353369
Ok(config)
354370
}
355371

@@ -507,6 +523,21 @@ fn parse_wasi_modules(modules: &str) -> Result<WasiModules> {
507523
}
508524
}
509525

526+
#[cfg(feature = "digital-signatures")]
527+
fn parse_public_keys(pk_files: &str) -> Result<PublicKeySet> {
528+
let mut public_key_set = PublicKeySet::empty();
529+
for pk_file in pk_files.split(',') {
530+
let pk_file = pk_file.trim();
531+
public_key_set.insert_any_file(pk_file)?;
532+
}
533+
Ok(public_key_set)
534+
}
535+
536+
#[cfg(not(feature = "digital-signatures"))]
537+
fn parse_public_keys(_pk_files: &str) -> Result<PublicKeySet> {
538+
bail!("Digital signatures support is not enabled");
539+
}
540+
510541
/// Select which WASI modules are available at runtime for use by Wasm programs.
511542
#[derive(Debug, Clone, Copy, PartialEq)]
512543
pub struct WasiModules {

0 commit comments

Comments
 (0)