Add com.codename1.security: biometric auth + secure storage#4987
Open
shai-almog wants to merge 11 commits into
Open
Add com.codename1.security: biometric auth + secure storage#4987shai-almog wants to merge 11 commits into
shai-almog wants to merge 11 commits into
Conversation
Promotes biometric authentication (Touch ID, Face ID, Android BiometricPrompt) from the FingerprintScanner cn1lib into core so it is available alongside Location, Capture, and the other first-class device APIs. Public surface mirrors Flutter's local_auth: typed BiometricType list, typed BiometricError codes, AuthenticationOptions builder, and a stopAuthentication() cancel. iOS port wraps LocalAuthentication.framework + Security.framework (SecItemAdd / SecItemCopyMatching / SecItemDelete). Android port keeps the cn1lib's dual path -- FingerprintManager on API 23-28 and BiometricPrompt on API 29+; the BiometricPrompt + BiometricManager calls go through a reflection adapter (BiometricsApi29) because the cn1-binaries android.jar predates API 28. JavaSE port adds a Simulate -> Biometric Simulation submenu (Available toggle, per-modality enrollment, configurable next-call outcome) so apps can be exercised in the simulator. The Maven plugin always links LocalAuthentication.framework on iOS and injects USE_BIOMETRIC / USE_FINGERPRINT permissions on Android so apps don't need build hint surgery. The existing FingerprintScanner cn1lib continues to work unchanged for projects that depend on it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 19 screenshots: 19 matched. |
Collaborator
Author
|
Compared 11 screenshots: 11 matched. |
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
CI surfaced two issues against the initial commit:
1. CodenameOne/ and Ports/CLDC11/ run a validator that rejects classic /**
Javadoc -- the codebase has standardised on /// markdown comments. Convert
all 8 files in com.codename1.security to that style and translate
{@link X} / {@code Y} to [X] / `Y` markdown.
2. IOSNative.m calls com_codename1_impl_ios_IOSBiometrics_* and
com_codename1_impl_ios_IOSSecureStorage_* callbacks but did not #include
the ParparVM-generated headers, so clang complained about implicit
function declarations and the iOS native build, build-ios, build-ios-metal
and packaging jobs all failed identically. Add the two #includes alongside
the existing com_codename1_impl_ios_IOSImplementation.h include.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JDK 17 (and JDK 8) javac on CI defaults file.encoding to US-ASCII;
file.encoding=UTF-8 is only the JDK 18+ default per JEP 400. That made
the em-dash in AndroidBiometrics.java:64 ('-- values are stable per
AOSP') fatal with three "unmappable character" errors and broke the
Ant compile step. Replace with ASCII double-hyphen to match the
ASCII-only invariant documented in CLAUDE memory.
Also remove the unreachable UnrecoverableEntryException catch in
AndroidSecureStorage.getSecretKey -- keyStore.getKey() only declares
UnrecoverableKeyException, not its parent, so the second clause is
dead code (javac warning) and the import is unused.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-test (8) flagged LI_LAZY_INIT_STATIC on the lazy `fallback` field in both Biometrics.getInstance() and SecureStorage.getInstance() -- the if-null-then-assign-then-return pattern isn't thread-safe and the workflow treats this rule as forbidden. The fallback stubs are cheap immutable objects, so promote them to `private static final` eager fields and return them with a ternary. Also drop the explicit no-arg constructors on AuthenticationOptions, StubBiometrics and StubSecureStorage (PMD UnnecessaryConstructor) -- the compiler-generated default has the correct visibility in each case (public, package-private, package-private). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 108 screenshots: 108 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
…ject CodeQL alert "Insecure local authentication" (java/android/insecure- local-authentication, alert #108) correctly flagged that AndroidBiometrics.authenticate() granted access on the bare onAuthenticationSucceeded callback. Without a CryptoObject the success path can be reached via runtime hooking tools (Frida) without the user ever actually authenticating. Add a single-use AES probe key to the AndroidKeyStore with setUserAuthenticationRequired(true) and (API 24+) setInvalidatedByBiometricEnrollment(true), initialise a Cipher under it, and pass that Cipher to BiometricPrompt / FingerprintManager as the CryptoObject. The success callbacks then run authedCipher.doFinal(PROBE_PLAINTEXT); a real biometric unlocks the cipher and the doFinal returns, a spoofed callback fails because the Keystore refuses the operation. Probe key is recreated on KeyPermanentlyInvalidatedException (which happens when the user enrols a new biometric -- the security property CodeQL is asking us to enforce). The key is independent of the AndroidSecureStorage per-account keys, so SecureStorage entries are not affected when the probe is rotated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tighten the legacy FingerprintManager success path so CodeQL's dataflow analyser can see unambiguously that the cipher used for doFinal() comes from the AuthenticationResult.getCryptoObject() that the OS returned, not the local probe Cipher variable. If the result is missing a CryptoObject (shouldn't happen, but a spoofed callback might supply null) we now fail with AUTHENTICATION_FAILED rather than falling back to the local cipher reference. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round of fixes from the PR review: 1. **Non-abstract base classes.** Biometrics and SecureStorage are now concrete with no-op default implementations -- StubBiometrics and StubSecureStorage are deleted. The default class is returned as the fallback for unsupported ports, so app code never needs a null check or a platform if. Reduces class count and eliminates two public-package types that were really impl details. 2. **Six SIC_INNER_SHOULD_BE_STATIC_ANON warnings fixed** in AndroidBiometrics + AndroidSecureStorage by converting the self-contained Runnables / CipherWork lambdas to actual Java 8 lambdas (the Android port already compiles -source 1.8). 3. **SpotBugs forbidden_rules now applied across every project**, not only core-unittests. A regression in android/ios spotbugs now fails CI exactly the way a regression in core does. 4. **Conditional injection.** IPhoneBuilder and AndroidGradleBuilder only inject LocalAuthentication.framework / USE_BIOMETRIC / USE_FINGERPRINT when the bytecode scanner observes any com.codename1.security class. Apps that never touch the API pay nothing. Mirrored in BuildDaemon (legacy build server). 5. **JavaSEBiometrics installBuildHints** -- first time the API is touched in the simulator, set ios.NSFaceIDUsageDescription on the project (placeholder text the developer should overwrite). Mirrors the historical FingerprintScanner cn1lib pattern. 6. **/// javadoc on every public getter / setter / enum constant** under com.codename1.security; STRONG/WEAK/IRIS now explained; iOS/Android/JavaSE/fallback behaviour spelled out throughout. 7. **Developer guide chapter** docs/developer-guide/Biometric- Authentication.asciidoc covering quick start, platform support matrix, build hints, prompt configuration, typed errors, simulator workflow, and the Keystore-bound success-verification security note. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Contributor
Cloudflare Preview
|
build-test (8) runs the Android port Ant build with -source 1.6 (the
Maven build uses 1.8, so my local checks missed this). Revert the
Java-8 lambdas added in the previous commit to named private static
inner classes -- same SpotBugs SIC_INNER_SHOULD_BE_STATIC_ANON
outcome, Java 1.6 compatible.
Also clean up the new docs/developer-guide/Biometric-Authentication
asciidoc against the project's Vale style ruleset: 8 Microsoft.
Contractions findings (is not / will not / cannot / does not / was not
must be contractions), one Microsoft.Auto (don't hyphenate
"auto-sets"), one Microsoft.Adverbs ("silently" removed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three editorial cleanups requested in PR review: 1. Drop the comparison to Flutter local_auth in Biometrics.java javadoc and in the developer guide intro. The API stands on its own. 2. Avoid asciidoc em-dashes (--) throughout the new chapter. Replace with colons in headings and definition lists, semicolons or full sentences in prose. The four-dash code-block delimiters (----) are intentional asciidoc syntax and remain. 3. Drop the "legacy build daemon" wording. The build daemon is the build server and is not legacy. Reword to "the Codename One Maven plugin and the build daemon both automatically inject ...". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous round of feedback flagged that the build-hint injection wasn't visible in JavaSEPort -- it was buried in JavaSEBiometrics' public methods. Move the logic to JavaSEPort.installBiometricsBuildHints IfNeeded() and call it from getBiometrics() and getSecureStorage(). The semantics are unchanged: the first time the application touches either API in the simulator we detect whether ios.NSFaceIDUsageDescription is set on the project; if not, write a placeholder so the next iOS device build doesn't crash. The developer should overwrite the placeholder text with their app-specific localised reason before shipping (Apple rejects builds with the placeholder default). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vale's Microsoft.HeadingColons rule flags lowercase first words after a heading colon. Capitalise "Authenticate" and "Secure storage" in the two Quick start subsection titles. Fallout from the em-dash removal in the previous commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
com.codename1.security, so Touch ID / Face ID / Android BiometricPrompt are first-class APIs alongsideDisplay.capturePhoto,LocationManager, etc.local_auth: typedBiometricTypelist (FINGERPRINT, FACE, IRIS, STRONG, WEAK), typedBiometricErrorcodes (NOT_AVAILABLE, NOT_ENROLLED, LOCKED_OUT, PERMANENTLY_LOCKED_OUT, USER_CANCELED, KEY_REVOKED, ...), fluentAuthenticationOptionsbuilder, and a workingstopAuthentication()cancel.SecureStoragesibling API gives biometric-gated keychain storage (iOS Security framework, Android AES/AndroidKeyStore with the cn1lib's hard-won workarounds for Samsung 8.0.0 and the API 33 carve-out preserved).LocalAuthentication.frameworkon iOS andUSE_BIOMETRIC/USE_FINGERPRINTpermissions on Android, so apps don't need build-hint surgery.API
Port implementations
IOSBiometrics+IOSSecureStorage): wrapsLAContext.evaluatePolicy(...)andSecItemAdd/SecItemCopyMatching/SecItemDelete. 9 new natives added toIOSNative.java/IOSNative.m. Non-ARC-safe (matches the iOS port'sCLANG_ENABLE_OBJC_ARC=NOconfig).stopAuthentication()calls[LAContext invalidate]and resolves the in-flightAsyncResourcewithUSER_CANCELED— the cn1lib previously logged "not implemented on iOS" here.AndroidBiometrics+AndroidSecureStorage): keeps the cn1lib's dual path —FingerprintManageron API 23-28,BiometricPrompton API 29+. The API 29+ paths go through a small reflection adapter (BiometricsApi29) because the cn1-binariesandroid.jarpredates API 28; runtime resolution works fine on devices that support it. Preserves the cn1lib's non-obvious workarounds (Samsung 8.0.0 cipher-init quirk,setUserAuthenticationRequiredAPI 33 carve-out per FingerprintScanner Simulator - native j2me theme is not taken from project #8).JavaSEBiometrics+JavaSESecureStorage): adds aSimulate -> Biometric Simulationsubmenu next to the existingLocation SimulationandPush Simulation. Lets the developer toggle hardware availability, per-modality enrollment (Face / Touch / Iris), and the outcome of the nextauthenticate()call. State persists across simulator restarts viajava.util.prefs. Secure-storage round-trip works againstPreferencesso apps can be exercised end-to-end without a device.Test plan
-source 1.5 -target 1.5(mvn -pl core install -Plocal-dev-javase -DskipTests)JavaSEBiometrics/JavaSESecureStoragecodenameone-maven-pluginbuilds with the auto-injection edits toIPhoneBuilderandAndroidGradleBuilderBiometricType,BiometricError,AuthenticationOptions,BiometricExceptionagainst the built core jarSimulate -> Biometric Simulation -> Hardware Available+Face ID Enrolled, verifyBiometrics.getAvailableBiometrics()returns[FACE]and that cycling the "Next outcome" radio produces the matching success/error in the test appBiometricError.LOCKED_OUTafter repeated failures and thatstopAuthentication()dismisses the prompt[LAContext invalidate]cancellation path and theSecureStorageround-tripSecureStorageinvalidation: write a value, enroll a new biometric, verify the nextget(...)fails withBiometricError.KEY_REVOKEDon both Android and iOSios.add_libsor any biometric build hint; confirm the generated Xcode project linksLocalAuthentication.frameworkand the generatedAndroidManifest.xmlcontains<uses-permission android:name="android.permission.USE_BIOMETRIC" />BiometricsAPI in the same module without symbol conflicts🤖 Generated with Claude Code