Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions rust/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@
//! surfaces it as a trap-shaped host error, the guest treats WASI random
//! as infallible.
//!
//! ## Class-D policy: ECDSA signing and the RSA private-key families are not compiled for wasm
//! ## Class-D policy: ECDSA signing and the RSA private-key families are withheld from wasm
//!
//! ECDSA signing handles a per-signature secret nonce whose timing leakage
//! is key-recovering, and RSA private-key operations leak key material
//! through timing unless constant-time end to end — class D in
//! polymorph-webcrypto-guest-provider's timing-channel classification. The
//! through timing unless constant-time end to end. The in-guest provider
//! withholds both under class D in its timing-channel classification —
//! for ECDSA, a conservative exclusion pending signing-path assurance
//! rather than a claim that constant-time ECDSA is infeasible in wasm
//! (see that crate's README for the distinction from Ed25519). The
//! load-bearing enforcement is the in-guest provider's world, which never
//! exports `ecdsa-sign`, the `rsa-sign` interfaces, or (with working
//! implementations) the RSA-OAEP operations: a composition that
Expand Down
7 changes: 2 additions & 5 deletions rust/core/src/sig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,8 @@ impl std::fmt::Debug for SigPublic {
}

/// The private key backing a [`SigningKeyMaterial`]. The ECDSA and RSA
/// arms exist only on non-wasm targets: ECDSA signing is class D
/// (per-signature secret nonce; small timing leaks are key-recovering),
/// and RSA private-key operations are class D outright (the Marvin attack
/// lineage), so their code is structurally absent from every wasm build
/// (see the crate doc).
/// arms exist only on non-wasm targets — both are withheld under class D
/// (see the crate doc for the policy and the enforcement mechanics).
enum SigPrivate {
Ed25519(ed25519_dalek::SigningKey),
#[cfg(not(target_family = "wasm"))]
Expand Down
31 changes: 23 additions & 8 deletions rust/guest-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ JIT), weighted by how **forgiving of failure** the construction is
| **A — structurally constant-time** | None beyond a correct compiler: no secret-dependent branches or memory indices, only add/xor/rotate. Nothing for a JIT to miscompile into a leak. | SHA-2, SHA-3, BLAKE2/3, HMAC, HKDF | Export freely. |
| **B — CT given a constant-time multiplier and benign lowering** | Constant-latency hardware multiply; JIT lowers select/cmov without branches. This is where the two-compiler problem lives. | GHASH, X25519/Ed25519 | Export with the CT-variant implementation; document. |
| **C — CT only via costly variants** | The *fast* implementation leaks (secret-indexed tables); a bitsliced/fixsliced variant is CT at a several-fold cost. | AES | Export **only** the CT variant. |
| **D — not realistically CT in portable wasm** | Heroic implementation effort with near-zero leak tolerance: bignum branches, secret-dependent allocation, catastrophic small leaks (nonce bits → key recovery; remote-exploitable history). | RSA private-key ops, ECDSA signing, classic DH | **Never exported by this provider.** |
| **D — withheld from this provider** | Known variable-time hazards in the available implementation, or signing-path assurance not established. ECDSA signing is a conservative policy exclusion, not an inherent constant-time limitation — see below. | RSA private-key ops, ECDSA signing, classic DH | **Never exported by this provider.** |

Class D is enforced structurally, not by documentation: this provider simply
does not export those algorithm interfaces, so a composition that needs them
**fails at `wac plug` time** rather than running quietly degraded — and one
level deeper, the shared `polymorph-webcrypto-core` compiles no ECDSA signing
code for wasm targets at all (`#[cfg(not(target_family = "wasm"))]`), so the
class-D code is absent from this component's binary, not merely unexported.
Choose a host-side provider for them.
**fails at `wac plug` time** rather than running quietly degraded. The shared
`polymorph-webcrypto-core` cfg-gates its own ECDSA and RSA private-key arms out
of wasm targets; RSA's signing backend is additionally dependency-gated out of the
wasm build entirely, while ECDSA's absence from the final `.wasm` rests on
dead-code elimination (see `rust/core/src/lib.rs`'s crate doc for the
distinction). Choose a host-side provider for ECDSA or RSA signing.

`just conformance-ct::class-d` is that enforcement's gate: it asserts that the
conformance signing guest, whose world imports `ecdsa-sign`, does not
Expand Down Expand Up @@ -120,11 +121,25 @@ what marks where secrets flow.
| ECDH P-256/P-384 (key agreement) | B | `p256`/`p384` (RustCrypto: complete Renes–Costello–Batina formulas, constant-time field and scalar arithmetic, no secret-dependent branches or indices; strict point validation at import) | Constant-latency integer multiply; JIT does not pathologically rewrite straight-line arithmetic. |
| SHA-2 digests (256/384/512) | exempt (secret-free) | `sha2` | The `digest` primitive is unkeyed — hashing public data carries no secret to leak. |
| Checked SHA-1 digests (`sha1-checked`) | exempt (secret-free) | `sha1-checked` (sha1dc counter-cryptanalysis; both postures) | Unkeyed, like SHA-2; the collision detection branches only on the input, which the digest kind treats as public. |
| Ed25519 (sign + verify) | B | `ed25519-dalek` (complete addition laws, no per-signature secret nonce, constant-time scalar arithmetic) | Constant-latency integer multiply; JIT does not pathologically rewrite straight-line arithmetic. |
| ECDSA P-256/P-384 (**verify only**) | exempt (secret-free) | `p256`/`p384` verification — public keys and public signatures | Signing is class D (per-signature secret nonce; small leaks are key-recovering) and its interface (`ecdsa-sign`) is **not exported**; compositions requiring it fail at `wac plug` time. |
| Ed25519 (sign + verify) | B | `ed25519-dalek` (complete addition laws, constant-time scalar arithmetic; a deterministic per-message secret nonce) | Constant-latency integer multiply; LLVM and the runtime preserve secret-independent control flow and memory access; `timing-lab/` does not currently probe Ed25519 signing. |
| ECDSA P-256/P-384 (**verify only**) | exempt (secret-free) | `p256`/`p384` verification — public keys and public signatures | Signing carries a per-signature secret nonce and is withheld under this provider's class-D policy; its interface (`ecdsa-sign`) is **not exported**, so compositions requiring it fail at `wac plug` time. |
| RSASSA-PKCS1-v1_5 / RSA-PSS (**verify only**) | exempt (secret-free) | `rsa` crate verification — public keys and public signatures | Signing and decryption are class D (per-message secrets and blinded private-key ops; the `rsa` crate's private-key operations additionally carry RUSTSEC-2023-0071, the Marvin timing sidechannel) — the RSA private-key interfaces (`rsassa-pkcs1-v15-sign`, `rsa-pss-sign`) are **not exported**. |
| RSA-OAEP (**neither half exported**) | D (decrypt); encrypt has no secret-free half | None — the `public-encryption` kind is exported with uninhabited key resources | Decryption is class D and the attack lineage's prime target (blinded private-key ops; the Marvin sidechannel, RUSTSEC-2023-0071). Encryption is *not* secret-free, unlike signature verification: the plaintext is the secret, and it transits general-purpose bignum arithmetic with no constant-time variant — so the kind has no exportable half at all. Exporting the kind makes compositions requiring `rsa-oaep-encrypt` or `rsa-oaep-decrypt` fail at `wac plug` time. |

Ed25519 and ECDSA signing both handle a per-message secret nonce scalar;
Ed25519's determinism (deriving it from a secret prefix and the message,
[RFC 8032, section 5.1.6](https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.6))
removes the need for fresh per-signature randomness, not the risk of
leaking that nonce through timing. The pinned P-256 and P-384 crates
already use fixed-width, constant-time-designed field and scalar
arithmetic, including for nonce-scalar inversion. ECDSA signing's
class-D policy is a conservative exclusion pending signing-path
assurance, not a measured safety difference from Ed25519: Ed25519's
class-B classification rests on its source design (complete addition
laws, constant-time scalar arithmetic) plus the stated compiler and
runtime assumptions, and neither classification claims a proven
end-to-end constant-time guarantee.

