Skip to main content

synheart_auth 0.1.13 copy "synheart_auth: ^0.1.13" to clipboard
synheart_auth: ^0.1.13 copied to clipboard

Flutter plugin for Synheart device authentication — ECDSA P-256 keys backed by iOS Secure Enclave or Android Keystore.

Changelog #

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

0.1.13 - 2026-10-15 #

Changed #

  • Android: native SDK ai.synheart:synheart-auth 0.1.5 (was 0.1.4). Signing no longer fails after a key rotation, device identity survives a process restart, and an interrupted registration no longer blocks registerDevice forever.
  • iOS: requires the SynheartAuth pod 0.1.3 or later (was unpinned). CocoaPods trunk still served 0.1.0, which predates the Keychain absent-vs-unavailable fix (0.1.2); 0.1.3 adds registration recovery and re-registration after key loss. Pin :tag => 'v0.1.3' in the host Podfile until trunk carries 0.1.3.

0.1.12 - 2026-10-15 #

Fixed — Android secure storage restored from a backup no longer fails forever #

  • A secure store sealed by another Keystore key is now discarded. Android backup and device-to-device transfer restore synheart_core_secure_storage but never the Keystore master key that sealed it, so on the new install EncryptedSharedPreferences.create threw AEADBadTagException on every call. secureLoad retried and returned null each time, and everything that needs secure storage (device registration, a second runtime instance) failed until the user cleared the app's data. On that exception only, the store is deleted and recreated empty. Every other failure (Keystore not ready, user not unlocked, OEM faults) still keeps the store and retries, since discarding it then would orphan every sealed value.
  • The runtime's .smk-provisioned-<subject> markers are cleared with it. They live in plain app storage, so a restore brings them back too, and the runtime then refuses to mint a replacement storage master key (ERR_SECURE_STORAGE_UNAVAILABLE on every launch). Once the store is gone, every key they vouch for is gone, so they are removed from the app's data directory and the runtime mints a fresh key.
  • Host apps should also exclude their data from backup and device transfer (allowBackup="false" plus dataExtractionRules): nothing sealed by Keystore keys can be read after a restore anyway.

