Skip to content

Repository files navigation

polymorph:tls

A TLS 1.3 WIT package and pure-wasm implementation, with a noq-proto compatibility layer serving QUIC embedders. A sibling of polymorph:webcrypto and polymorph:webrtc-datachannels, following the same architecture.

Status: spike implementation. The WIT package, the profile and its two deliveries, and the QUIC compatibility layer exist; they pass a cross-implementation conformance suite on the polymorph:test harness (one shared guest suite against every composed delivery, under Wasmtime and under Node/jco via JSPI — browser legs pending), cross-implementation interop against OpenSSL, Go crypto/tls, and quic-go over real transports, and a scheduled dudect-style timing lab, with the performance tradeoff measured in bench/. Upstreaming and the tls-virt follow-ons remain — see the issue tracker.

Why pure-wasm TLS 1.3 is plausible

Whether an algorithm is safe to run in wasm is a timing-channel question. This repository inherits the class A–D classification from component-webcrypto's in-guest provider — wasm makes no constant-time guarantee for any instruction, so algorithms are classed by how much their best software implementation must trust the machine below it, weighted by the blast radius of a small leak. TLS 1.3's secret-bearing surfaces walk through it as follows.

  • Key schedule and session machinery — HKDF, transcript hashing, the Finished MAC, session tickets, key update: class A (structurally constant-time).
  • Key exchange — X25519 or ECDH P-256, one ephemeral scalar multiplication per handshake: class B (constant-time given a constant-latency multiplier and benign JIT lowering).
  • Peer authentication — certificate-chain and CertificateVerify verification is secret-free, hence exempt regardless of the peer's algorithm. A pure-wasm TLS 1.3 client without client certificates never touches class D at all.
  • Packet protection — ChaCha20-Poly1305 is class A/B and the preferred suite; TLS_AES_128_GCM_SHA256 is mandatory-to-implement (RFC 8446 §9.1), so a conformant stack carries it as fixsliced, table-free AES (class C: constant-time only via the costly variant) and never prefers it. QUIC header protection follows the same split.
  • The one class-D hole — the endpoint's own CertificateVerify signature. ECDSA and RSA signing are class D, and the attack lineage (Brumley–Boneh, Minerva, TPM-FAIL) targets exactly this shape: repeated signatures under the same long-term key with attacker-observable timing, and the attacker initiates handshakes. Ed25519 signing is class B, so an Ed25519 identity key closes the hole in-guest — RFC 8446's mandatory-to-implement signature schemes constrain what an endpoint can verify, while the algorithm it signs with is determined by its own certificate, so an Ed25519-only endpoint remains conformant. No public CA issues Ed25519 certificates, so that posture requires a private PKI; WebPKI identities (ECDSA/RSA) require delegating the signature out of the guest instead.

Design

The central ruling: the algorithm profile is the primary artifact, and it has two deliveries.

A second ruling orders the goals: the primary goal is a generally-useful TLS interface and implementation. QUIC support is a real but secondary requirement, served at noq-proto's crypto seam: a separate compatibility layer, never part of the core library, for embedders that bring their own transport — component-iroh, the motivating consumer, drives noq-proto over its own wires. QUIC over wasi:sockets à la carte is not a deliverable: the in-repo driver exists to validate the stack under a WASI runtime (smoke, interop, bench, audit) and lives inside the rig that runs it.

  • The profile fixes the algorithm policy once: ChaCha20-Poly1305 preferred; fixsliced TLS_AES_128_GCM_SHA256 present for conformance, never preferred; X25519 and P-256 key exchange; the full mandatory-to-implement verification set (secret-free); signing is Ed25519 in-guest or delegated — class-D signing never runs in the guest.
  • The component is the profile's enforced delivery. Consumers get no algorithm configuration surface at all. The CertificateVerify signer is a world import — satisfiable by a host-side provider, left unwired for Ed25519-only deployments — so in-guest class-D signing is structurally unrepresentable, in the same way component-webcrypto's in-guest provider withholds class-D exports. The component imports wasi:clocks and wasi:random — the consumer brings the transport streams — and its export shape should stay swappable with an eventual host-terminated TLS provider (the wasi-tls proposal), so a composition chooses in-guest versus host TLS at wac plug time the way component-webcrypto compositions choose their crypto provider.
  • The Rust guest library is the profile's curated delivery. Same profile underneath, delivered as the ergonomic path for Rust guests. Its one near-airtight rule is API shape rather than configuration default: no constructor accepts class-D private key material; signing is only ever a caller-supplied trait object.

The implementation path is assembly, not invention: rustls with a pure- RustCrypto CryptoProvider at the core; for the QUIC leg, noq-proto (sans-IO QUIC) with the profile implementing its crypto traits, the embedder driving the I/O.

Performance posture

Measured, not estimated (just bench; methodology, caveats, and provenance-stamped numbers in bench/):

  • The AEAD gap is the wasm story. Fixsliced AES-128-GCM plus software GHASH runs roughly 25x off native hardware AES at QUIC packet sizes; ChaCha20-Poly1305 runs within about 2x of native and roughly 3x faster than AES inside wasm. The profile's ChaCha-first preference order is this measurement, applied. +simd128 improves ChaCha by a further ~15-20% and AES by single digits; it changes no ordering.
  • Componentization is cheap where it matters: pushing the record path through the composed component costs about a tenth of the in-guest library's throughput, against the multiples lost to being in wasm at all — the tradeoff that matters remains wasm versus native, not library versus component. Connection setup pays more (roughly 2.4x fewer handshakes/s than the in-guest library) from per-connection stream and resource churn.
  • QUIC pays per-packet AEAD and per-datagram I/O, with no kTLS/GSO offload reachable from wasi:sockets (datagram batching only): wasm loopback throughput lands within 2x of the same stack running natively, both well below the record path. Suitable for control planes and moderate throughput, not line rate.
  • Handshakes (Ed25519 identity) run at thousands per second in wasm, roughly 2x off native: connection setup is not the bottleneck for the deployment shapes this repository targets.

Threat model

The same frame as the sibling's in-guest provider. The host is already trusted — anyone running TLS in a wasm guest has conceded that the host can read all key material. The marginal adversaries are co-tenants and remote observers: do not deploy this where hostile co-tenancy is part of the threat model (wasm gives memory isolation, not microarchitectural isolation). Against remote observers, the profile keeps every secret-bearing operation at class ≤ B except the fixsliced AES-GCM conformance floor — and wasm's timing story is per-runtime-empirical, never guaranteed (the two-compiler problem: source-level constant-time discipline defeats LLVM, and the JIT is a second optimizer free to reintroduce what the source fought off), so empirical timing verification is part of the design, not an afterthought.

What the component buys over a linked TLS library is memory isolation for long-lived secrets: session keys and the identity key live in the TLS component's linear memory, unreachable from consumers — a memory-safety bug or malicious dependency in the application cannot exfiltrate them (Heartbleed is precisely the co-linked-TLS-library failure mode). Timing is the remaining channel; memory is closed.

About

TLS implementations for WebAssembly Components

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages