Pin arti dependency and reconcile Tor activation docs
Address two follow-ups from the Tor (arti) MR review. Supply-chain hardening for the arti-mobile AAR, a native artifact with network-proxy privileges: - Pin the gpmaven Maven source to an immutable commit SHA (guardianproject/gpmaven@b3ee2a6) instead of the mutable `master` branch, so a force-push or new commit can't silently change what we resolve. - Verify the resolved AAR's SHA-256 at build time (verifyArtiChecksum, wired ahead of assemble/bundle). A mismatch fails the build before any APK is produced. Scoped to the one privileged artifact rather than enabling global dependency verification, which would force-verify every transitive dep. Reconcile stale "apply on relaunch" / "next app launch" doc comments in AppContext.ts, tor.ts, useTor.ts, TorController.java, and TorPlugin.java with the actual behavior: the Advanced Settings toggle activates Tor live via start/stop (arti starts/stops immediately, relay layer remounts); the persisted flag only governs cold-launch auto-start.
This commit is contained in:
@@ -63,8 +63,10 @@ dependencies {
|
||||
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||
implementation project(':capacitor-android')
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
// Tor in Rust (arti) — prebuilt AAR from Guardian Project's gpmaven repo.
|
||||
// Tor in Rust (arti) — prebuilt AAR from Guardian Project's gpmaven repo
|
||||
// (source pinned to an immutable commit in the root build.gradle).
|
||||
// Provides org.torproject.arti.ArtiProxy used by TorController.
|
||||
// The resolved AAR is checksum-verified below (verifyArtiChecksum).
|
||||
implementation 'org.torproject:arti-mobile:1.7.0.1'
|
||||
// arti pulls androidx.webkit in transitively but only at runtime; we
|
||||
// compile against ProxyController/WebViewFeature in TorController, so
|
||||
@@ -78,6 +80,49 @@ dependencies {
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
// Supply-chain pin for the arti-mobile AAR. It carries a native library with
|
||||
// network-proxy privileges and is sourced from Guardian Project's gpmaven repo,
|
||||
// so we verify the resolved artifact's SHA-256 against a value pinned here.
|
||||
// A mismatch fails the build before any APK is assembled. To bump arti, update
|
||||
// the version + repo commit (root build.gradle) and replace this checksum after
|
||||
// re-verifying a fresh download.
|
||||
ext.artiMobileSha256 = 'cbdb34ce3cdb32f755f25f6dd05a2d1eb9a44025a17ec9202729816e2a3af05b'
|
||||
|
||||
task verifyArtiChecksum {
|
||||
doLast {
|
||||
def artifact = configurations.releaseRuntimeClasspath
|
||||
.resolvedConfiguration
|
||||
.resolvedArtifacts
|
||||
.find { it.moduleVersion.id.group == 'org.torproject' && it.moduleVersion.id.name == 'arti-mobile' }
|
||||
|
||||
if (artifact == null) {
|
||||
throw new GradleException("arti-mobile artifact not found on the runtime classpath; cannot verify Tor native library.")
|
||||
}
|
||||
|
||||
def actual = java.security.MessageDigest.getInstance("SHA-256")
|
||||
.digest(artifact.file.bytes)
|
||||
.collect { String.format('%02x', it) }
|
||||
.join('')
|
||||
|
||||
if (actual != project.ext.artiMobileSha256) {
|
||||
throw new GradleException(
|
||||
"arti-mobile AAR checksum mismatch!\n" +
|
||||
" expected: ${project.ext.artiMobileSha256}\n" +
|
||||
" actual: ${actual}\n" +
|
||||
" file: ${artifact.file}\n" +
|
||||
"Refusing to build a Tor proxy from an unverified native artifact."
|
||||
)
|
||||
}
|
||||
logger.lifecycle("Verified arti-mobile AAR checksum (${actual}).")
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.matching { it.name.startsWith('assemble') || it.name.startsWith('bundle') }.configureEach {
|
||||
dependsOn verifyArtiChecksum
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
|
||||
@@ -36,10 +36,12 @@ import okhttp3.Response;
|
||||
* Capacitor WebView traffic (every {@code fetch} and relay {@code WebSocket})
|
||||
* is routed through Tor. No changes to the TypeScript HTTP layer are needed.
|
||||
*
|
||||
* <p>Activation is "apply on relaunch": the enabled flag is persisted to
|
||||
* {@link SharedPreferences} by {@link TorPlugin} and read here at startup from
|
||||
* {@link MainActivity}. arti is started <em>before</em> the WebView loads so
|
||||
* there is no pre-bootstrap leak window.
|
||||
* <p>The enabled flag is persisted to {@link SharedPreferences} by
|
||||
* {@link TorPlugin} and read here at startup from {@link MainActivity}, so arti
|
||||
* auto-starts on a cold launch <em>before</em> the WebView loads — there is no
|
||||
* pre-bootstrap leak window. Beyond that, activation is live: the settings
|
||||
* toggle calls {@link #start}/{@link #stop} (bridged through {@link TorPlugin}),
|
||||
* which start or stop arti immediately while also updating the persisted flag.
|
||||
*
|
||||
* <p>Pluggable transports (obfs4 via IPtProxy) are intentionally not wired up
|
||||
* yet — the builder already exposes {@code setObfs4Port}/{@code setBridgeLines}
|
||||
@@ -104,13 +106,17 @@ public class TorController {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/** Whether Tor is enabled in persisted preferences (read at next launch). */
|
||||
/** Whether Tor is enabled in persisted preferences (read at cold-launch startup). */
|
||||
public static boolean isEnabled(Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
return prefs.getBoolean(KEY_ENABLED, false);
|
||||
}
|
||||
|
||||
/** Persist the enabled flag. Takes effect on the next app launch. */
|
||||
/**
|
||||
* Persist the enabled flag only. This controls whether arti auto-starts on
|
||||
* the next cold launch; it does not start or stop arti now. For live
|
||||
* activation call {@link #start}/{@link #stop}, which also persist the flag.
|
||||
*/
|
||||
public static void setEnabled(Context context, boolean enabled) {
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
|
||||
@@ -10,9 +10,12 @@ import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
* Capacitor bridge for the Tor (arti) mode.
|
||||
*
|
||||
* <p>Mirrors {@link DittoNotificationPlugin}'s pattern: JS configures native
|
||||
* state, native owns the work. The enabled flag is persisted only — arti is
|
||||
* actually started at launch from {@link MainActivity} (apply on relaunch).
|
||||
* Live bootstrap status is pushed to JS via the {@code torStatus} event.
|
||||
* state, native owns the work. On a cold launch arti auto-starts from
|
||||
* {@link MainActivity} based on the persisted flag. At runtime the settings
|
||||
* toggle activates Tor live via {@link #start}/{@link #stop}, which start or
|
||||
* stop arti immediately and update the persisted flag. ({@link #setEnabled}
|
||||
* persists the flag only, without touching the running proxy.) Live bootstrap
|
||||
* status is pushed to JS via the {@code torStatus} event.
|
||||
*
|
||||
* <p>JS interface: see {@code src/lib/tor.ts}.
|
||||
*/
|
||||
@@ -43,7 +46,11 @@ public class TorPlugin extends Plugin {
|
||||
call.resolve(ret);
|
||||
}
|
||||
|
||||
/** Persist the enabled flag. Applied on the next app launch. */
|
||||
/**
|
||||
* Persist the enabled flag only, without starting or stopping arti now.
|
||||
* Controls whether arti auto-starts on the next cold launch. For live
|
||||
* activation use {@link #start}/{@link #stop}.
|
||||
*/
|
||||
@PluginMethod
|
||||
public void setEnabled(PluginCall call) {
|
||||
Boolean enabled = call.getBoolean("enabled");
|
||||
|
||||
+10
-1
@@ -23,7 +23,16 @@ allprojects {
|
||||
mavenCentral()
|
||||
// Guardian Project's experimental Maven repo, hosting the prebuilt
|
||||
// org.torproject:arti-mobile AAR (Tor in Rust) used for the optional Tor mode.
|
||||
maven { url "https://raw.githubusercontent.com/guardianproject/gpmaven/master" }
|
||||
//
|
||||
// Pinned to an immutable commit SHA rather than the mutable `master`
|
||||
// branch: this artifact ships a native library with network-proxy
|
||||
// privileges, so we don't want a force-push or new commit to gpmaven
|
||||
// silently changing what we resolve. To bump arti, update both the
|
||||
// commit below and the checksum pin in `app/build.gradle`, and re-verify
|
||||
// the SHA-256 against a fresh download.
|
||||
//
|
||||
// Commit: guardianproject/gpmaven@b3ee2a63eec4ce37ea22fcc6b1ff009f406f2b13
|
||||
maven { url "https://raw.githubusercontent.com/guardianproject/gpmaven/b3ee2a63eec4ce37ea22fcc6b1ff009f406f2b13" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,9 +290,11 @@ export interface AppConfig {
|
||||
lowBandwidthMode: boolean;
|
||||
/**
|
||||
* Route all app traffic through the Tor network (arti). **Android only** —
|
||||
* ignored on web and iOS. The actual proxy is installed natively at app
|
||||
* startup, so changes take effect on the next launch (see `src/lib/tor.ts`
|
||||
* and the native `TorController`).
|
||||
* ignored on web and iOS. The Advanced Settings toggle applies changes live
|
||||
* via the native `start`/`stop` bridge (arti starts/stops immediately and the
|
||||
* relay layer remounts). The flag is also persisted natively so arti
|
||||
* auto-starts on the next cold launch (see `src/lib/tor.ts` and the native
|
||||
* `TorController`).
|
||||
*
|
||||
* Default: false.
|
||||
*/
|
||||
|
||||
+4
-1
@@ -21,7 +21,10 @@ export interface UseTor {
|
||||
error: string | null;
|
||||
/** Tor exit-node IP from the last successful check, when connected. */
|
||||
exitIp: string | null;
|
||||
/** Persist the enabled flag natively. Takes effect on next app launch. */
|
||||
/**
|
||||
* Toggle Tor live: starts/stops arti immediately and persists the flag
|
||||
* natively (so it auto-starts again on the next cold launch).
|
||||
*/
|
||||
setEnabled: (enabled: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -15,7 +15,13 @@ export interface TorStatusEvent {
|
||||
export interface TorPlugin {
|
||||
/** Whether Tor is enabled in persisted native preferences. */
|
||||
isEnabled(): Promise<{ enabled: boolean }>;
|
||||
/** Persist the enabled flag. Applied on the next app launch. */
|
||||
/**
|
||||
* Persist the enabled flag only, without starting or stopping arti now.
|
||||
* The persisted value controls whether arti auto-starts on the next cold
|
||||
* launch. For live activation use {@link TorPlugin.start} /
|
||||
* {@link TorPlugin.stop} (what the settings toggle calls), which both
|
||||
* change state immediately *and* persist the flag.
|
||||
*/
|
||||
setEnabled(options: { enabled: boolean }): Promise<void>;
|
||||
/** Start arti now (live) and persist enabled=true. */
|
||||
start(): Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user