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 "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||||
implementation project(':capacitor-android')
|
implementation project(':capacitor-android')
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
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.
|
// 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'
|
implementation 'org.torproject:arti-mobile:1.7.0.1'
|
||||||
// arti pulls androidx.webkit in transitively but only at runtime; we
|
// arti pulls androidx.webkit in transitively but only at runtime; we
|
||||||
// compile against ProxyController/WebViewFeature in TorController, so
|
// compile against ProxyController/WebViewFeature in TorController, so
|
||||||
@@ -78,6 +80,49 @@ dependencies {
|
|||||||
|
|
||||||
apply from: 'capacitor.build.gradle'
|
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 {
|
try {
|
||||||
def servicesJSON = file('google-services.json')
|
def servicesJSON = file('google-services.json')
|
||||||
if (servicesJSON.text) {
|
if (servicesJSON.text) {
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ import okhttp3.Response;
|
|||||||
* Capacitor WebView traffic (every {@code fetch} and relay {@code WebSocket})
|
* Capacitor WebView traffic (every {@code fetch} and relay {@code WebSocket})
|
||||||
* is routed through Tor. No changes to the TypeScript HTTP layer are needed.
|
* 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
|
* <p>The enabled flag is persisted to {@link SharedPreferences} by
|
||||||
* {@link SharedPreferences} by {@link TorPlugin} and read here at startup from
|
* {@link TorPlugin} and read here at startup from {@link MainActivity}, so arti
|
||||||
* {@link MainActivity}. arti is started <em>before</em> the WebView loads so
|
* auto-starts on a cold launch <em>before</em> the WebView loads — there is no
|
||||||
* there is no pre-bootstrap leak window.
|
* 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
|
* <p>Pluggable transports (obfs4 via IPtProxy) are intentionally not wired up
|
||||||
* yet — the builder already exposes {@code setObfs4Port}/{@code setBridgeLines}
|
* yet — the builder already exposes {@code setObfs4Port}/{@code setBridgeLines}
|
||||||
@@ -104,13 +106,17 @@ public class TorController {
|
|||||||
return instance;
|
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) {
|
public static boolean isEnabled(Context context) {
|
||||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
return prefs.getBoolean(KEY_ENABLED, false);
|
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) {
|
public static void setEnabled(Context context, boolean enabled) {
|
||||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
.edit()
|
.edit()
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ import com.getcapacitor.annotation.CapacitorPlugin;
|
|||||||
* Capacitor bridge for the Tor (arti) mode.
|
* Capacitor bridge for the Tor (arti) mode.
|
||||||
*
|
*
|
||||||
* <p>Mirrors {@link DittoNotificationPlugin}'s pattern: JS configures native
|
* <p>Mirrors {@link DittoNotificationPlugin}'s pattern: JS configures native
|
||||||
* state, native owns the work. The enabled flag is persisted only — arti is
|
* state, native owns the work. On a cold launch arti auto-starts from
|
||||||
* actually started at launch from {@link MainActivity} (apply on relaunch).
|
* {@link MainActivity} based on the persisted flag. At runtime the settings
|
||||||
* Live bootstrap status is pushed to JS via the {@code torStatus} event.
|
* 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}.
|
* <p>JS interface: see {@code src/lib/tor.ts}.
|
||||||
*/
|
*/
|
||||||
@@ -43,7 +46,11 @@ public class TorPlugin extends Plugin {
|
|||||||
call.resolve(ret);
|
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
|
@PluginMethod
|
||||||
public void setEnabled(PluginCall call) {
|
public void setEnabled(PluginCall call) {
|
||||||
Boolean enabled = call.getBoolean("enabled");
|
Boolean enabled = call.getBoolean("enabled");
|
||||||
|
|||||||
+10
-1
@@ -23,7 +23,16 @@ allprojects {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
// Guardian Project's experimental Maven repo, hosting the prebuilt
|
// Guardian Project's experimental Maven repo, hosting the prebuilt
|
||||||
// org.torproject:arti-mobile AAR (Tor in Rust) used for the optional Tor mode.
|
// 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;
|
lowBandwidthMode: boolean;
|
||||||
/**
|
/**
|
||||||
* Route all app traffic through the Tor network (arti). **Android only** —
|
* Route all app traffic through the Tor network (arti). **Android only** —
|
||||||
* ignored on web and iOS. The actual proxy is installed natively at app
|
* ignored on web and iOS. The Advanced Settings toggle applies changes live
|
||||||
* startup, so changes take effect on the next launch (see `src/lib/tor.ts`
|
* via the native `start`/`stop` bridge (arti starts/stops immediately and the
|
||||||
* and the native `TorController`).
|
* 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.
|
* Default: false.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+4
-1
@@ -21,7 +21,10 @@ export interface UseTor {
|
|||||||
error: string | null;
|
error: string | null;
|
||||||
/** Tor exit-node IP from the last successful check, when connected. */
|
/** Tor exit-node IP from the last successful check, when connected. */
|
||||||
exitIp: string | null;
|
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>;
|
setEnabled: (enabled: boolean) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -15,7 +15,13 @@ export interface TorStatusEvent {
|
|||||||
export interface TorPlugin {
|
export interface TorPlugin {
|
||||||
/** Whether Tor is enabled in persisted native preferences. */
|
/** Whether Tor is enabled in persisted native preferences. */
|
||||||
isEnabled(): Promise<{ enabled: boolean }>;
|
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>;
|
setEnabled(options: { enabled: boolean }): Promise<void>;
|
||||||
/** Start arti now (live) and persist enabled=true. */
|
/** Start arti now (live) and persist enabled=true. */
|
||||||
start(): Promise<void>;
|
start(): Promise<void>;
|
||||||
|
|||||||
Reference in New Issue
Block a user