AES-GCM (fixsliced, class C + B) is the package's only AEAD, and in-guest
it is a heroic implementation working against the algorithm's nature: the
constant-time property rests on a costly bitsliced countermeasure rather
Expand Down
14 changes: 5 additions & 9 deletions rust/guest-provider/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1672,11 +1672,9 @@ options_resource! {
/// An exported `verifying-key`: public material bound to its algorithm
/// (and its curve, digest, or salt-length parameterization) at minting.
/// The ECDSA and RSA arms exist for *verification only* — secret-free, so
/// exempt from the timing-channel classes; ECDSA signing is class D, its
/// interface is not exported, and the shared core compiles no ECDSA
/// signing code for wasm targets. The RSA private-key interfaces
/// (`rsassa-pkcs1-v15-sign`, `rsa-pss-sign`, `rsa-oaep-decrypt`) are
/// likewise class D and not exported.
/// exempt from the timing-channel classes. ECDSA signing and RSA
/// private-key interfaces are withheld under class D (see this crate's
/// README for the policy).
pub struct VerifyingKey {
public: SigPublic,
}
Expand Down Expand Up @@ -1721,10 +1719,8 @@ impl GuestVerifyingKey for VerifyingKey {
}

/// An exported `signing-key`: the shared core's signing-key material. On
/// this wasm target the core mints only Ed25519 signing keys
/// (constant-time by construction); ECDSA signing is class D, its
/// interface is not exported, and the core compiles no ECDSA signing code
/// for wasm targets.
/// this wasm target the core mints only Ed25519 signing keys (see this
/// crate's README for the timing-channel policy).
pub struct SigningKey {
material: SigningKeyMaterial,
}
Expand Down
16 changes: 8 additions & 8 deletions wit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,14 @@ NOT include any of it.

## Timing-channel policy

Some algorithms leak key material through execution timing when the
implementation shares a timing domain with an observer. In particular,
ECDSA signing handles a per-signature secret nonce whose timing leakage is
key-recovering. Providers that execute inside an attacker-observable timing
domain should not export such interfaces; a composition that requires one
then fails at composition (`wac plug`) time rather than at run time. This
repository's in-guest provider documents its classification and policy in
`rust/guest-provider/README.md`.
Execution timing can reveal secret material when an implementation shares
a timing domain with an observer. Both Ed25519 and ECDSA signing use secret
nonce scalars; deterministic generation does not remove their secrecy
requirement. Resistance to timing leakage depends on the implementation,
compiler, runtime, and hardware, not the algorithm alone. Providers should
withhold interfaces that cannot meet their deployment's timing requirements.
This repository's in-guest provider documents its classification and
composition-time enforcement in `rust/guest-provider/README.md`.

## Portability contract

Expand Down
9 changes: 4 additions & 5 deletions wit/ecdsa.wit
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,10 @@ interface ecdsa-verify {
/// ECDSA signing-key minting (FIPS 186-5).
///
/// Security:
/// - ECDSA signing handles a per-signature secret nonce whose timing
/// leakage is key-recovering. Providers in attacker-observable timing
/// domains do not export this interface, and compositions requiring it
/// then fail at composition time (see `README.md`, "Timing-channel
/// policy"). Host-backed providers serve it.
/// - ECDSA signing handles a per-signature secret nonce. Leakage of that
/// nonce can expose the signing key. Providers must assess their
/// implementation against the deployment's timing requirements (see
/// `README.md`, "Timing-channel policy").
///
/// Signing keys import as PKCS#8 or an EC private JWK — the platform
/// pass-through formats — and never as bare scalars (no platform door;
Expand Down
13 changes: 10 additions & 3 deletions wit/ed25519.wit
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,16 @@ interface ed25519-verify {
/// Ed25519 signing-key minting (RFC 8032).
///
/// Split from `ed25519-verify` so a provider can serve verification alone.
/// Ed25519 signing is constant-time by construction (no per-signature
/// secret nonce, complete addition laws), so providers in shared timing
/// domains can serve it (see `README.md`, "Timing-channel policy").
///
/// Security:
/// - Signing derives a per-message secret nonce scalar deterministically
/// from a secret prefix and the message
/// ([RFC 8032, section 5.1.6](https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.6)).
/// Determinism removes the need for fresh per-signature randomness; it
/// does not protect the nonce from timing leakage, which can expose the
/// signing key. Providers must assess their implementation against the
/// deployment's timing requirements (see `README.md`, "Timing-channel
/// policy").
///
/// Signing keys import as PKCS#8 or an OKP private JWK — the platform
/// pass-through formats — and never as a bare seed (see `README.md`,
Expand Down
Loading