Fixed — signRequest now signs with the runtime-registered device key #

  • signRequest, isRegistered and getDeviceId never saw the device the runtime registered. They delegated to the native SDK's SynheartAuth.shared, which keeps its own appId-scoped store. Registration runs in synheart-core-runtime, which writes a different store through this plugin's crypto/storage callbacks — the device record at ("synheart-core", "device_record:<appId>") and a key keyed by device_id (Android Keystore synheart_device_<id>, iOS Keychain ai.synheart.auth.fficrypto). Nothing could populate the native SDK store (Dart registerDevice throws before reaching the channel, and on Android that store is in-memory), so standalone signRequest always threw NotRegistered, contrary to the README.
  • The three methods now read the runtime's identity first — usable only when both the record and its key exist, the runtime's own rule — and sign with that key through the same callbacks the runtime uses (NativeCryptoBridge on Android, the SynheartAuth pod's synheart_native_* on iOS). Message, signature encoding and header values match the runtime's Mode A ingest signing exactly (METHOD\npath\ntimestamp\n + body, SHA-256 ECDSA P-256, DER, standard base64, X-Synheart-Sig-Version: 1). No header or wire format changed. With no runtime identity they fall back to the native SDK path, so behaviour there is unchanged.
  • correctClockSkew now also offsets these signatures.
  • These calls now run off the platform main thread (secure-storage reads retry for up to ~1–1.5 s; Keystore / Secure Enclave signing blocks).
  • resetDeviceIdentity is unchanged and still clears only the native SDK store; it does not remove the runtime identity (the runtime's logout is the only supported route to a new device_id).

Changed #

  • Android: native ai.synheart:synheart-auth dependency 0.1.3 → 0.1.4 (StrongBox → TEE fallback in HardwareKeyManager; CryptoError keeps its cause).

0.1.11 - 2026-09-24 #

Fixed — iOS: the plugin's own Keychain bridge was never compiled #

  • ios/Classes/SynheartAuth/Crypto/NativeCryptoBridge.swift has been removed. The podspec compiles Classes/*.swift only, so that file — and with it the iOS half of the 0.1.10 secure_load fix — never made it into any host app. Every synheart_native_* symbol the Dart side resolves on iOS comes from the SynheartAuth pod (synheart-auth-swift), whose FFI module defines all eight (five crypto, three secure-storage). The 0.1.10 changelog entry for iOS therefore described code that was not running; the fix itself ships in synheart-auth-swift 0.1.2, which also applies the same absent-vs-unavailable classification to key_exists / sign_bytes. The Android half of 0.1.10 (NativeCryptoBridge.kt) is compiled and was correct as described.
  • Podspec and README now say so, and pin the host Podfile at SynheartAuth v0.1.2. The README previously claimed the pod was not on CocoaPods trunk; trunk carries 0.1.0, which is what an unpinned host resolves and which predates both the 0.1.1 App Attest timeout and this fix.

0.1.10 - 2026-09-23 #

Fixed — secure_load no longer reports a failed read as "no such key" #

  • iOS: synheart_native_secure_load returned NULL for every Keychain status other than success — including errSecInteractionNotAllowed (device locked / before first unlock) and errSecNotAvailable. The runtime reads NULL as "absent", so one locked-Keychain launch minted a new storage master key over the existing one and orphaned every sealed blob. Absence (errSecItemNotFound) is now the only immediate NULL; transient statuses are retried with a bounded ~1.5 s backoff; other failures are logged with their OSStatus. A non-UTF-8 item is reported as unavailable, not absent.
  • Android: secureLoad returned null whenever EncryptedSharedPreferences could not be created (Keystore not ready after boot, OEM Keystore faults) or the read threw. It now distinguishes absent (contains == false) from unavailable, retries the latter with a bounded ~1 s backoff, and logs the cause.
  • Both callbacks still have to return NULL when storage is genuinely unavailable — the C signature has no error channel. On runtime ≥ 0.31.1 the provisioning marker turns that into ERR_SECURE_STORAGE_UNAVAILABLE (retryable) rather than a re-mint; older runtimes keep the re-mint exposure.

0.1.9 - 2026-09-18 #

Fixed #

  • Android: pinned the CMake version to 3.22.1. The 3.18.1 toolchain ships an x86_64-only ninja, which cannot start on Apple silicon without Rosetta (macOS 27), breaking the native build. 3.22.1 is a universal binary and still satisfies the project's 3.18 minimum.

Documentation #

  • Corrected stale native SDK setup instructions and documented attestation failure handling.

0.1.8 - 2026-08-20 #

Added #

  • Android: Play Integrity failures are now classified by whether retrying can help. A new PlayIntegrityReasons helper maps Play Integrity error codes and throwables onto the reason vocabulary the runtime understands — transient, timeout, quota, unsupported, misconfigured, unknown — and NativeCryptoBridge.getAttestation emits it as the reason field of its {"format":"none","blob":"","reason":"…"} payload. Previously every failure looked alike to synheart-core-runtime, so a network blip worth retrying in seconds and a device that can never attest were both reported as permanent.
  • Android: unit tests covering the error-code and throwable classification.

Fixed #

  • Android: a missing or R8-stripped Play Integrity artifact no longer takes the process down. NoClassDefFoundError (an Error, not an Exception) used to escape getAttestation into JNI; PlayIntegrityReasons.forThrowable accepts Throwable and degrades these to unsupported.

Notes #

  • The reason tokens above are a cross-repo contract: they must stay in step with AttestationReason in synheart-core-runtime (crates/core-runtime/src/auth/types.rs and book/src/auth/platform.md). Adding a token is safe — the Rust side degrades anything unrecognized to unknown — but renaming an existing one is a breaking change.

0.1.7 - 2026-06-28 #

Changed #

  • Android: bump the native ai.synheart:synheart-auth dependency to 0.1.3, which adds the matching 30-second Play Integrity attestation timeout to the Kotlin SDK's PlayIntegrityAttestationProvider.
  • iOS: the native SynheartAuth Swift SDK dependency now targets 0.1.1, which bounds App Attest with a 30-second timeout. Host Podfiles pinning the pod from git should use :tag => 'v0.1.1'.

Fixed #

  • Android: NativeCryptoBridge.getAttestation now waits on the Play Integrity CountDownLatch with a 30-second timeout instead of blocking indefinitely. Play Integrity is supposed to invoke one of its success/failure listeners, but a stalled IntegrityService bind — no Play Store, an unlinked package, or a sideloaded debug build whose cloud project can't be resolved — can leave the Task pending forever. Before this fix the calling FFI isolate parked on latch.await() with no exit, so device registration never reached a terminal state (and could trip a SIGQUIT/ANR during the cold bind). A timeout is now treated as {"format":"none","blob":""} (attestation unavailable), letting the runtime fail fast and fall back / retry. This complements 0.1.5 (callbacks off the main thread) and 0.1.6 (main-thread guard) — those addressed slow resolution; this addresses resolution that never arrives.
  • Android: PlayIntegrityAttestationProvider (coroutine variant) wraps its token request in withTimeoutOrNull with the same 30-second cap, for parity with the JNI bridge path.

0.1.6 - 2026-05-26 #

Added #

  • Android: NativeCryptoBridge now warns via Log.w when any of its three blocking @JvmStatic methods (generateKey, signBytes, getAttestation) is invoked on the main thread. Each can park its caller for hundreds of milliseconds to several seconds (Keystore hardware-backed keygen, Play Integrity service bind, signature IPC). The contract today is that the runtime drives them from a background isolate's OS thread, but nothing in this file actually checked it. The guard is cheap (one Looper identity compare), only logs on the assertion failure, and adds no behaviour change on the correct path — it just makes a regression visible the instant it happens, instead of letting it surface as a silent ANR.

0.1.5 - 2026-05-24 #

Fixed #

  • Android: route Play Integrity callbacks (addOnSuccessListener / addOnFailureListener) through a dedicated single-thread executor instead of the default Android main thread. Symptom this fixes: during the first consent grant on a fresh install, the host app's UI froze for several seconds while the FFI worker isolate sat on latch.await() waiting for the Play Integrity result — the callback was queued behind whatever else the main thread was doing (Flutter input dispatch, surface compositor work, the IntegrityService binding callback itself). With a dedicated executor (named syn-integrity-callback, daemon thread) the latch resolves the instant Play Services hands the result back, regardless of main- thread load. Doesn't reduce the cold Play Integrity bind latency itself (that's owned by Google Play Services) but it stops the resolution from being held behind unrelated main-thread work.

0.1.4 - 2026-05-24 #

Documentation #

  • iOS: replaced the misleading "consumed via SwiftPM" podspec comment with the actual incantation host Podfiles need. SynheartAuth is distributed from git, not the CocoaPods trunk spec repo, so each consuming app must declare the source explicitly:

    pod 'SynheartAuth',
        :git => 'https://github.com/synheart-ai/synheart-auth-swift.git',
        :tag => 'v0.1.0'
    

    No code or behaviour change; the s.dependency 'SynheartAuth' line (restored in 0.1.3) remains. The previous comment implied SwiftPM was sufficient, which led at least one downstream consumer to drop the pod source and then hit Unable to resolve module dependency: 'SynheartAuth' at compile time.

0.1.3 - 2026-05-08 #

Fixed #

  • iOS: Re-added s.dependency 'SynheartAuth' to synheart_auth.podspec. The 0.1.2 podspec dropped the dep on the assumption that consumers would pull the native SDK via SwiftPM, but pub.dev consumers using CocoaPods (the default Flutter iOS toolchain) had no header/framework search path for the SynheartAuth module, so import SynheartAuth in SynheartAuthPlugin.swift failed with Unable to resolve module dependency: 'SynheartAuth'. The dep is now declared explicitly so CocoaPods wires up the search paths.

0.1.2 - 2026-05-08 #

Changed #

  • Android: bumped Maven dep to ai.synheart:synheart-auth:0.1.1. Picks up DeviceRegistrar register/rotate race fix, ClockSkewTracker auto-applying skew on every signed request, AuthNetworkClient HTTP timeouts, and §13 audit-log PII redaction.

0.1.1 - 2026-05-08 #

Fixed #

  • Android: Replaced filesystem-relative source include of synheart-auth-kotlin with the published Maven Central artifact (ai.synheart:synheart-auth:0.1.0). 0.1.0 only resolved when the Flutter plugin and the Kotlin SDK were sibling directories on disk — pub.dev consumers (where the package lives in ~/.pub-cache/) saw import ai.synheart.auth.registration.AttestationProvider fail to resolve. 0.1.0 has been retracted on pub.dev for this reason.

0.1.0 - 2026-03-04 #

Initial release.

  • Request signing for HSI ingest via the ai.synheart.auth MethodChannel (iOS Secure Enclave / Android Keystore on the native side).
  • signRequest() returns the six wire headers (X-App-ID, X-Device-ID, X-Synheart-Signature, X-Synheart-Timestamp, X-Synheart-Nonce, X-Synheart-Sig-Version).
  • correctClockSkew(serverTimestamp) to align the device clock with the server.
  • Typed DeviceAuthState lifecycle and SynheartAuthError hierarchy.
  • SynheartAuth.forTesting(bridge:) for mock injection.

Note: registerDevice() and rotateKey() are exposed on the API for parity but currently throw UnsupportedError. Device registration and key rotation are performed by the native runtime; this Flutter shell only signs.

0
likes
130
points
455
downloads

Documentation

API reference

Publisher

verified publishersynheart.ai

Weekly Downloads

Flutter plugin for Synheart device authentication — ECDSA P-256 keys backed by iOS Secure Enclave or Android Keystore.

Repository (GitHub)
View/report issues
Contributing

Topics

#authentication #flutter #ecdsa #secure-enclave #keystore

License

unknown (license)

Dependencies

flutter

More

Packages that depend on synheart_auth

Packages that implement synheart_auth