Merge pull request 'fix(sdk): enable rustls' ring provider — APK died at 'Failed to start / Could not determine the process-level CryptoProvider'' (#15) from wt/t_dcb8565a into main
All checks were successful
ci / test (push) Successful in 4m5s
All checks were successful
ci / test (push) Successful in 4m5s
Reviewed-on: #15
This commit is contained in:
commit
c141eb45b8
5 changed files with 153 additions and 2 deletions
38
crates/onionwire-sdk/Cargo.lock
generated
38
crates/onionwire-sdk/Cargo.lock
generated
|
|
@ -2507,6 +2507,16 @@ dependencies = [
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md-5"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.3"
|
version = "2.8.3"
|
||||||
|
|
@ -2750,18 +2760,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onionwire"
|
name = "onionwire"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"arti-client",
|
"arti-client",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"futures",
|
"futures",
|
||||||
|
"md-5",
|
||||||
"rand 0.8.8",
|
"rand 0.8.8",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"rpassword",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"safelog",
|
"safelog",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha3 0.10.9",
|
||||||
"snow",
|
"snow",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tor-cell",
|
"tor-cell",
|
||||||
|
|
@ -2776,6 +2789,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arti-client",
|
"arti-client",
|
||||||
"onionwire",
|
"onionwire",
|
||||||
|
"rustls",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uniffi",
|
"uniffi",
|
||||||
|
|
@ -3472,6 +3486,17 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rpassword"
|
||||||
|
version = "7.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rtoolbox",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.10"
|
version = "0.9.10"
|
||||||
|
|
@ -3493,6 +3518,16 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtoolbox"
|
||||||
|
version = "0.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.36.0"
|
version = "0.36.0"
|
||||||
|
|
@ -3553,6 +3588,7 @@ checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,17 @@ arti-client = { version = "0.46", default-features = false, features = [
|
||||||
"static-sqlite",
|
"static-sqlite",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "time"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "time"] }
|
||||||
|
# rustls 0.23 resolves its provider from its OWN `ring` / `aws-lc-rs` features,
|
||||||
|
# never from whichever crypto crates happen to be linked in the graph. Arti
|
||||||
|
# pulls rustls in through `tor-rtcompat` with `default-features = false`, so
|
||||||
|
# without this line rustls compiles with zero providers and the first TLS config
|
||||||
|
# built from the process default fails at runtime — the "Failed to start /
|
||||||
|
# Could not automatically determine the process-level CryptoProvider" screen.
|
||||||
|
#
|
||||||
|
# `ring`, not `aws-lc-rs`: aws-lc-rs needs CMake and a C toolchain for the NDK
|
||||||
|
# and fights the Android cross-compile. The `ring` crate was already in the
|
||||||
|
# graph (via `snow`, for Noise) but that is a different thing entirely.
|
||||||
|
rustls = { version = "0.23", default-features = false, features = ["ring"] }
|
||||||
uniffi = { version = "0.32", features = ["cli", "tokio"] }
|
uniffi = { version = "0.32", features = ["cli", "tokio"] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,38 @@
|
||||||
//! no TUI/ratatui type leaks into the AAR.
|
//! no TUI/ratatui type leaks into the AAR.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
use onionwire::node::Node;
|
use onionwire::node::Node;
|
||||||
use onionwire::qr;
|
use onionwire::qr;
|
||||||
|
|
||||||
uniffi::setup_scaffolding!();
|
uniffi::setup_scaffolding!();
|
||||||
|
|
||||||
|
/// Install rustls's process-level default `CryptoProvider`, exactly once.
|
||||||
|
///
|
||||||
|
/// Belt and braces. `Cargo.toml` pins `rustls` with the `ring` feature, which
|
||||||
|
/// is what actually fixes the missing-provider failure: with it, rustls
|
||||||
|
/// resolves a provider from crate features on its own. This function exists
|
||||||
|
/// because the cdylib is loaded into a process we do not own — an explicitly
|
||||||
|
/// installed provider makes the SDK independent of how the surrounding app's
|
||||||
|
/// feature graph happens to resolve rustls.
|
||||||
|
///
|
||||||
|
/// Idempotent and cheap after the first call (`OnceLock` short-circuits it).
|
||||||
|
/// An `Err` means some provider is already installed process-wide, which is the
|
||||||
|
/// outcome we want, so it is deliberately ignored — including the case of a
|
||||||
|
/// consumer that installed `aws-lc-rs` itself. A genuine *conflict* — both
|
||||||
|
/// provider features compiled in — is a build-graph bug, not something to
|
||||||
|
/// paper over here; `Cargo.toml` enables `ring` only.
|
||||||
|
///
|
||||||
|
/// Not exported over UniFFI: it is Rust-side plumbing, not part of the Kotlin
|
||||||
|
/// surface.
|
||||||
|
pub fn install_crypto_provider() {
|
||||||
|
static INSTALLED: OnceLock<()> = OnceLock::new();
|
||||||
|
INSTALLED.get_or_init(|| {
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||||
#[uniffi(flat_error)]
|
#[uniffi(flat_error)]
|
||||||
pub enum WireError {
|
pub enum WireError {
|
||||||
|
|
@ -88,6 +113,10 @@ pub struct Wire {
|
||||||
/// that is minutes, not seconds. Call it off the main thread.
|
/// that is minutes, not seconds. Call it off the main thread.
|
||||||
#[uniffi::export(async_runtime = "tokio")]
|
#[uniffi::export(async_runtime = "tokio")]
|
||||||
pub async fn open_wire(home: String, passphrase: String) -> WResult<Arc<Wire>> {
|
pub async fn open_wire(home: String, passphrase: String) -> WResult<Arc<Wire>> {
|
||||||
|
// Before anything that can build a TLS config: Arti's rustls backend dies
|
||||||
|
// with "Could not automatically determine the process-level CryptoProvider"
|
||||||
|
// if no provider is resolvable. See `install_crypto_provider`.
|
||||||
|
install_crypto_provider();
|
||||||
let node = Node::start_with_passphrase(PathBuf::from(home), &passphrase)
|
let node = Node::start_with_passphrase(PathBuf::from(home), &passphrase)
|
||||||
.await
|
.await
|
||||||
.map_err(WireError::new)?;
|
.map_err(WireError::new)?;
|
||||||
|
|
|
||||||
30
crates/onionwire-sdk/tests/init_provider.rs
Normal file
30
crates/onionwire-sdk/tests/init_provider.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
//! `open_wire` must not depend on how the consumer's feature graph resolves a
|
||||||
|
//! rustls provider — it installs the process default itself, first.
|
||||||
|
//!
|
||||||
|
//! This is the belt-and-braces half of the fix for the on-device "Failed to
|
||||||
|
//! start / Could not automatically determine the process-level CryptoProvider"
|
||||||
|
//! screen; the primary fix is the `rustls` `ring` feature in `Cargo.toml`,
|
||||||
|
//! covered by `tests/provider_resolution.rs`. Kept in its own test binary on
|
||||||
|
//! purpose: installing a provider here would mask that test if they shared a
|
||||||
|
//! process.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sdk_installs_the_process_default_provider() {
|
||||||
|
assert!(
|
||||||
|
rustls::crypto::CryptoProvider::get_default().is_none(),
|
||||||
|
"this test must start with no provider installed"
|
||||||
|
);
|
||||||
|
|
||||||
|
onionwire_sdk::install_crypto_provider();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
rustls::crypto::CryptoProvider::get_default().is_some(),
|
||||||
|
"install_crypto_provider() left the process without a default provider"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Second call: idempotent, not a panic and not an error.
|
||||||
|
onionwire_sdk::install_crypto_provider();
|
||||||
|
|
||||||
|
// The call Arti makes that blew up on device.
|
||||||
|
let _ = rustls::ClientConfig::builder();
|
||||||
|
}
|
||||||
45
crates/onionwire-sdk/tests/provider_resolution.rs
Normal file
45
crates/onionwire-sdk/tests/provider_resolution.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
//! Regression test for the on-device failure: the APK built, installed and
|
||||||
|
//! opened, but pressing **Open** on the unlock screen died one call deep inside
|
||||||
|
//! Arti's rustls backend with
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! 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.
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! rustls 0.23 chooses its provider from its own `ring` / `aws-lc-rs` **crate
|
||||||
|
//! features**, not from which crypto crates happen to be linked. Arti reaches
|
||||||
|
//! rustls through `tor-rtcompat` with `default-features = false`, so neither
|
||||||
|
//! provider feature is on and rustls is compiled with *no* provider at all:
|
||||||
|
//! `ring` showing up in `cargo tree` (pulled in by `snow`, for Noise) proves
|
||||||
|
//! nothing. Everything compiles, the APK ships, and the process-default lookup
|
||||||
|
//! fails at runtime.
|
||||||
|
//!
|
||||||
|
//! This test is the invariant Arti relies on: the process default must be
|
||||||
|
//! resolvable **without** anyone calling `install_default()` first.
|
||||||
|
//!
|
||||||
|
//! Deliberately the only test in this file — a second test that installs a
|
||||||
|
//! provider would race with it inside the same test binary and could mask the
|
||||||
|
//! regression.
|
||||||
|
|
||||||
|
/// Building a `rustls` config the way Arti does, straight from the process
|
||||||
|
/// default, must not panic.
|
||||||
|
#[test]
|
||||||
|
fn rustls_default_provider_resolves_without_manual_install() {
|
||||||
|
assert!(
|
||||||
|
rustls::crypto::CryptoProvider::get_default().is_none(),
|
||||||
|
"this test must start with no provider installed"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pre-fix this panics with the device's exact message.
|
||||||
|
let _ = rustls::ClientConfig::builder();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
rustls::crypto::CryptoProvider::get_default().is_some(),
|
||||||
|
"rustls resolved no CryptoProvider — the crate feature that selects a \
|
||||||
|
provider is not enabled in this workspace"
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue