android: V4.9 + V4.10 Kotlin ports + KeystoreStorage adapter
Some checks failed
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Test / test (push) Has been cancelled

Pure-JVM additions to shade-android (no Android SDK needed):
- V4.9 blob primitives: BlobKdf (HKDF deriveBlobSlotId/Key/SigningSeed),
  BlobAead (nonce||ct||tag with shade-profile-aad-v1:<slot> AAD),
  BlobClient (java.net.http with hand-written canonical JSON signing
  matching TS signPayload output), Profile high-level namespace.
- V4.10 approval helpers: CanonicalProfileBlob schema with denormalized
  trustedApproverFingerprints, build/sign/verify proxy approvals via
  length-prefixed u16 BE UTF-8 canonical signing payload.
- Password KDFs: scrypt + argon2id via Bouncy Castle, NFKC-normalized.
- SessionStateJson at-rest serializer for persistence layer.

Cross-platform vectors (test-vectors/blob.json, approval.json) gate
byte-identical output between TS and Kotlin, including a TS-signed
Ed25519 signature the Kotlin port verifies and reproduces (Ed25519 is
deterministic).

New shade-android-keystore sibling Gradle module (Android-specific):
- KeystoreMasterKey: hardware-backed AES-256-GCM with BIOMETRIC_STRONG
  gating, StrongBox-backed when available, invalidated on enrollment.
- BiometricUnlock: coroutine wrapper around BiometricPrompt with
  tagged cancellation/failure exceptions.
- KeystoreStorage: StorageProvider over biometric-gated AES-encrypted
  SharedPreferences with AAD-bound row keys.

All 25 SDK packages typecheck clean; 104 SDK tests + 24 new Kotlin
tests + 11 cross-platform vector tests all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 17:38:15 +02:00
parent 1bd7037a6d
commit 188c3db56a
26 changed files with 3181 additions and 1 deletions

View File

@@ -5,6 +5,84 @@ All notable changes to Shade are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased — 2026-05-09] — Android: V4.9/V4.10 ports + KeystoreStorage adapter
The Kotlin side of the v4.10 cross-host approval routing FR. With this
release every primitive Prism Plan 04 needs on the phone has a Kotlin
implementation that produces byte-identical output to the TS reference.
**`shade-android` (pure-JVM, no Android SDK needed)**
- V4.9 blob primitives ported: `deriveBlobSlotId / deriveBlobKey /
deriveBlobSigningSeed`, `aeadSeal / aeadOpen` (`nonce(12) || ct||tag`
format with `shade-profile-aad-v1:<slotIdHex>` AAD),
`ed25519PublicKeyFromSeed`, `slotIdToHex`. Lives under `no.zyon.shade.blob`.
- `BlobClient` HTTP wrapper for `/v1/blob/<slotId>` (java.net.http) —
GET/PUT/DELETE with the same canonical-JSON-sorted-keys signing form
the TS server expects. Hand-written JSON canonicalizer keeps Ed25519
signing-input bytes identical to TS `signPayload` output.
- `Profile` high-level namespace (`createProfileNamespace`) — bundles
KDF + AEAD seal/open + BlobClient calls into the same shape as
`@shade/sdk`'s `createProfileNamespace`.
- V4.10 approval helpers ported: canonical profile schema
(`CanonicalProfileBlob`, `ProfileHostEntry`, `ProfileClientEntry`)
with parse/serialize/upsert/setTrustedApprover mutators that
re-derive `trustedApproverFingerprints[]` invariantly.
`buildApprovalRequest`, `signProxyApproval`, `verifyProxyApproval`,
and the load-bearing `canonicalApprovalSigningBytes` (length-prefixed
u16 BE UTF-8). All under `no.zyon.shade.approval`.
- Password KDFs: `deriveMasterKey` (scrypt) + `deriveMasterKeyArgon2id`
via Bouncy Castle. NFKC-normalize string inputs to match TS.
- New `SessionStateJson` serializer for at-rest persistence of
`IdentityKeyPair` / `SignedPreKey` / `OneTimePreKey` / `SessionState`.
**Cross-platform vectors**
- `test-vectors/blob.json` — V4.9 blob KDF + AEAD round-trip. Three
(master, app) cases plus two pinned AEAD seal/open round-trips.
- `test-vectors/approval.json` — V4.10 approval signing-payload bytes
+ a TS-signed Ed25519 signature the Kotlin port verifies. Ed25519 is
deterministic, so the Kotlin sign-with-the-same-seed produces the
same 64 bytes back — that's checked too.
- Both wired into `CrossPlatformVectorTest` so any byte-divergence
fails Gradle within the existing 60-second parity gate.
**`shade-android-keystore` (new sibling module — Android-specific)**
- `KeystoreMasterKey` — hardware-backed AES-256-GCM master key.
- `BIOMETRIC_STRONG` gating only (Class 3 assurance) — explicitly
excludes `DEVICE_CREDENTIAL` so a stolen-device-with-known-PIN
can't unlock Shade.
- StrongBox-backed when available; transparent fallback to TEE.
- `setInvalidatedByBiometricEnrollment(true)`: a newly enrolled
fingerprint/face wipes the key, forcing credential rebootstrap.
- `BiometricUnlock` — coroutine wrapper around `BiometricPrompt`.
Tagged exceptions (`BiometricCancelledException` /
`BiometricFailedException`) so callers handle UX without writing
callback boilerplate.
- `KeystoreStorage` — `StorageProvider` impl over `SharedPreferences`
with each row AES-GCM-encrypted under the keystore key. AAD = the
pref key string so a substituted-prefs swap fails to open. Exposes
`unlock(BiometricUnlock)` / `lock()` / `forgetEverything()` for
app-lifetime gating (single biometric prompt at start, in-memory
unlock thereafter).
- Builds as a standard AAR (`com.android.library`, AGP 8.7.3), depends
transitively on `:shade-android` for protocol types and on
`androidx.biometric:biometric:1.2.0-alpha05`.
**Threat model**
The keystore key never leaves the secure environment — encrypt/decrypt
operations happen in the TEE/StrongBox. A compromised app process can
ask the TEE to use the cipher only after biometric authentication
within the same `Cipher` instance. Combined with `Profile.delete()`
+ `forgetEverything()`, this gives a credible erase path: zero
recoverable plaintext after a rebootstrap.
Instrumented tests for `KeystoreStorage` are deferred (need an
emulator/device); the pure-JVM `SessionStateJson` round-trip is unit-
tested in `:shade-android`.
## [4.10.0] — 2026-05-09 — cross-host approval routing primitives
Prism filed a follow-up feature request