diff --git a/Cargo.lock b/Cargo.lock index ead84e08d3..f3fe6511f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1038,9 +1038,12 @@ dependencies = [ "cc", "cpal", "flate2", + "getrandom 0.3.4", + "rustls", "rustysynth", "softbuffer", "tar", + "toyos-crypto", "ureq", "winit", ] @@ -4430,6 +4433,7 @@ dependencies = [ "libc", "libm", "pem", + "rustls", "serde", "serde_json", "tar", diff --git a/Cargo.toml b/Cargo.toml index 5f946a9f75..e01001176e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -265,17 +265,19 @@ toyos-ssh = { path = "build/ssh" } # `classify`, so the host reading a log volume and `logkeeper` writing it cannot # disagree about which files on it are `logkeeper`'s. toyos-wallclock = { path = "lib/wallclock" } -# Every SHA-256 the build takes (`toyos_build::sha256`). -toyos-crypto = { path = "lib/crypto" } +# Every SHA-256 the build takes (`toyos_build::sha256`), and the TLS provider of +# every request it makes (`release::agent`). +toyos-crypto = { path = "lib/crypto", features = ["std", "rustls-provider"] } +# The provider's types: its random source and its key-exchange groups. +rustls = { version = "0.23.45", default-features = false } # The toolchain release's tarball, packed in-process (`build/release.rs`): Rust's # own tar and gzip, where the binaries are hosts' tools. tar = { version = "0.4.46", default-features = false } flate2 = { version = "1", default-features = false, features = ["rust_backend"] } # Every request the build system makes, GitHub's API and the crates.io index, -# where curl is a host's tool: rustls on ring, the one TLS provider the tree -# takes (`issues/the-internet-clients-work-unchanged.md`), whose C -# and assembly the host's `cc` compiles. -ureq = { version = "3", default-features = false, features = ["rustls"] } +# where curl is a host's tool: rustls on toyos-crypto's provider, with webpki's +# roots. +ureq = { version = "3", default-features = false, features = ["rustls-no-provider", "rustls-webpki-roots"] } # The authorities `build::trust_roots` writes to ROOT, as the rustls project # publishes the Mozilla root program, and the PEM it writes them in. webpki-root-certs = "1" diff --git a/apps/doom/Cargo.toml b/apps/doom/Cargo.toml index 103f8e36ed..2e98aab37a 100644 --- a/apps/doom/Cargo.toml +++ b/apps/doom/Cargo.toml @@ -18,7 +18,12 @@ softbuffer = { version = "0.4", default-features = false, features = ["wayland", [build-dependencies] cc = "1.1" -ureq = { version = "3", default-features = false, features = ["rustls"] } +# doomgeneric's source, over rustls on toyos-crypto's provider with webpki's +# roots, its entropy the host's. +ureq = { version = "3", default-features = false, features = ["rustls-no-provider", "rustls-webpki-roots"] } +toyos-crypto = { path = "../../lib/crypto", features = ["std", "rustls-provider"] } +rustls = { version = "0.23.45", default-features = false } +getrandom = "0.3" flate2 = { version = "1", default-features = false, features = ["rust_backend"] } tar = "0.4" diff --git a/apps/doom/build.rs b/apps/doom/build.rs index e0130ad5c2..f8135b567f 100644 --- a/apps/doom/build.rs +++ b/apps/doom/build.rs @@ -157,12 +157,33 @@ fn main() { println!("cargo:rerun-if-changed=doomgeneric"); } +/// The host's random bytes, which the provider's key exchanges draw. +#[derive(Debug)] +struct SystemRandom; + +impl rustls::crypto::SecureRandom for SystemRandom { + fn fill(&self, bytes: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> { + getrandom::fill(bytes).map_err(|_| rustls::crypto::GetRandomFailed) + } +} + +static TLS: toyos_crypto::rustls_provider::Backend = toyos_crypto::rustls_provider::Backend::new(&SystemRandom); + +/// toyos-crypto's provider, exchanging keys on X25519 alone, hybrid or not, +/// as the build's own client does (`build/release.rs`). +fn tls_provider() -> std::sync::Arc { + let mut provider = TLS.provider(); + provider.kx_groups.retain(|group| matches!(group.name(), rustls::NamedGroup::X25519MLKEM768 | rustls::NamedGroup::X25519)); + std::sync::Arc::new(provider) +} + /// Root `CLAUDE.md`'s: the only `User-Agent` ToyOS sends. const USER_AGENT: &str = "toyos-build (https://github.com/ToyOSOrg/ToyOS)"; fn download_doomgeneric(root: &Path) { println!("Downloading doomgeneric {DOOMGENERIC_COMMIT}..."); - let agent = ureq::Agent::config_builder().user_agent(USER_AGENT).build().new_agent(); + let tls = ureq::tls::TlsConfig::builder().unversioned_rustls_crypto_provider(tls_provider()).build(); + let agent = ureq::Agent::config_builder().user_agent(USER_AGENT).tls_config(tls).build().new_agent(); // The commit is the checksum: GitHub's archive of a sha is that sha's tree // and can be nothing else, so pinning the URL needs no second hash of ours. let resp = agent diff --git a/build/release.rs b/build/release.rs index 497608ca29..efd803c29a 100644 --- a/build/release.rs +++ b/build/release.rs @@ -119,11 +119,33 @@ fn on_runner() -> bool { std::env::var("GITHUB_ACTIONS").is_ok_and(|v| v == "true") } -/// The HTTP client of every request the build system makes: rustls on ring -/// with webpki's roots, as ureq configures it; a status is an answer, not an +/// The HTTP client of every request the build system makes: rustls on +/// toyos-crypto's provider with webpki's roots; a status is an answer, not an /// error. pub(crate) fn agent() -> ureq::Agent { - ureq::Agent::config_builder().user_agent(USER_AGENT).http_status_as_error(false).build().new_agent() + let tls = ureq::tls::TlsConfig::builder().unversioned_rustls_crypto_provider(tls_provider()).build(); + ureq::Agent::config_builder().user_agent(USER_AGENT).http_status_as_error(false).tls_config(tls).build().new_agent() +} + +/// The system's random bytes, which the provider's key exchanges draw. +#[derive(Debug)] +struct SystemRandom; + +impl rustls::crypto::SecureRandom for SystemRandom { + fn fill(&self, bytes: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> { + getrandom::fill(bytes).map_err(|_| rustls::crypto::GetRandomFailed) + } +} + +static TLS: toyos_crypto::rustls_provider::Backend = toyos_crypto::rustls_provider::Backend::new(&SystemRandom); + +/// toyos-crypto's provider, exchanging keys on X25519 alone, hybrid or not: +/// no P-256 or P-384 secret meets a server's input while the T14 measures +/// those curves leaking (`issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md`). +fn tls_provider() -> std::sync::Arc { + let mut provider = TLS.provider(); + provider.kx_groups.retain(|group| matches!(group.name(), rustls::NamedGroup::X25519MLKEM768 | rustls::NamedGroup::X25519)); + std::sync::Arc::new(provider) } /// Whether this job is main's publisher — [`PUBLISHER`] on main, scheduled or diff --git a/issues/the-build-runs-host-tools-outside-rust-and-qemu.md b/issues/the-build-runs-host-tools-outside-rust-and-qemu.md index 18a380fb83..d199efe052 100644 --- a/issues/the-build-runs-host-tools-outside-rust-and-qemu.md +++ b/issues/the-build-runs-host-tools-outside-rust-and-qemu.md @@ -20,7 +20,7 @@ arrives and is not one. M4 and M5 are stages of `issues/toyos-builds-itself.md`. | `git` for worktrees, submodules, checkouts, fixtures and rustc's bootstrap | adds worktrees (`build/sysroot.rs`); updates submodules (`src/lib.rs`, `build/sysroot.rs`); fetches the fork from the primary's and checks it out (`build/sysroot.rs`); makes the tests' fixture repositories; runs inside rustc's bootstrap | admitted: no Rust tool does the job, gitoxide 0.85 adds, removes and prunes no worktree, updates no submodule, stages, resets and pushes nothing, checks out only a fresh clone and fetches a local path by spawning `git`; a fixture must be what `git` makes, and bootstrap runs `git` itself | M4 runs it in the guest | | `git` for reads, a config write, a commit's paths written out, and clones and fetches over HTTPS | `rev-parse`, `merge-base`, `ls-tree`, `ls-files`, `cat-file`, `config --get-regexp`, `config --blob`, `status`, `diff`, `ls-remote` and `grep`, in the build system and its tests; `config --global --add safe.directory` in CI's containers; `checkout -- ` through an index of its own, which writes the C++ runtime's sources out of the LLVM commit into the stored LLVM, and the ToyOS-hosted clang's out of it into its build (`build/llvm.rs`, `build/hostedclang.rs`); every workflow's checkout | refused: a Rust tool does it, gitoxide 0.85, which reads refs, objects, the index, config, worktrees and status, adds a value to a config file and writes it (gix-config 0.58's `File::section_mut_or_create_new`, `SectionMut::push`, `File::write_to`), walks history, diffs, and lists, fetches and clones a remote over HTTPS; `grep` is a search of the files its index names; and gitoxide's CLI 0.59 (gix 0.88) wrote the runtimes' sources of LLVM `849da7d6` into an empty directory, each path's tree through `gix rev parse`, `gix index from-tree` and `gix free index checkout-exclusive`, exit 0 each: the 18759 files `git` writes there, byte for byte and mode for mode | those are gitoxide's | | `git log` and `git status` for os-release | the commit, its committer time and whether the tree is that commit's, which `build/build.rs`'s `release` writes into `/system/etc/os-release` in every image build | admitted: the owner's ruling on the dependency audit (2026-10-09), "Drop gix, use git", which covers these two reads and nothing else this table names: they were gitoxide's, gitoxide is no tool the build sheds while `git` stays for the worktree row, and for them it held 84 of the root `Cargo.lock`'s packages | M4 runs it in the guest | -| `cc`, `c++` and `ar` on a Linux host, `build-essential` in CI's containers | rustc links every host binary through `cc`; `cc` and `c++` compile LLVM, clang, LLD and `rustc_llvm` (`build/llvm.rs` names both to bootstrap), and the tablegens of the ToyOS-hosted clang's `NATIVE` build (`build/hostedclang.rs`); `cc` compiles ring's C and assembly in every build of the build system itself, whose HTTP agent is rustls on ring (`build/release.rs`), and in every build of doom's build script, whose download of doomgeneric is `ureq` on rustls on ring (`apps/doom/build.rs`), and in every `--ci host` run as `toyos-crypto`'s dev-dependency oracle (`lib/crypto`); `ar` archives what `cc::Build` compiles, ring's included | admitted: no Rust tool compiles C or C++, or takes rustc's host link; ring is the one TLS provider the tree takes (owner, 2026-10-02), over a provider in Rust alone | M5: no host in the loop | +| `cc`, `c++` and `ar` on a Linux host, `build-essential` in CI's containers | rustc links every host binary through `cc`; `cc` and `c++` compile LLVM, clang, LLD and `rustc_llvm` (`build/llvm.rs` names both to bootstrap), and the tablegens of the ToyOS-hosted clang's `NATIVE` build (`build/hostedclang.rs`); `cc` compiles ring's C and assembly in every `--ci host` run, as the dev-dependency oracle of `toyos-crypto` and `toyos-update` (`lib/crypto`, `lib/update`); `ar` archives what `cc::Build` compiles, ring's included | admitted: no Rust tool compiles C or C++, or takes rustc's host link; ring is the independent oracle the crypto crate is held to, and the build's own HTTP agent and doom's build script are on `toyos-crypto`'s provider (#1020) | M5: no host in the loop | | the toolchain's own `clang`, `llvm-ar`, `rust-lld` and `llvm-config`, built from `ToyOSOrg/llvm-project` | rustc links every guest binary with `rust-lld`; `clang` compiles the C corpus (`tests/common/compile.rs`) and, with `llvm-ar`, through `cc::Build` the C of every guest crate that has any (`build/clang.rs`, `build/build.rs`'s `GuestEnv`): doomgeneric, and ring's C and assembly in a test crate's `https_get` and `ring_kat`; rustc's bootstrap asks `llvm-config` how to link LLVM | admitted: our fork's C++, which ToyOS can one day build and run; no Rust tool compiles C, `cc::Build` archives with an `ar`, bootstrap reads LLVM through `llvm-config`, and `CLAUDE.md` links everything with `rust-lld` | M5: no host in the loop | | `ovmf-generic`, `qemu-efi-aarch64` | the x86-64 and AArch64 UEFI firmware of CI's guest containers (`build/firmware.rs`), packaged by Debian apart from QEMU | admitted: QEMU's own firmware, and no Rust firmware does its job | the instrument's QEMU carries its own firmware | | `ca-certificates` | the trust store `git` and `curl` verify against in CI's containers | admitted: data both of them need | goes when neither runs there | diff --git a/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md b/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md index 9d82023bb7..7adb02d735 100644 --- a/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md +++ b/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md @@ -64,7 +64,7 @@ review has been done, and passing vectors does not show it is safer than ring. | `system/sshserver` | russh with the RustCrypto stack, including `rsa` (RUSTSEC-2023-0071) | none directly; it reaches the crate through `toyos-ssh` | | Image and package signing: `lib/update` (the loader, `/system/bin/update`, `build/signing.rs`, `build/root`, #892's repository keys) | the crate's strict Ed25519 and SHA-2, since #1019; signing and a seed's public key behind `sign`, on the host | stage 6's bench readings | | Every SHA-256: the build, `pkg`, `lib/swap`, `system/update`, `lib/update` and the loader | the crate, since #1016: SHA-NI on x86-64, the UEFI loader's in a block that saves the XMM registers it uses; the Armv8 SHA instructions where the target promises them. No slower than `toyos-sha2-hw`, which it replaces, on either bench: on the T14 4.6 times as fast at 64 B and 9 and 6 percent faster at 16 KiB and 1 MiB, on the AMD bench 2.8 times at 64 B, 2 percent at 16 KiB and equal at 1 MiB (#1016's evidence) | nothing | -| ToyOS's own HTTPS clients. On the host: the build's `ureq` (`build/release.rs`) and doom's build script (`apps/doom/build.rs`). In a guest, none today: `https_tls13` and `pkg install ` are stages 3 and 5 of `issues/the-internet-clients-work-unchanged.md` | rustls on `ring`, which is `ToyOSOrg/ring` in a guest | the rustls provider through ureq's `rustls-no-provider` feature. Stage 5, once parity and security hold (owner, 2026-10-13) | +| ToyOS's own HTTPS clients. On the host: the build's `ureq` (`build/release.rs`) and doom's build script (`apps/doom/build.rs`). In a guest, none today: `https_tls13` and `pkg install ` are stages 3 and 5 of `issues/the-internet-clients-work-unchanged.md` | the host's two: the crate's rustls provider through ureq's `rustls-no-provider`, since #1020, its key exchange on X25519 and X25519MLKEM768 alone while the T14 finding stands. In a guest, none | the NIST groups back once the T14 finding is resolved | | Wi-Fi's `wlan` (`issues/there-is-no-wifi.md` P6, W2) | nothing yet | PBKDF2-HMAC-SHA1, the 802.11i PRF and RFC 3394 unwrap, which the crate has. AES-CMAC-128, SAE over P-256 by hash-to-element, a public constant-time comparison and a public wiping secret, which it lacks | | `issues/programs-hold-aes-keys-as-key-locker-handles.md` | blocked on a session-long AES key | a TLS client on the crate is that consumer: AES behind a Key Locker handle. SSH holds no AES key | @@ -474,18 +474,18 @@ Not consumers: `toyos-sha2` and `toyos-sha2-hw` are deleted; the crate's SHAVS tests replace theirs. NIST's ACVP vectors moved to `tests/acvp/`, outside the crate's directory, once the crate entered an image. - - ToyOS's own HTTPS clients use the crate's rustls provider once stages 3 - and 4 hold for TLS's primitives. The owner ruled this on 2026-10-13: - the crypto goal overrides his ruling of 2026-10-02 in - `issues/the-internet-clients-work-unchanged.md` stage 3. Until then ring - stays the provider. `ToyOSOrg/ring` stays as a fork so that unmodified - programs build. + - ToyOS's own HTTPS clients use the crate's rustls provider. The owner + ruled on 2026-10-13 that the crypto goal overrides his ruling of + 2026-10-02 in `issues/the-internet-clients-work-unchanged.md` stage 3, + and on 2026-10-22 that the tree uses the crate now. The host's two, the + build's agent and doom's build script, take it since #1020, on X25519 + groups alone while the T14 finding stands. `ToyOSOrg/ring` stays as a + fork so that unmodified programs build. - The `https_tls13` metal row passes on it. - The `cc` row of - `issues/the-build-runs-host-tools-outside-rust-and-qemu.md` stops - naming ring for the build system's HTTP agent. It still names ring as - `lib/crypto`'s dev-dependency oracle, whose C and assembly every - `--ci host` run compiles. + `issues/the-build-runs-host-tools-outside-rust-and-qemu.md` names ring + only as the crate's and `lib/update`'s dev-dependency oracle, whose C + and assembly every `--ci host` run compiles. Done (#1020). - Wi-Fi's P6 is this crate's AES-CMAC, SAE by hash-to-element, public constant-time comparison and public wiping secret, each against its RFC or Annex J vectors. @@ -534,6 +534,13 @@ Not consumers: per-temporary erasure on the T14, 0.79–0.88 on the AMD bench) and a reviewed contract. Compiler copies, registers, spills and caller-held bytes are not wiped. +- The host's HTTPS clients exchange keys on X25519 and X25519MLKEM768 + alone: no P-256 or P-384 secret meets a server's input while the T14 + finding stands, and a server offering only those groups is refused. Each + client keeps the two groups itself (`build/release.rs`, + `apps/doom/build.rs`). **Owner**: this track. **Exit**: stage 4's T14 + finding (P-256 key derivation and agreement, and P-384 agreement, leak) is + resolved, and the clients take the provider's groups whole. - ToyOS's AArch64 targets hash with the portable SHA-2 rounds: no target promises the Armv8 SHA instructions, and neither the kernel nor the crate detects them at run time. **Owner**: this track. **Exit**: the crate takes