fix(sdk): enable rustls' ring provider — APK died at 'Failed to start / Could not determine the process-level CryptoProvider' #15
4 changed files with 69 additions and 6 deletions
1
crates/onionwire-sdk/Cargo.lock
generated
1
crates/onionwire-sdk/Cargo.lock
generated
|
|
@ -3588,6 +3588,7 @@ checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
|
|||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
|
|
|
|||
|
|
@ -35,11 +35,14 @@ 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
|
||||
# unless this crate turns a provider feature on, rustls compiles with zero
|
||||
# providers and the first TLS config built from the process default fails at
|
||||
# runtime. (Currently: no provider feature — see the regression test in
|
||||
# `tests/provider_resolution.rs`.)
|
||||
rustls = { version = "0.23", default-features = false }
|
||||
# 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"] }
|
||||
thiserror = "2"
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,38 @@
|
|||
//! no TUI/ratatui type leaks into the AAR.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use onionwire::node::Node;
|
||||
use onionwire::qr;
|
||||
|
||||
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)]
|
||||
#[uniffi(flat_error)]
|
||||
pub enum WireError {
|
||||
|
|
@ -88,6 +113,10 @@ pub struct Wire {
|
|||
/// that is minutes, not seconds. Call it off the main thread.
|
||||
#[uniffi::export(async_runtime = "tokio")]
|
||||
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)
|
||||
.await
|
||||
.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();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue