Skip to content

Repository files navigation

fdroid_dart

CI

A self-contained Dart/Flutter client for the F-Droid repository vertical: mirror-aware transport, native cryptographic trust, streaming index-v2 ingestion, and a local SQLite (Drift) catalog with FTS5 search.

Unlike a store client that trusts a locally-synced metadata file, fdroid_dart owns the whole chain — transport, signature verification, diff application, parse, and catalog DB — because an F-Droid repo has no trusted local source to lean on.

Scope boundary. Acquisition ends at a verified APK artifact (downloadApk + verifyApk). Install, launch, and installed-state enumeration are deliberately out of scope.

Features

  • Mirror-aware transport — conditional GET, hash-verify-or-discard, mirror rotation, and streaming APK download with progress.
  • Native trust module (C++23 + OpenSSL 3) — the only signature checks:
    • entry.jar JAR v1 signature (the trust root): full CMS → .SFMANIFEST.MFentry.json chain walk, pinned to the repo's signing certificate SHA-256 (no trust-on-first-use).
    • APK Signature Scheme v2/v3 (v3.1-aware) with a JAR v1 (SHA-256 or SHA-1) fallback for older apps.
  • index-v2 ingestion — streaming parser and RFC 7386 merge-patch for diffs, both walked at the byte level so a full sync never materializes the whole document.
  • Per-repo Drift/SQLite catalog — version-centric schema, FTS5 search, locale-fallback queries, and an atomic database swap.
  • Compatibility filteringlatestCompatible picks the best version for a DeviceProfile (SDK bounds × ABI × features × release channel).
  • Content-addressed media cache — icons and screenshots, hash-verified and LRU-capped.

Usage

import 'package:fdroid_dart/fdroid_dart.dart';

final client = FdroidClient(storageDir: '/path/to/app-support');

final repo = client.addRepo(
  'https://f-droid.org/repo',
  fingerprint:
      '43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab',
);

// Sync (staged progress; the DB is swapped in atomically at the end).
await for (final p in repo.sync()) {
  print(p.runtimeType); // SyncFetchingEntry, SyncVerifyingEntry, ...
}

// Query the local catalog.
final results = await repo.search('podcast', locale: 'de');
final detail = await repo.app('org.fdroid.fdroid');
final icon = detail?.icon == null ? null : await repo.mediaFile(detail!.icon!);

// Acquire: pick the best version for this device, download, verify.
final best = await repo.latestCompatible('org.fdroid.fdroid',
    profile: DeviceProfile.arm64);
if (best != null) {
  await for (final e in repo.downloadApk(best)) {
    if (e is DownloadVerified) {
      final verdict = client.verifyApk(e.path, expectedSigners: best.signers);
      print('verified via scheme ${verdict.scheme.name}');
      // Install is out of scope — hand the artifact to the platform.
    }
  }
}

await client.close();

Architecture

Layer Where
Transport (Transport, HttpTransport, FakeTransport) lib/src/transport
Native trust (C ABI over OpenSSL) native/src
Trust facade (FFI + fake) lib/src/trust
entry.json model, rollback guard, diff/full planning lib/src/entry
index-v2 model, streaming parser, merge-patch lib/src/index
Drift catalog DB (schema, writer, FTS5, queries) lib/src/db
FdroidClient / RepoHandle / media cache lib/src/api

The C ABI in native/src/fdroid_trust.h is the only FFI surface; the shared library exports just those symbols. Its fd_status_t integer values are a stable ABI contract with FdroidTrustFault.code in lib/src/errors.dart.

Example app

example/fdroid_catalog is a browse-only Flutter app demonstrating the whole vertical: first-sync with staged progress, FTS5 search, category browse, and an app-detail screen with permission and anti-feature panels plus a download-and-verify flow.

cd example/fdroid_catalog && flutter run -d linux

Building & requirements

  • Dart SDK ≥ 3.7 (Flutter for the example app).
  • Native build (driven automatically by the native-assets hook): a C++23 compiler, CMake ≥ 3.22, Ninja, OpenSSL 3, and zlib.
dart pub get
dart test          # builds the native trust asset via the CMake hook

Testing

  • Dart: dart analyze / dart test (unit tests run against in-package fakes and an in-memory catalog — no network, no trust FFI).
  • Native: cmake -S native -B native/build -DFDROID_BUILD_TESTS=ON then ctest --test-dir native/build; sanitizers via -DENABLE_SANITIZER=asan|ubsan.
  • Differential oracle: scripts/apksigner_oracle.sh cross-checks the APK corpus against Android's apksigner.
  • Fuzzing: libFuzzer harnesses under native/test/fuzz (-DFDROID_BUILD_FUZZERS=ON).

CI (.github/workflows/ci.yml) runs all of the above on every push and pull request.

Clean-room

The trust module is implemented from public specifications and black-box oracles (apksigner) only; no GPL fdroidclient source was consulted.

License

Apache-2.0 © 2026 Joel Winarske

About

F-Droid repository client for Dart/Flutter: mirror-aware transport, native signature verification (JAR v1 + APK Signature Scheme v2/v3), streaming index-v2 ingestion, and a local SQLite (Drift) catalog with FTS5 search. Acquisition ends at a verified APK; install is out of scope.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages