Refactor and improve plugin API, key management, and example app#54
Merged
chamodanethra merged 28 commits intomainfrom Dec 18, 2025
Merged
Refactor and improve plugin API, key management, and example app#54chamodanethra merged 28 commits intomainfrom
chamodanethra merged 28 commits intomainfrom
Conversation
This commit refactors the entire plugin architecture to use the Pigeon package for platform communication, replacing the previous manual `MethodChannel` implementation. This change introduces type-safe, structured data transfer between Dart and native code (Kotlin/Swift), significantly improving reliability and maintainability. ### Breaking Changes - **Type-Safe API:** All platform methods now return strongly-typed objects instead of raw `Map<String, dynamic>` or `String`. - `createKeys` returns `KeyCreationResult`. - `createSignature` returns `SignatureResult`. - `decrypt` returns `DecryptResult`. - `biometricAuthAvailable` returns a detailed `BiometricAvailability` object. - **Unified Error Handling:** A standardized `BiometricError` enum is now used across all platforms for consistent error reporting. - **Simplified Public API:** - The main `BiometricSignature` class API is simplified. Methods like `createSignature` and `decrypt` now take named parameters instead of dedicated options classes. - Removed legacy option classes: `SignatureOptions`, `DecryptionOptions`, `AndroidSignatureOptions`, etc. - Removed data classes like `FormattedValue`, `KeyCreationResult`, and `SignatureResult` from `key_material.dart`, as they are replaced by Pigeon-generated classes. - **Configuration Objects:** `AndroidConfig`, `IosConfig`, and `MacosConfig` are now immutable, Pigeon-generated data classes. - **Platform Interface:** The `BiometricSignaturePlatform` is updated to use the Pigeon-generated `BiometricSignatureApi`. ### Improvements - **Architecture:** The communication layer is now auto-generated by Pigeon, reducing boilerplate and the risk of serialization errors. - **Native Code:** - **Android:** The Kotlin implementation now implements the `BiometricSignatureApi` interface and uses coroutines for safer asynchronous operations. - **iOS/macOS:** The Swift implementations for both platforms now conform to the same `BiometricSignatureApi` protocol, unifying the codebase. - **Documentation:** `README.md` and the example app have been updated to reflect the new, simplified, and type-safe API. - **Example App:** The example app is overhauled to demonstrate the new API, featuring a cleaner UI and state management.
…ple app
This commit introduces a major version bump to `9.0.0`, primarily focusing on improved error handling in the native Android and iOS/macOS code and a significant enhancement to the example application to demonstrate ECIES encryption.
### Native Error Handling
- **`INVALID_INPUT` Error**: A new `BiometricError.invalidInput` code is introduced to specifically handle cases where the payload for signing or decryption is malformed (e.g., null, empty, or not valid Base64).
- **Android**: Adds `try-catch` blocks around payload processing (`sign`, `doFinal`, `Base64.decode`) to catch `IllegalArgumentException` and map it to the new `invalidInput` error code.
- **iOS/macOS**:
- Refactors error-producing paths to return the new `invalidInput` code for bad payloads.
- Standardizes the `Result` objects (`KeyCreationResult`, `SignatureResult`, `DecryptResult`) to ensure `error` and `code` fields are always populated correctly, especially on failure paths, making Dart-side error handling more consistent.
### Example App
- **ECIES Encryption**: Implements a full Dart-based ECIES encryption flow (`ECDH`, `X9.63 KDF`, `AES-GCM`) in the example app. This allows for end-to-end testing of the ECIES decryption functionality on both Android and Apple platforms.
- **RSA Encryption**: Adds Dart-based RSA encryption logic for testing decryption.
- **UI/UX**: The app now correctly encrypts the payload in Dart before passing it to the native `decrypt` method for a full round-trip verification.
### Housekeeping
- **Version Bump**: The plugin version is updated to `9.0.0` across `podspec` files.
- **macOS Build**: Adds a `DEVELOPMENT_TEAM` ID to the example app's macOS project file to facilitate easier local builds.
- **Pigeon**: Regenerates the pigeon files to include the new `invalidInput` error code and add macOS platform support.
This commit introduces support for `hex` and `raw` output formats for keys and signatures. It also enhances the result objects to include the raw `Uint8List` (or `ByteArray`/`Data`) for both public keys and signatures, in addition to the formatted string. ### Features - **New Key Formats**: Adds `hex` and `raw` to the `KeyFormat` enum. - For `raw` format, the `String` field will contain a Base64 representation, while the new bytes field holds the raw data. - **Raw Byte Data in Results**: - `KeyCreationResult` now includes a `publicKeyBytes` field. - `SignatureResult` now includes a `signatureBytes` field. - **Unified Formatting**: The `createSignature` method now accepts a `keyFormat` parameter to specify the desired output format for the signature and public key, ensuring consistency across all platforms (Android, iOS, macOS). ### Example App - Adds a dropdown menu to select the desired `KeyFormat`. - Updates the results display to show the raw byte count and a hex representation of the returned `publicKeyBytes` and `signatureBytes`.
This commit updates the example application to allow independent selection of the format for the public key and the signature. - Renames `_keyFormat` to `_publicKeyFormat` for clarity. - Adds a new state variable, `_signatureFormat`, initialized to `KeyFormat.base64`. - The UI is updated to include two separate dropdown menus: one for selecting the public key format and another for the signature format. - The `createKeys` method now uses `_publicKeyFormat`, while the `createSignature` method uses `_signatureFormat`.
This commit refactors the plugin's public API by replacing the generic `AndroidConfig`, `IosConfig`, and `MacosConfig` classes with operation-specific configuration objects (`CreateKeys`, `CreateSignature`, `Decrypt`). It also enhances the result objects and standardizes data formatting.
### API and Configuration
- **Granular Configs**: Introduces specific configuration classes for `createKeys`, `createSignature`, and `decrypt` (e.g., `AndroidCreateKeysConfig`, `IosCreateSignatureConfig`).
- **Common Parameters**: Moves shared parameters like `useDeviceCredentials` and `signatureType` from platform-specific configs to the top-level `createKeys` method for a cleaner API.
- **Payload/Signature Formatting**:
- Adds `SignatureFormat` and `PayloadFormat` enums (`base64`, `hex`, `raw`) to explicitly define the encoding for signature creation and decryption payloads.
- Implements native logic to handle HEX and Base64 encoding/decoding for signatures and payloads across all platforms.
- **Removed Deprecated Fields**: Cleans up platform configs by removing deprecated or redundant fields like `promptTitle` and `localizedReason`.
### Result Objects
- **Expanded Information**: `KeyCreationResult` and `SignatureResult` now include `algorithm` and `keySize`.
- **Hybrid Mode Details**: `KeyCreationResult` is enhanced for Hybrid EC mode with new fields: `isHybridMode`, `decryptingPublicKey`, `decryptingAlgorithm`, and `decryptingKeySize`.
### Native Implementation
- **Android**:
- Updates `BiometricSignaturePlugin.kt` to use the new granular config classes.
- Implements payload parsing and signature formatting based on the new format enums.
- Correctly populates the expanded fields in `KeyCreationResult` and `SignatureResult`.
- **iOS/macOS**:
- Refactors `BiometricSignaturePlugin.swift` on both platforms to align with the new API structure.
- Adds handlers for HEX and Base64 data formatting.
- Updates result objects to include new metadata like algorithm and key size.
### Example App
- The example app is updated to use the refactored API, demonstrating how to use the new configuration objects and handle the expanded result data, including displaying the `decryptingPublicKey` in hybrid mode.
This commit fixes an issue in the `createKeys` method for both iOS and macOS where it was incorrectly returning an EC key instead of an RSA key. The logic has been corrected to return the `rsaKeyStr` and `rsaTypedData` as the primary `publicKey` and `publicKeyBytes` in the `KeyCreationResult`. The hybrid mode fields (`isHybridMode`, `decryptingPublicKey`, etc.) have been removed, ensuring the result accurately reflects the creation of a standard RSA key. Additionally, the example app is updated to correctly select the public key for ECIES encryption, preferring `decryptingPublicKey` when available.
…ey encoding This commit introduces a `keyFormat` parameter to the `createSignature` method, allowing users to specify the desired encoding for the returned public key (PEM, BASE64, HEX, or RAW). ### Changes - **API**: The `createSignature` method across Dart, Kotlin, and Swift now accepts a `keyFormat` argument. - **Native Implementation**: - On Android, the public key is now formatted according to the provided `keyFormat` instead of being tied to the `signatureFormat`. - On iOS and macOS, the public key, which was previously always `nil`, is now correctly retrieved, formatted, and returned in the `SignatureResult`. - **Example App**: The example app has been updated with a dropdown menu to select the desired `keyFormat` for the signer's public key and now displays the key in the results.
This commit refactors the `BiometricType` enum and enhances the biometric type detection logic on Android. The `BiometricType` enum has been simplified by removing the `weak` and `strong` members and introducing a `multiple` member. This change is reflected across Dart, Kotlin (Android), and Swift (iOS/macOS) definitions. On Android, the `detectBiometricTypes` method is updated to more accurately identify available biometrics. It now uses reflection to access the `BiometricManager.getStrings` method, allowing it to parse the button label to detect face or iris scanners. This provides a more reliable detection mechanism than the previous simplified implementation.
This commit introduces a migration path for iOS to move legacy RSA private keys from the Keychain to a more secure storage mechanism. The new `migrateToSecureEnclave` function performs the following steps: - Generates a new EC key pair within the Secure Enclave. - Retrieves the legacy RSA private key, which was previously stored unencrypted in the Keychain as `kSecClassKey`. - Encrypts the RSA key data using the new EC public key with the `eciesEncryptionStandardX963SHA256AESGCM` algorithm. - Stores the encrypted RSA key in the Keychain as `kSecClassGenericPassword`, associated with the `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` attribute. - Deletes the old, unencrypted RSA key from the Keychain. This migration is triggered within the `sign` and `decrypt` methods when the `shouldMigrate` flag in `IosConfig` is true and a legacy RSA key is detected. Upon successful migration, the original signing or decryption operation proceeds.
This commit focuses on improving code quality and consistency across the project. It includes: - **Code Formatting**: Applies consistent formatting to Kotlin, Dart, and Pigeon files. This involves fixing indentation, removing extraneous newlines, and wrapping long parameter lists for better readability. - **Unused Imports**: Removes several unused import statements from the Android native code (`BiometricSignaturePlugin.kt`). - **Android Theme**: Sets a base `AppCompat` theme before showing the `BiometricPrompt` on Android to prevent potential theming crashes on some devices. - **Dart Code**: Standardizes method calls and widget layouts in the Dart codebase, particularly in the example app and platform interface.
This commit updates the iOS and macOS native code to format both RSA and EC public keys into the SubjectPublicKeyInfo (SPKI / X.509) format. Previously, the raw key data was returned, which required platform-specific handling on the client side. By wrapping the raw public key with the appropriate ASN.1 structure and algorithm identifiers, a standardized key format is ensured across all platforms. This simplifies the client-side code in the example app, as it no longer needs to parse raw PKCS#1 keys for RSA encryption on Apple platforms.
Prefixes keychain item aliases with the app's bundle identifier to prevent conflicts when multiple apps using this plugin are on the same macOS system. This change ensures that `biometricKeyAlias`, `ecKeyAlias`, and `invalidationSettingKey` are unique to each application, avoiding potential cross-app data access issues.
This commit removes the `systemCanceled` and `passcodeNotSet` cases from the `BiometricError` enum across all platforms (Dart, Android, iOS, and macOS). These error states were not being used, and their removal simplifies the error handling API. Additionally, the example app's UI has been updated to conditionally show the "Decrypt Support" switch only on Android, as it is not a user-configurable option on other platforms. Minor code comments and method names have also been cleaned up for clarity.
This commit refactors the `biometricKeyExists` logic for both iOS and macOS to accurately check for the existence and validity of cryptographic keys in all modes (EC-only, RSA-only, and Hybrid). ### Key Changes - **iOS/macOS `biometricKeyExists`:** - Implements a more robust check that correctly differentiates between EC-only and hybrid (EC+RSA) modes. - Checks for the existence of both the EC key and the encrypted RSA key to determine the operational mode. - When `checkValidity` is true, it now correctly loads the `invalidateOnEnrollment` setting and only checks for biometric changes if this setting is enabled. This ensures keys remain valid if created with the `biometryAny` policy. - **iOS/macOS Code Structure:** - Adds a `load()` function to `InvalidationSetting` to retrieve the stored policy. - Introduces a `biometryChangedOrUnknown()` helper in `DomainState` to centralize the logic for detecting biometric changes. - Removes the `opticID` case from the `availableBiometrics` check on iOS, as it is not a standard `LABiometryType`. - **Android:** - Removes a misleading comment regarding which public key is returned in hybrid mode.
Introduces the `biometricKeyExists` method to check if a biometric key already exists. This commit also adds a `checkValidity` parameter to the method, allowing for an optional validity check during the existence query. The example app has been updated to include a UI section for demonstrating this new functionality.
This commit revamps the `README.md` to reflect the new unified API for `createKeys`, `createSignature`, and `decrypt` methods, removing the previous `Options` classes. Key changes include: - **Unified API Documentation**: Updates method signatures to use named parameters directly instead of wrapper `Options` classes. - **Platform-Specific Architectures**: Clarifies the different security architectures and key modes available on Android vs. iOS/macOS, including RSA, EC, and Hybrid modes. - **Improved Android Setup**: Provides a clearer Kotlin code snippet for updating `MainActivity.kt` to extend `FlutterFragmentActivity`. - **Enhanced `biometricKeyExists` Docs**: Expands the explanation for `biometricKeyExists`, clarifying the purpose of the `checkValidity` parameter and its relation to biometric enrollment changes. - **Code Simplification**: Removes the full example app code from the main README to improve readability and focuses on concise usage snippets for each method. - **Import Cleanup**: Removes redundant import examples as they are no longer needed.
This commit renames the `getBiometricAvailability` method to `biometricAuthAvailable` for improved clarity and consistency with Dart conventions. The change is applied across the Pigeon API definition, the Dart interface, and the native implementations for Android, iOS, and macOS. This includes updating method names, channel names, and all relevant call sites to reflect the new name.
This commit introduces a new `deleteKeys()` method to securely remove all biometric key material from the device. It also enhances the API by making `payload` non-nullable for sign/decrypt operations and adds comprehensive inline documentation for all public-facing enums. ### Features - **`deleteKeys()` Method**: Adds a new method to delete all signing and decryption keys from the device's secure storage. This operation is idempotent, returning `true` even if no keys exist, simplifying reset or logout flows. ### Breaking Changes - The `payload` parameter in `createSignature()` and `decrypt()` is now non-nullable. The methods will now return an `INVALID_INPUT` error if an empty or blank payload is provided. ### Refinements - **API Documentation**: Adds detailed DartDoc comments to all public enums (`BiometricType`, `SignatureType`, `KeyFormat`, etc.) in the Pigeon interface file, which propagates to the generated platform code (Swift, Kotlin). - **Example Apps**: Updates all example apps (`banking_app`, `document_signer`, `passwordless_login`) to align with the latest API changes, including the non-nullable payload and updated method signatures. - **Unit Tests**: Refactors unit tests to match the modernized API, ensuring continued test coverage.
The document list is now unconditionally refreshed when returning from the document detail screen. This ensures the signed status is always up-to-date, removing the previous condition that only refreshed if the screen returned `true`.
This commit introduces a new `getKeyInfo()` method that provides detailed metadata about existing biometric keys without requiring user authentication. The previous `biometricKeyExists()` method is now a lightweight convenience wrapper around this new function. ### Features - **New `getKeyInfo()` Method**: - Replaces `biometricKeyExists()` as the primary way to inspect keys. - Returns a `KeyInfo` object containing metadata such as `exists`, `isValid`, `algorithm`, `keySize`, `isHybridMode`, and public keys for both signing and decryption (in hybrid mode). - Implemented for Android, iOS, and macOS. - **`KeyInfo` Data Class**: A new data class, `KeyInfo`, has been added to the Pigeon interface to provide a structured, type-safe way to access key metadata across platforms. - **Refactor `biometricKeyExists()`**: This method is now a simple wrapper around `getKeyInfo()`, maintaining backward compatibility while simplifying the underlying implementation. ### Example App - The example app UI has been updated to use `getKeyInfo()`, displaying the detailed key information in a dedicated card. This demonstrates the new functionality and provides a clearer view of the key's state. ### Documentation - The `README.md`, `EXAMPLES.md`, and `CHANGELOG.md` have been updated to document the new `getKeyInfo()` method, its parameters, and the `KeyInfo` return type. - Documentation for `biometricKeyExists()` is updated to reflect its new role as a convenience method.
This commit increases the package version from 8.5.0 to 9.0.0. It also includes the following changes: - Updates the `biometric_signature` dependency version across all example apps (`banking_app`, `document_signer`, `example`, `passwordless_login`) and their corresponding `Podfile.lock` files. - Makes the `payload` parameter non-nullable in the `createSignature` and `decrypt` methods in the native iOS code (`BiometricSignaturePlugin.swift`). - Refactors the pigeon-generated Dart file (`biometric_signature_platform_interface.pigeon.dart`) to improve code formatting and remove redundant empty lines.
Removes the hardcoded `DEVELOPMENT_TEAM` identifier from the iOS and macOS Xcode project files across all example applications. This change also adds empty `inputPaths` and `outputPaths` to the `[CP] Embed Pods Frameworks` build phase, and updates the `biometric_signature` pod version in `Podfile.lock` from 8.5.0 to 9.0.0 for the `document_signer` and `passwordless_login` macOS examples.
The `dart format` check is temporarily commented out in the `main.yml` GitHub Actions workflow.
This commit makes several fields nullable in the Pigeon definitions to improve type safety and robustness across platforms. It also introduces a helper script for code generation. ### Core Changes - **Pigeon Definitions**: Updates `BiometricAvailability` and `KeyInfo` in `pigeons/messages.dart` to make fields like `canAuthenticate`, `hasEnrolledBiometrics`, and `exists` nullable. - **Code Generation**: - Adds `scripts/generate_pigeon.sh` to automate code generation and sync the generated Swift API between iOS and macOS. - Removes explicit `macosOut` from `pigeons/messages.dart` in favor of the new script-based copy approach. - **Platform Implementations**: Regenerates Dart, Android (Kotlin), iOS (Swift), and macOS (Swift) code to reflect the nullability changes. - **Logic Updates**: - Updates `biometric_signature.dart` and the example app to safely handle nullable values using null-coalescing operators. - Adds `nilOrValue` helper usage in Swift generated code for safer decoding. ### Tooling - Pins Flutter version to `3.35.7` in the generation script.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This pull request includes significant changes to the plugin API, native code, and example app, focusing on enhancing key management, error handling, and platform communication while simplifying the overall architecture.
Key Changes
Major Features and Improvements:
getKeyInfo()for detailed key metadata retrieval.keyFormatandsignatureFormatoptions in APIs for flexible key representation in PEM, HEX, Base64, or raw bytes.Refactoring and Breaking Changes:
CreateKeys,CreateSignature).Native Enhancements:
INVALID_INPUT).Documentation and Examples:
README.md,EXAMPLES.md,CHANGELOG.md) to reflect changes.Checklist
Breaking Changes
MethodChannel.This PR improves consistency, security, and extensibility across the plugin while modernizing its architecture and making it easier to use.