Recursive AI

The browser run

All onecrypto docs

OneCrypto's core package runs in the browser's .NET runtime (browser-wasm). That runtime offers hashes and HMAC (and, in the .NET 10 version tested, PBKDF2 over HMAC) but no AES, no RSA, no ECDSA or ECDH and no SHA-3. Parsing, inspecting and verifying have to work there anyway, through the verify-only managed fallbacks and our own curves; everything the runtime cannot do must fail with AlgorithmNotSupportedException naming the algorithm and platform, never with the runtime's own PlatformNotSupportedException. availability.md lists what each platform provides; its browser-wasm section is checked against the running browser.

What runs

tests/BrowserSuite is one browser-wasm app. Its cases print CASE <area> <id> <pass|fail|skip> <verdict> <detail> lines. The status says whether the case met its expectation. The verdict is the observed outcome in words that don't depend on the host, so the same case can be compared across hosts.

AreaWhat it checks
platformthe provider the dispatch picked (managed fallback in the browser), the runtime's own PBKDF2 and AES, hashes and HMAC; the availability section is compared with docs/availability.md
parse-fixtures, parse-limbo-certscertificates (generated fixtures, and every certificate of the sampled x509-limbo cases), a CRL, OCSP responses, a time-stamp request, PEM framing; the signature of each fixture certificate, CRL and OCSP response is verified
private-keys, load, sshPKCS#8, Crypto.Load on every fixture (PEM, DER, PKCS#12, OpenSSH), OpenSSH public keys to SubjectPublicKeyInfo
kdfPBKDF2-HMAC-SHA-256, scrypt and Argon2id against the RFC vectors
protected-inputswhich password-protected inputs open (see below)
verify-wycheproofRSA PKCS#1 v1.5 and PSS, ECDSA (P-256, P-384, P-521, brainpool, DER and P1363), Ed25519 and Ed448 on a sample of Wycheproof vectors
cmsCMS SignedData (RSA PKCS#1 v1.5, RSA-PSS, P-256, P-384, P-521, Ed25519), signer chains validated against a trust anchor, and a tampered copy of each
timestampan RFC 3161 response against its request, with the authority's certificate validated, and against a different request
validate-limbopath validation on a sample of x509-limbo testcases (the first four of each category)
unsupportedAES key wrap, OCB, EAX, PBES2 decryption and PKCS#12 opening with AES or legacy ciphers: typed refusal in the browser, success natively

The fixtures are generated by tools/gen-browser-fixtures.sh (OpenSSL and ssh-keygen; keys are throwaway) and committed under testdata/browser. tools/gen-browser-data.py bundles them with samples of the fetched Wycheproof and x509-limbo suites into one JSON file, embedded in the app at build time; the vector suites themselves are never committed. A run without the suites tests the fixtures only.

How it runs

tests/BrowserSuite/run.sh <output dir> publishes the app, then runs the same published files three ways and compares them:

  1. Headless Chromium. serve-and-drive.mjs serves the published folder on a loopback port, starts the pinned Chrome for Testing headless shell (tools/fetch-chrome-headless.sh, version and SHA-256 pinned in the script) with a throwaway profile and waits for the page to post its output lines back. A crash, an early exit or no answer within 15 minutes is a failure.
  2. Node, the host the WebAssembly smoke app uses.
  3. Natively on the .NET runtime, and again natively with the managed verifiers forced (force-fallbacks), which runs the browser's verification code on the same vectors where a fast platform could hide a defect.

compare.py requires: no failed case anywhere; Chromium and Node identical on every case; Chromium and native identical on every area that tests the same code on the same inputs. The areas that probe the platform itself (platform, unsupported, protected-inputs, load, private-keys) are listed, not judged, and so are the three ML-DSA limbo testcases, whose certificates a platform without ML-DSA cannot verify (the validator refuses the path, with Signature as the deciding rule). The report is report.md in the output folder.

Results

Latest run: Chrome for Testing headless shell 156.0.8078.80 (linux64), .NET 10.0.14 browser-wasm, Node 26.13.0, on a Linux x64 worker, with the Wycheproof and x509-limbo suites fetched (40 Wycheproof vectors per key group and the first four x509-limbo testcases of each category). Cases per area, pass / fail / skip:

areachromium (pass/fail/skip)node (pass/fail/skip)native (pass/fail/skip)native, fallbacks forced (pass/fail/skip)
platform7/0/07/0/07/0/07/0/0
parse-fixtures12/0/012/0/012/0/012/0/0
private-keys2/0/02/0/02/0/02/0/0
parse-limbo-certs778/0/0778/0/0778/0/0778/0/0
load30/0/030/0/030/0/030/0/0
kdf3/0/03/0/03/0/03/0/0
protected-inputs12/0/012/0/012/0/012/0/0
verify-wycheproof920/0/0920/0/0920/0/0920/0/0
ssh3/0/03/0/03/0/03/0/0
cms12/0/012/0/012/0/012/0/0
timestamp2/0/02/0/02/0/02/0/0
validate-limbo153/0/0153/0/0153/0/0153/0/0
unsupported6/0/06/0/06/0/06/0/0
  • Chromium and Node agree on all 1,940 shared cases (status and verdict); Chromium and the native run agree on every judged area. The only differences are the platform probes and the three ML-DSA limbo testcases (Invalid with the signature rule in the browser, Valid natively).
  • Nothing was skipped. The availability section the browser prints equals the browser-wasm section of availability.md.
  • The managed verifiers (forced natively) and the browser give the same verdicts on all 920 Wycheproof vectors; no vector fails anywhere.
  • The suite takes about a minute in Chromium once the app is published.

A run without the vector suites (the hosted CI job) covers the committed fixtures only: certificates, CRL, OCSP, CMS, time-stamp, keys, OpenSSH, KDFs, and the typed refusals.

What the browser cannot do

NeedsIn the browserBecause
AES (PBES2 keys, PKCS#12 with AES, OCB, EAX, AES key wrap, AES-GCM/CCM in CMS and OpenSSH)AlgorithmNotSupportedExceptionthe runtime has no AES; the library never implements it
triple DES and RC2 (legacy PKCS#12 and PEM, read only)AlgorithmNotSupportedExceptionthe runtime has no symmetric ciphers
RSA, ECDSA or ECDH key objects, private-key operations, deriving an EC public key from a private keyAlgorithmNotSupportedExceptionno managed private-key operations, on any platform
ML-KEM, ML-DSA, SLH-DSAAlgorithmNotSupportedExceptionno fallback; ML-DSA certificates fail path validation with the signature rule

Password-protected inputs. PBKDF2 is available in the browser runtime tested (.NET 10.0.14), and so are scrypt and Argon2 (ours), so key derivation is not the limit; the cipher is. A PKCS#12 file's MAC can be checked with VerifyMac (HMAC and the PKCS#12 key derivation only), and the container can be parsed. Crypto.Load and Open of any file with AES-, 3DES- or RC2-encrypted content (a PBES2 ENCRYPTED PRIVATE KEY, a PKCS#12 written by current OpenSSL or by the legacy provider) report Unsupported; a PKCS#12 with no encryption and no MAC opens through Pkcs12File.TryOpen(requireMac: false) but Crypto.Load refuses it for want of a MAC.

Reproducing

tools/gen-browser-fixtures.sh                  # only to regenerate the committed fixtures
ONECRYPTO_VECTORS=<folder with wycheproof/ and x509-limbo/> tests/BrowserSuite/run.sh <output dir>

Needs the wasm-tools workload, Node 22 or newer and python3. ONECRYPTO_BROWSER points at an installed headless shell instead of fetching one.