Follow a passkey request
1 · Server challenge — missing from the inspected demo
A real relying party issues a fresh challenge and retains verification context. Apps.swift instead constructs timestamp-based challenge strings for key.danger.plus. That tests a request shape; it is not a complete Danger+ login.
2 · Swift bridge — implemented
AuthenticationServices.swift queues pk.create or pk.get, with a request ID, RP ID and challenge. Creation also includes user ID and name. A pending callback is matched when pk-result arrives.
3 · Android Credential Manager — implemented
DeviceServices builds options and MainActivity runs the platform request in a coroutine. Missing activity handlers fail. User verification is preferred, not required. Provider availability and app/domain association remain runtime dependencies.
4 · Response returns — implemented
The platform response JSON travels through pk-result. Unknown request IDs are ignored. Success here means a credential response was received; it does not establish server acceptance.
5 · Server verification and session — not demonstrated
A relying party must validate challenge, origin, RP binding, signature and its verification policy, then consume the challenge and establish the account session. This facade does not implement that server.
Android documents app/domain association requirements and the server-backed sign-in flow. A browser login alone does not verify the Android app signing association.
Where data and keys live
Swift process · plaintext mirror
Keychain.set changes the in-process string mirror immediately and queues kc.set. On a cache miss, string(forKey:) queues one deduplicated kc.get and returns nil now. A later kc-value hydrates the mirror and requests a redraw.
Android process · AES-GCM operation
The host obtains or creates daybreak-kc through AndroidKeyStore, then uses AES-256-GCM on the UTF-8 string. Plaintext exists in app memory during use.
Preferences · encrypted blob
The host stores Base64(IV || ciphertext-and-tag) in daybreak_keychain under kc:<key>. Base64 is encoding; GCM supplies encryption and integrity. Entry names remain visible.
Keystore · key handle
The AES key is managed by Android Keystore. The current code does not request StrongBox or inspect the security level. Hardware backing must be checked on each device.
The passkey provider manages separate credentials; this AES key is not the passkey. Provider passkeys may sync, while this host key belongs to the Android Keystore path. See Android’s security-level guidance.
What the implementation does not yet guarantee
- Swift mirror updates precede host encryption. SharedPreferences.apply is asynchronous; no positive persistence receipt returns.
- The host maps both absent and unreadable entries to null. Callers cannot distinguish those outcomes.
- Separate host threads and late read hydration need ordering tests. Request IDs do not provide per-key versioning.
- The facade handles strings, not full SecItem classes, access groups or accessibility flags.
- LocalAuthentication uses a separate screen-lock confirmation path. The AES key is not configured to require that prompt per use.
- The registration handler catches a get-credential cancellation exception. Registration cancellation may reach generic failure; verify platform behavior before recording a pass.
Evidence a service workbench should show
Record platform response, server acceptance and session creation as separate events, correlated by request/challenge identifiers. Do not publish private keys, tokens or full credential responses. Exercise wrong-origin and replay rejection. For storage, acknowledge persistence, restart the process and verify a test value; test missing and corrupt entries independently.
These are proposed acceptance checks, not new device results. Source inspected: ShimKitCore/AuthenticationServices.swift, Keychain.swift, LocalAuthentication.swift and Apps.swift; Android DeviceServices.kt and MainActivity.kt. No device authentication certification is claimed.
References and evidence
Implementation references describe a working-tree snapshot. They are not all present in a public repository. The source inventory records file hashes; historical notes are not new device tests.