fix(sdk): enable rustls' ring provider — APK died at 'Failed to start / Could not determine the process-level CryptoProvider' #15

Merged
sirius merged 2 commits from wt/t_dcb8565a into main 2026-09-11 02:35:32 +00:00
Owner

Symptom

The APK built, installed and opened. Pressing Open on the unlock screen failed at runtime:

Failed to start

Could not automatically determine the process-level CryptoProvider from
Rustls crate features.
Call CryptoProvider::install_default() before this point to select a provider
manually, or make sure exactly one of the 'aws-lc-rs' and 'ring' features is
enabled.

Not a Tor problem, not an Arti publishing problem — a missing rustls provider.

Root cause (confirmed against the real feature graph, not inferred)

rustls 0.23 selects its CryptoProvider from its own ring / aws-lc-rs crate features, never from which crypto crates happen to be linked. Arti reaches rustls through tor-rtcompat with default-features = false, and nothing in the onionwire-sdk workspace turned a provider feature on:

$ cargo tree -f '{p} [{f}]' -p rustls        # BEFORE
rustls v0.23.44 [log,logging,std,tls12]

No ring, no aws-lc-rs → rustls compiled with zero providers → the first TLS config built from the process default panics with the message above (rustls-0.23.44/src/crypto/mod.rs:249).

ring did appear in cargo tree — pulled in by snow for Noise IK. That was never relevant to rustls; it is a different consumer of the same crate.

aws-lc-rs is absent from the lock entirely, so there was no second-provider conflict to report. aws-lc-rs would also be the wrong choice for this target: it wants CMake and a C toolchain for the NDK and fights the cross-compile.

Fix (5 files, all under crates/onionwire-sdk/)

  1. Cargo.toml — explicit provider pin:

    rustls = { version = "0.23", default-features = false, features = ["ring"] }
    
    $ cargo tree -f '{p} [{f}]' -p rustls        # AFTER
    rustls v0.23.44 [log,logging,ring,std,tls12]
    

    Committed in 411194a without the feature first, so the regression test fails against the pre-fix graph.

  2. src/lib.rs — belt and braces: install_crypto_provider(), a OnceLock-guarded rustls::crypto::ring::default_provider().install_default() with the already-installed Err ignored. Called from open_wire() before Node::start_with_passphrase, so the cdylib is correct regardless of how a consumer's feature graph resolves rustls. Not exported over UniFFI — Rust-side plumbing only.

  3. Two regression tests, in separate test binaries on purpose (a provider installed in one would mask the other):

    • tests/provider_resolution.rs — the invariant Arti relies on: the process default must resolve with nobody calling install_default().
    • tests/init_provider.rsinstall_crypto_provider() installs the default; the second call is a no-op; ClientConfig::builder() then works.

The Linux TUI is untouched: root Cargo.toml features are unchanged, default = ["native-tls"] still holds, and the two Cargo workspaces stay separate. No protocol, TUI, store.rs, session.rs, schema or invite-format changes.

Verification

RED — host, pre-fix (411194a), reproduces the device error verbatim:

$ cargo test --release --test provider_resolution
thread '...' panicked at rustls-0.23.44/src/crypto/mod.rs:249:14:
Could not automatically determine the process-level CryptoProvider from Rustls crate features.
Call CryptoProvider::install_default() before this point to select a provider manually, or make
sure exactly one of the 'aws-lc-rs' and 'ring' features is enabled.
test result: FAILED. 0 passed; 1 failed

GREEN — host, post-fix:

$ cargo test --release                       # in crates/onionwire-sdk
test sdk_installs_the_process_default_provider ... ok
test rustls_default_provider_resolves_without_manual_install ... ok
test result: ok. 2 passed; 0 failed

Root-crate gates (unchanged working tree, lock untouched):

$ cargo test --locked      -> 126 passed; 0 failed; 3 ignored
$ cargo clippy --locked --all-targets -- -D warnings  -> clean

On-device: actually run, not inferred. No phone was attached, so this ran on a Pixel 7 AVD (API 35, x86_64, -gpu swiftshader_indirect), which exercises the same open_wire path. A/B with the same emulator, same passphrase input, same tap coordinates:

build tap Open result
pre-fix APK (rebuilt from 411194a) Failed to start + exactly the Could not determine the process-level CryptoProvider message
post-fix APK Publishing onion service… → roster screen

Post-fix, the node reached Ready on the emulator: Arti bootstrapped, the v3 onion service published, the roster showed

YOU (stable forever)  db37298b1b7c2fbf31d39310496ad347c1d27c09fa26c0adafb2e7f79dc95145
ONION (locator)       hyflvsbh2d43bxh4quxlsmz5je3dql33ee53mmu6245ggaabqzvudzad.onion
sdk 0.1.0 · identity = ed25519 pubkey

adb logcat -d | grep -c CryptoProvider over the whole session: 0.

