Commit Graph

108 Commits

Author SHA1 Message Date
r-zakarya 3c1525e46f feat(android): end-to-end read path validated on device, add the container diagnostic
GET in 80 ms, decryption in 3893 ms, entries and trash counted, second call answers 304. try-open.js opens a container with Node so a failure can be attributed: a GCM tag that does not verify means wrong password OR altered file, and the app cannot tell which.
2026-08-15 00:31:01 +01:00
r-zakarya a641669150 fix(android): keep the ETag only when the decryption that followed succeeded
The harness stored the ETag on download, so a first attempt with no password left a valid tag behind: the next call answered 304 and there was nothing to decrypt, precisely when the right password had just been typed. Sending If-None-Match without holding the matching plaintext trades a download for having no data - the real client needs the same rule, the tag belonging to the cached vault.

Also drops an em dash from an exception message: Delphi reads a BOM-less source as ANSI and it reached the device as mojibake.
2026-08-15 00:06:30 +01:00
r-zakarya 8796bb341d feat(android): HTTP client for the sync endpoint, with ETag reuse
Fetches the opaque container and the ETag; a 304 skips both the download and the 4-second derivation, which is the whole point of keeping the tag between calls. Redirects are not followed - one to another host would send the bearer token there.

Local testing goes through adb reverse tcp:8080 tcp:80, which makes the phone reach the PC as 127.0.0.1 and satisfies the endpoint's loopback exemption. That needs usesCleartextTraffic in the manifest, marked for removal before shipping.
2026-08-14 23:37:06 +01:00
r-zakarya c684d9073f feat(android): adopt the platform PBKDF2, record the timing and what follows from it
Measured on device: Android derives the same key as WebCrypto on a non-ASCII password, so PBEKeySpec does encode as UTF-8 - the doubt that justified the Pascal implementation was unfounded. It is barely faster though, 3869 ms against 4139 ms: PBKDF2 gets no hardware acceleration.

So 4 seconds per container is incompressible, and the iteration count belongs to the desktop format. The answer is not to speed up the derivation but to stop repeating it: derive once per snapshot, then keep the vault locally under an Android Keystore key. Written up in android-client/README.md.
2026-08-14 23:30:15 +01:00
r-zakarya 4cc79a5216 fix(android): declare KeySpec so the native PBKDF2 call resolves, and report why it fails
generateSecret takes a KeySpec; passing a JObject made the bridge emit a signature no method matches, and the call failed at runtime. Swallowed as 'unavailable', which hid my own mistake for a full test round - the failure now carries its exception message.

Measured on device: the Pascal PBKDF2 takes 6651 ms, unusable per unlock. AES-GCM through JNI works, plaintext and hash match the vector, so the container format is validated end to end.
2026-08-14 23:22:56 +01:00
r-zakarya c607e26c4a feat(android): measure the native PBKDF2 against the vector instead of guessing
The Pascal loop takes 1736 ms on a desktop CPU, so several seconds on a phone - too slow for every unlock. SecretKeyFactory is native and roughly an order of magnitude faster, but PBEKeySpec takes a char[] and the provider picks the encoding, which was the reason for avoiding it.

The non-ASCII vector settles it on a real device: same key means the provider uses UTF-8 and we adopt it; a different key means the Pascal implementation stays. DecryptContainer keeps using the Pascal one until that measurement says otherwise.
2026-08-14 18:33:56 +01:00
r-zakarya 386bf74a2a feat(android): crypto self-test harness on the new FMX project
Two stages tested separately, because a global failure says nothing about where: PBKDF2 against the known derived key - which also runs on Win64, so that stage validates in seconds without deploying to a phone - then AES-GCM against the plaintext hash, Android only.

The vector is embedded as a unit rather than read from a file: no network, no deployment, so a first-run failure can only come from the decryption itself. Work runs on a thread; 600k HMAC rounds on the UI thread would have Android kill the app for not responding.
2026-08-14 18:29:12 +01:00
r-zakarya 02d89c58d2 feat(android): container crypto unit plus a known-answer test vector
PBKDF2 is implemented in Pascal rather than through javax.crypto SecretKeyFactory: PBEKeySpec takes a char[] and the provider decides how those chars become bytes, so a non-ASCII sync password would derive a different key than the browser and fail as 'wrong password'. AES-GCM does go through JNI - one call, hardware-backed, and WebCrypto's tag-appended layout is exactly what Cipher expects.

The vector is generated with WebCrypto itself and uses a non-ASCII password on purpose; an ASCII-only vector would have hidden that bug. It records the derived key separately from the plaintext so a failure points at one stage instead of the whole chain.
2026-08-14 18:04:39 +01:00