|
| 1 | +# Acquisition archives |
| 2 | + |
| 3 | +androidqf writes each acquisition directly to a ZIP archive instead of first |
| 4 | +building an acquisition directory. This keeps storage use bounded and allows |
| 5 | +encryption to protect evidence from the moment it reaches the host. |
| 6 | + |
| 7 | +## Output files |
| 8 | + |
| 9 | +The `-output` option selects the output directory. If it is omitted, androidqf |
| 10 | +uses the current working directory. The output filename is based on the |
| 11 | +acquisition UUID: |
| 12 | + |
| 13 | +| Configuration | Output | |
| 14 | +|---|---| |
| 15 | +| No `key.txt` found | `<UUID>.zip` | |
| 16 | +| Valid age recipient in `key.txt` | `<UUID>.zip.age` | |
| 17 | + |
| 18 | +androidqf looks for `key.txt` in the current working directory and then beside |
| 19 | +the executable. A key in the current working directory takes precedence. |
| 20 | + |
| 21 | +The archive contains the collected module outputs documented in the main |
| 22 | +[README](../README.md#how-to-use), plus these acquisition-level entries: |
| 23 | + |
| 24 | +| Entry | Purpose | |
| 25 | +|---|---| |
| 26 | +| `acquisition.json` | Acquisition UUID, timestamps, androidqf version and device information. | |
| 27 | +| `command.log` | Debug-level command and acquisition log, when log output was produced. | |
| 28 | +| `hashes.csv` | SHA-256 integrity records for preceding plaintext archive entries. | |
| 29 | + |
| 30 | +`hashes.csv` has two CSV fields per row: the ZIP entry name and its lowercase |
| 31 | +SHA-256 digest. The digest covers the plaintext, uncompressed entry content. |
| 32 | +The file does not contain a hash record for itself. |
| 33 | + |
| 34 | +## Streaming and failed pulls |
| 35 | + |
| 36 | +Module output is written to the archive as it is collected. Data that can be |
| 37 | +produced reliably as one stream, such as a backup or bug report, is streamed |
| 38 | +directly into its ZIP entry. |
| 39 | + |
| 40 | +Files that may disappear or fail partway through an ADB pull are staged and |
| 41 | +validated before androidqf creates their ZIP entries. Consequently, a failed |
| 42 | +pull does not leave an empty or partial entry that appears valid in |
| 43 | +`hashes.csv`. |
| 44 | + |
| 45 | +Archive completion writes `acquisition.json`, `command.log` and `hashes.csv`, |
| 46 | +then closes the ZIP and, when enabled, the age encryption stream. A failure in |
| 47 | +any of these operations is reported as a failed acquisition; androidqf does not |
| 48 | +print the acquisition-success message for an archive that could not be |
| 49 | +finalized. |
| 50 | + |
| 51 | +## Encrypted acquisitions |
| 52 | + |
| 53 | +When `key.txt` is present, the ZIP stream is passed directly through age into |
| 54 | +`<UUID>.zip.age`: |
| 55 | + |
| 56 | +```text |
| 57 | +device data -> ZIP writer -> age encryption -> <UUID>.zip.age |
| 58 | +``` |
| 59 | + |
| 60 | +No plaintext ZIP archive or acquisition directory is created. To decrypt an |
| 61 | +acquisition: |
| 62 | + |
| 63 | +```bash |
| 64 | +age --decrypt -i /path/to/privatekey.txt \ |
| 65 | + -o <UUID>.zip <UUID>.zip.age |
| 66 | +``` |
| 67 | + |
| 68 | +The decrypted ZIP can then be opened with standard ZIP tooling. |
| 69 | + |
| 70 | +### Validated encrypted staging |
| 71 | + |
| 72 | +A ZIP entry cannot be removed after it has been created. For device files that |
| 73 | +must be completely pulled or inspected before entry creation, androidqf uses a |
| 74 | +temporary encrypted staging file during encrypted acquisitions. |
| 75 | + |
| 76 | +Temporary staging uses MinIO SIO's DARE format with authenticated |
| 77 | +ChaCha20-Poly1305 encryption. A new random 256-bit key is generated for each |
| 78 | +staged file and retained only in process memory. DARE provides authenticated |
| 79 | +random access, allowing APK signature verification without writing a plaintext |
| 80 | +APK to disk or loading the entire APK into memory. |
| 81 | + |
| 82 | +```text |
| 83 | +ADB pull |
| 84 | + -> ChaCha20-Poly1305 DARE temporary file |
| 85 | + -> authenticated seekable plaintext view in memory |
| 86 | + -> APK certificate verification |
| 87 | + -> final age-encrypted ZIP stream |
| 88 | +``` |
| 89 | + |
| 90 | +The staging file is deleted and its in-memory key is cleared after use. DARE is |
| 91 | +an internal temporary format only; the delivered acquisition remains a |
| 92 | +standard age-encrypted ZIP stream. |
| 93 | + |
| 94 | +During an unencrypted acquisition, files requiring validation are staged in an |
| 95 | +ordinary temporary file before being committed to the ZIP. A process or host |
| 96 | +crash may leave such a plaintext temporary file behind. Use age encryption |
| 97 | +when plaintext-at-rest protection is required. |
| 98 | + |
| 99 | +## APK handling |
| 100 | + |
| 101 | +androidqf uses an in-memory buffer of up to 500 MB for APK processing. APKs |
| 102 | +within that limit are certificate-checked from memory for both encrypted and |
| 103 | +unencrypted acquisitions. |
| 104 | + |
| 105 | +Larger APKs use seekable staging so certificate verification still occurs: |
| 106 | + |
| 107 | +| Acquisition | APK larger than 500 MB | |
| 108 | +|---|---| |
| 109 | +| Encrypted | Authenticated ChaCha20-Poly1305 DARE staging; no plaintext temporary APK. | |
| 110 | +| Unencrypted | Plaintext temporary staging. | |
| 111 | + |
| 112 | +The certificate metadata is recorded in `packages.json` in both cases. If the |
| 113 | +operator selects removal of APKs signed by a trusted certificate, that choice |
| 114 | +also applies to APKs larger than 500 MB. A trusted APK selected for removal is |
| 115 | +not added to the archive. |
| 116 | + |
| 117 | +## Operational considerations |
| 118 | + |
| 119 | +- Keep sufficient free space for the output archive and, when staging is |
| 120 | + required, one additional copy of the largest staged file. |
| 121 | +- Treat a finalization error as a failed acquisition. The output may be |
| 122 | + incomplete or unreadable and should not be treated as finalized evidence. |
| 123 | +- Preserve the age identity separately from the acquisition host when the |
| 124 | + operator should not be able to decrypt collected evidence under duress. |
0 commit comments