Artifact-level check that the fix is really in the packaged library, not just the host test binary (mangled rustls::crypto::ring symbols in the shipped .so):

lib/arm64-v8a/libonionwire_sdk.so : 134 rustls-ring symbols, 25717904 bytes
lib/x86_64/libonionwire_sdk.so    : 134 rustls-ring symbols, 24303496 bytes
   (pre-fix build of the same library: 0)

Build

cd android
./gradlew :sdk:assembleRelease :app:assembleDebug -Ponionwire.abis=arm64-v8a,x86_64
  • android/app/build/outputs/apk/debug/app-debug.apk — 62,621,963 bytes (arm64-v8a + x86_64)
  • android/sdk/build/outputs/aar/sdk-release.aar — 14,671,935 bytes

For a phone, arm64 is all that is needed: omit the ABI property to get the default single-ABI build (~38 MB).

Base

Cut from 389d8f1 = current origin/main. git rev-list --left-right --count origin/main...HEAD0 2. Audit items F1–F7 from PRs #3/#7/#12/#13/#14 are present in the base and untouched by this diff (which only adds/or edits the 5 files listed above).

## Symptom The APK built, installed and opened. Pressing **Open** on the unlock screen failed at runtime: ``` Failed to start Could not automatically determine the process-level CryptoProvider from Rustls crate features. Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'aws-lc-rs' and 'ring' features is enabled. ``` Not a Tor problem, not an Arti publishing problem — a missing rustls provider. ## Root cause (confirmed against the real feature graph, not inferred) `rustls` 0.23 selects its `CryptoProvider` from its **own** `ring` / `aws-lc-rs` **crate features**, never from which crypto crates happen to be linked. Arti reaches rustls through `tor-rtcompat` with `default-features = false`, and nothing in the `onionwire-sdk` workspace turned a provider feature on: ``` $ cargo tree -f '{p} [{f}]' -p rustls # BEFORE rustls v0.23.44 [log,logging,std,tls12] ``` No `ring`, no `aws-lc-rs` → rustls compiled with zero providers → the first TLS config built from the process default panics with the message above (`rustls-0.23.44/src/crypto/mod.rs:249`). `ring` *did* appear in `cargo tree` — pulled in by `snow` for Noise IK. That was never relevant to rustls; it is a different consumer of the same crate. `aws-lc-rs` is absent from the lock entirely, so there was no second-provider conflict to report. `aws-lc-rs` would also be the wrong choice for this target: it wants CMake and a C toolchain for the NDK and fights the cross-compile. ## Fix (5 files, all under `crates/onionwire-sdk/`) 1. `Cargo.toml` — explicit provider pin: ```toml rustls = { version = "0.23", default-features = false, features = ["ring"] } ``` ``` $ cargo tree -f '{p} [{f}]' -p rustls # AFTER rustls v0.23.44 [log,logging,ring,std,tls12] ``` Committed in `411194a` *without* the feature first, so the regression test fails against the pre-fix graph. 2. `src/lib.rs` — belt and braces: `install_crypto_provider()`, a `OnceLock`-guarded `rustls::crypto::ring::default_provider().install_default()` with the already-installed `Err` ignored. Called from `open_wire()` before `Node::start_with_passphrase`, so the cdylib is correct regardless of how a consumer's feature graph resolves rustls. Not exported over UniFFI — Rust-side plumbing only. 3. Two regression tests, in separate test binaries on purpose (a provider installed in one would mask the other): - `tests/provider_resolution.rs` — the invariant Arti relies on: the process default must resolve with nobody calling `install_default()`. - `tests/init_provider.rs` — `install_crypto_provider()` installs the default; the second call is a no-op; `ClientConfig::builder()` then works. The Linux TUI is untouched: root `Cargo.toml` features are unchanged, `default = ["native-tls"]` still holds, and the two Cargo workspaces stay separate. No protocol, TUI, `store.rs`, `session.rs`, schema or invite-format changes. ## Verification **RED — host, pre-fix** (`411194a`), reproduces the device error verbatim: ``` $ cargo test --release --test provider_resolution thread '...' panicked at rustls-0.23.44/src/crypto/mod.rs:249:14: Could not automatically determine the process-level CryptoProvider from Rustls crate features. Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'aws-lc-rs' and 'ring' features is enabled. test result: FAILED. 0 passed; 1 failed ``` **GREEN — host, post-fix**: ``` $ cargo test --release # in crates/onionwire-sdk test sdk_installs_the_process_default_provider ... ok test rustls_default_provider_resolves_without_manual_install ... ok test result: ok. 2 passed; 0 failed ``` **Root-crate gates** (unchanged working tree, lock untouched): ``` $ cargo test --locked -> 126 passed; 0 failed; 3 ignored $ cargo clippy --locked --all-targets -- -D warnings -> clean ``` **On-device: actually run, not inferred.** No phone was attached, so this ran on a Pixel 7 AVD (API 35, x86_64, `-gpu swiftshader_indirect`), which exercises the same `open_wire` path. A/B with the *same* emulator, *same* passphrase input, *same* tap coordinates: | build | tap **Open** result | |---|---| | pre-fix APK (rebuilt from `411194a`) | `Failed to start` + **exactly** the `Could not determine the process-level CryptoProvider` message | | post-fix APK | `Publishing onion service…` → roster screen | Post-fix, the node reached **Ready** on the emulator: Arti bootstrapped, the v3 onion service published, the roster showed ``` YOU (stable forever) db37298b1b7c2fbf31d39310496ad347c1d27c09fa26c0adafb2e7f79dc95145 ONION (locator) hyflvsbh2d43bxh4quxlsmz5je3dql33ee53mmu6245ggaabqzvudzad.onion sdk 0.1.0 · identity = ed25519 pubkey ``` `adb logcat -d | grep -c CryptoProvider` over the whole session: **0**. Artifact-level check that the fix is really in the packaged library, not just the host test binary (mangled `rustls::crypto::ring` symbols in the shipped `.so`): ``` lib/arm64-v8a/libonionwire_sdk.so : 134 rustls-ring symbols, 25717904 bytes lib/x86_64/libonionwire_sdk.so : 134 rustls-ring symbols, 24303496 bytes (pre-fix build of the same library: 0) ``` ## Build ``` cd android ./gradlew :sdk:assembleRelease :app:assembleDebug -Ponionwire.abis=arm64-v8a,x86_64 ``` - `android/app/build/outputs/apk/debug/app-debug.apk` — 62,621,963 bytes (arm64-v8a + x86_64) - `android/sdk/build/outputs/aar/sdk-release.aar` — 14,671,935 bytes For a phone, arm64 is all that is needed: omit the ABI property to get the default single-ABI build (~38 MB). ## Base Cut from `389d8f1` = current `origin/main`. `git rev-list --left-right --count origin/main...HEAD` → `0 2`. Audit items F1–F7 from PRs #3/#7/#12/#13/#14 are present in the base and untouched by this diff (which only adds/or edits the 5 files listed above).
sirius added 2 commits 2026-09-11 02:19:39 +00:00
RED. The APK builds, installs and opens, but the first rustls config built
from the process default panics one call deep inside Arti's rustls backend:

    Could not automatically determine the process-level CryptoProvider from
    Rustls crate features.
    Call CryptoProvider::install_default() before this point ...

rustls 0.23 picks its provider from its own 'ring' / 'aws-lc-rs' crate
features, not from which crypto crates happen to be linked. Arti reaches
rustls via tor-rtcompat with default-features = false, so neither provider
feature is on and rustls compiles with zero providers. 'ring' does appear in
cargo tree, pulled in by snow for Noise -- unrelated and irrelevant.

This commit adds rustls as an explicit dependency with no provider feature
(no behaviour change: it is already in the graph that way) so the test can
name the type, plus the regression test. On the host it reproduces the
device's panic verbatim.

The SDK lockfile is resynced with the root Cargo.toml on main (onionwire
0.2.1, rpassword, md-5, rtoolbox) in the same commit so the test build is
against a consistent lock.
fix(sdk): enable rustls' ring provider and install it in the init path
All checks were successful
ci / test (pull_request) Successful in 3m30s
d2d3b0c827
GREEN. rustls 0.23 selects its CryptoProvider from its own 'ring' /
'aws-lc-rs' crate features. Arti reaches rustls through tor-rtcompat with
default-features = false and nothing in this workspace turned a provider
feature on, so rustls was compiled with zero providers and the first TLS
config built from the process default panicked:

    Could not automatically determine the process-level CryptoProvider from
    Rustls crate features. ...

That is the 'Failed to start' screen on the unlock path. The 'ring' crate
being present in the graph (via snow, for Noise) never had anything to do
with it.

'ring', not 'aws-lc-rs': aws-lc-rs wants CMake and a C toolchain for the
NDK and fights the cross-compile.

Belt and braces: open_wire() now calls install_crypto_provider() before
Node::start_with_passphrase, a OnceLock-guarded
ring::default_provider().install_default() with the already-installed Err
ignored. That keeps the cdylib correct regardless of how a consumer's
feature graph resolves rustls.

aws-lc-rs is absent from the lock, so there is no two-provider conflict to
report.

Evidence:
  cargo test --release --test provider_resolution
    before: panicked at rustls-0.23.44/src/crypto/mod.rs:249 (device message)
    after:  1 passed
  cargo tree -f '{p} [{f}]' -p rustls
    rustls v0.23.44 [log,logging,ring,std,tls12]
sirius merged commit c141eb45b8 into main 2026-09-11 02:35:32 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: sirius/onionwire#15
No description provided.