Skip to content

Add com.codename1.security: biometric auth + secure storage#4987

Merged
shai-almog merged 12 commits into
masterfrom
feat/biometrics-core
May 21, 2026
Merged

Add com.codename1.security: biometric auth + secure storage#4987
shai-almog merged 12 commits into
masterfrom
feat/biometrics-core

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

  • Promotes biometric authentication from the FingerprintScanner cn1lib into core under com.codename1.security, so Touch ID / Face ID / Android BiometricPrompt are first-class APIs alongside Display.capturePhoto, LocationManager, etc.
  • Public surface designed to match Flutter's local_auth: typed BiometricType list (FINGERPRINT, FACE, IRIS, STRONG, WEAK), typed BiometricError codes (NOT_AVAILABLE, NOT_ENROLLED, LOCKED_OUT, PERMANENTLY_LOCKED_OUT, USER_CANCELED, KEY_REVOKED, ...), fluent AuthenticationOptions builder, and a working stopAuthentication() cancel.
  • SecureStorage sibling 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).
  • Build plugin auto-injects LocalAuthentication.framework on iOS and USE_BIOMETRIC / USE_FINGERPRINT permissions on Android, so apps don't need build-hint surgery.
  • The existing FingerprintScanner cn1lib stays untouched; projects that depend on it keep building unchanged.

API

Biometrics b = Biometrics.getInstance();
if (b.canAuthenticate()) {
    b.authenticate("Unlock your wallet").onResult((ok, err) -> {
        if (err != null) {
            BiometricError code = ((BiometricException) err).getError();
            // branch on the typed code
        } else {
            // authenticated
        }
    });
}

// Secure storage round-trip
SecureStorage.getInstance().set("Save token", "user@example.com", token);
SecureStorage.getInstance().get("Unlock token", "user@example.com")
    .onResult((value, err) -> { /* ... */ });

Port implementations

  • iOS (IOSBiometrics + IOSSecureStorage): wraps LAContext.evaluatePolicy(...) and SecItemAdd/SecItemCopyMatching/SecItemDelete. 9 new natives added to IOSNative.java / IOSNative.m. Non-ARC-safe (matches the iOS port's CLANG_ENABLE_OBJC_ARC=NO config). stopAuthentication() calls [LAContext invalidate] and resolves the in-flight AsyncResource with USER_CANCELED — the cn1lib previously logged "not implemented on iOS" here.
  • Android (AndroidBiometrics + AndroidSecureStorage): keeps the cn1lib's dual path — FingerprintManager on API 23-28, BiometricPrompt on API 29+. The API 29+ paths go through a small reflection adapter (BiometricsApi29) because the cn1-binaries android.jar predates 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, setUserAuthenticationRequired API 33 carve-out per FingerprintScanner Simulator - native j2me theme is not taken from project #8).
  • JavaSE simulator (JavaSEBiometrics + JavaSESecureStorage): adds a Simulate -> Biometric Simulation submenu next to the existing Location Simulation and Push Simulation. Lets the developer toggle hardware availability, per-modality enrollment (Face / Touch / Iris), and the outcome of the next authenticate() call. State persists across simulator restarts via java.util.prefs. Secure-storage round-trip works against Preferences so apps can be exercised end-to-end without a device.

Test plan

  • Core module builds clean under -source 1.5 -target 1.5 (mvn -pl core install -Plocal-dev-javase -DskipTests)
  • JavaSE port builds and packages JavaSEBiometrics / JavaSESecureStorage
  • Android port builds (reflection adapter compiles against the cn1-binaries android.jar)
  • codenameone-maven-plugin builds with the auto-injection edits to IPhoneBuilder and AndroidGradleBuilder
  • Static smoke test exercising BiometricType, BiometricError, AuthenticationOptions, BiometricException against the built core jar
  • Run a sample in the JavaSE simulator: toggle Simulate -> Biometric Simulation -> Hardware Available + Face ID Enrolled, verify Biometrics.getAvailableBiometrics() returns [FACE] and that cycling the "Next outcome" radio produces the matching success/error in the test app
  • Build an Android sample app for an API 23 fingerprint device (legacy path) and an API 29+ device (BiometricPrompt path); verify BiometricError.LOCKED_OUT after repeated failures and that stopAuthentication() dismisses the prompt
  • Build an iOS sample app for a Face ID iPhone and a Touch ID iPad; verify [LAContext invalidate] cancellation path and the SecureStorage round-trip
  • SecureStorage invalidation: write a value, enroll a new biometric, verify the next get(...) fails with BiometricError.KEY_REVOKED on both Android and iOS
  • Build a sample app that does not declare ios.add_libs or any biometric build hint; confirm the generated Xcode project links LocalAuthentication.framework and the generated AndroidManifest.xml contains <uses-permission android:name="android.permission.USE_BIOMETRIC" />
  • Coexistence: confirm an app depending on the original FingerprintScanner cn1lib can also use the new Biometrics API in the same module without symbol conflicts

🤖 Generated with Claude Code

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>
Comment thread Ports/Android/src/com/codename1/impl/android/AndroidBiometrics.java Fixed
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

Compared 20 screenshots: 20 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

Compared 11 screenshots: 11 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

Compared 110 screenshots: 110 matched.

Native Android coverage

  • 📊 Line coverage: 11.74% (6594/56180 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.49% (33109/348817), branch 4.14% (1372/33132), complexity 5.16% (1638/31754), method 8.95% (1328/14836), class 14.90% (301/2020)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 11.74% (6594/56180 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.49% (33109/348817), branch 4.14% (1372/33132), complexity 5.16% (1638/31754), method 8.95% (1328/14836), class 14.90% (301/2020)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 665.000 ms
Base64 CN1 encode 138.000 ms
Base64 encode ratio (CN1/native) 0.208x (79.2% faster)
Base64 native decode 990.000 ms
Base64 CN1 decode 115.000 ms
Base64 decode ratio (CN1/native) 0.116x (88.4% faster)
Image encode benchmark status skipped (SIMD unsupported)

shai-almog and others added 3 commits May 20, 2026 03:42
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>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 20, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 347 seconds

Build and Run Timing

Metric Duration
Simulator Boot 61000 ms
Simulator Boot (Run) 2000 ms
App Install 22000 ms
App Launch 11000 ms
Test Execution 308000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 861.000 ms
Base64 CN1 encode 2065.000 ms
Base64 encode ratio (CN1/native) 2.398x (139.8% slower)
Base64 native decode 422.000 ms
Base64 CN1 decode 1509.000 ms
Base64 decode ratio (CN1/native) 3.576x (257.6% slower)
Base64 SIMD encode 882.000 ms
Base64 encode ratio (SIMD/native) 1.024x (2.4% slower)
Base64 encode ratio (SIMD/CN1) 0.427x (57.3% faster)
Base64 SIMD decode 867.000 ms
Base64 decode ratio (SIMD/native) 2.055x (105.5% slower)
Base64 decode ratio (SIMD/CN1) 0.575x (42.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 144.000 ms
Image createMask (SIMD on) 14.000 ms
Image createMask ratio (SIMD on/off) 0.097x (90.3% faster)
Image applyMask (SIMD off) 303.000 ms
Image applyMask (SIMD on) 288.000 ms
Image applyMask ratio (SIMD on/off) 0.950x (5.0% faster)
Image modifyAlpha (SIMD off) 272.000 ms
Image modifyAlpha (SIMD on) 94.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.346x (65.4% faster)
Image modifyAlpha removeColor (SIMD off) 218.000 ms
Image modifyAlpha removeColor (SIMD on) 114.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.523x (47.7% faster)
Image PNG encode (SIMD off) 1700.000 ms
Image PNG encode (SIMD on) 1896.000 ms
Image PNG encode ratio (SIMD on/off) 1.115x (11.5% slower)
Image JPEG encode 982.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 20, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 257 seconds

Build and Run Timing

Metric Duration
Simulator Boot 67000 ms
Simulator Boot (Run) 2000 ms
App Install 20000 ms
App Launch 9000 ms
Test Execution 364000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 669.000 ms
Base64 CN1 encode 1458.000 ms
Base64 encode ratio (CN1/native) 2.179x (117.9% slower)
Base64 native decode 365.000 ms
Base64 CN1 decode 953.000 ms
Base64 decode ratio (CN1/native) 2.611x (161.1% slower)
Base64 SIMD encode 396.000 ms
Base64 encode ratio (SIMD/native) 0.592x (40.8% faster)
Base64 encode ratio (SIMD/CN1) 0.272x (72.8% faster)
Base64 SIMD decode 434.000 ms
Base64 decode ratio (SIMD/native) 1.189x (18.9% slower)
Base64 decode ratio (SIMD/CN1) 0.455x (54.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 69.000 ms
Image createMask (SIMD on) 9.000 ms
Image createMask ratio (SIMD on/off) 0.130x (87.0% faster)
Image applyMask (SIMD off) 150.000 ms
Image applyMask (SIMD on) 88.000 ms
Image applyMask ratio (SIMD on/off) 0.587x (41.3% faster)
Image modifyAlpha (SIMD off) 169.000 ms
Image modifyAlpha (SIMD on) 57.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.337x (66.3% faster)
Image modifyAlpha removeColor (SIMD off) 137.000 ms
Image modifyAlpha removeColor (SIMD on) 64.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.467x (53.3% faster)
Image PNG encode (SIMD off) 1150.000 ms
Image PNG encode (SIMD on) 1102.000 ms
Image PNG encode ratio (SIMD on/off) 0.958x (4.2% faster)
Image JPEG encode 547.000 ms

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

shai-almog and others added 3 commits May 20, 2026 19:01
…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>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Image references: No unused images detected (report)

@github-actions
Copy link
Copy Markdown
Contributor

Cloudflare Preview

shai-almog and others added 5 commits May 20, 2026 22:58
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>
The TeaVM cloud-build that ships the playground crashed mid-compile on
the previous commit (RMI EOFException from the build daemon). The new
com.codename1.security classes (Biometrics, SecureStorage and their
helper types) are present in the core jar the playground depends on,
and the generated CN1 reflection bridge in
tools/GenerateCN1AccessRegistry attempts to expose them. The cloud
TeaVM backend doesn't yet know how to handle the new bridge classes,
so the daemon dies before the playground can finish building.

Add the six new types to INTERNAL_CN1_TYPES (the same exclusion list
that already carries Simd, Accessor and IOAccessor) so the registry
generator skips them. This is a one-release workaround: once the
backend cloud build server is updated and rolls a new plugin, the
exclusions can be removed and biometrics will work inside playground
scripts too. Apps built off the device-side builders are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit c944378 into master May 21, 2026
26 of 28 checks passed
shai-almog added a commit that referenced this pull request May 21, 2026
Resolves conflicts with the parallel "com.codename1.security: biometric
auth + secure storage" addition (#4987) that also landed in com.codename1.security.
The two contributions are independent class sets that happen to share the
package -- preserved both.

Conflict resolution:

- IOSNative.m: keep both the crypto bridge block (#ifdef CN1_INCLUDE_CRYPTO)
  and the biometrics + SecureStorage block back-to-back.
- IPhoneBuilder.java: keep usesBiometrics + usesCryptoAPI/usesCryptoGcm
  fields side by side and route classes in com/codename1/security/ to the
  right flag by name (Biometric*, SecureStorage and AuthenticationOptions
  go to biometrics; everything else goes to crypto).
- GenerateCN1AccessRegistry.java: extend the existing one-release
  INTERNAL_CN1_TYPES exclusion to cover the new crypto types too, so the
  playground bridge generator skips them until the cloud TeaVM backend
  catches up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit that referenced this pull request May 21, 2026
CI's javac on JDK 8 / 17 / 21 uses the US-ASCII default file.encoding
(file.encoding=UTF-8 is only the JDK 18+ default per JEP 400) and
choked on the section-sign character in two new doc comments:

  RFC 4648 §5

Swap the symbol for the spelled-out "sec" abbreviation, mirroring the
ASCII-only convention noted in the previous biometrics commit
(#4987) that fixed the same family of failures for the em-dash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit that referenced this pull request May 21, 2026
* Add com.codename1.security crypto API + OtpField widget

Introduces a first-party cryptography API in com.codename1.security so apps
no longer need an external cn1lib for routine hashing, MAC, encryption,
signing, JWT, and OTP work. Pure-Java algorithms produce identical output
on every platform; AES/RSA/Signature/SecureRandom route through each port's
native crypto provider.

New public API:
- Hash         MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 (pure Java)
- Hmac         RFC 2104 + constant-time compare (pure Java)
- SecureRandom platform CSPRNG with bias-free intBelow/longBelow
- Cipher       AES (GCM, CBC, ECB) and RSA (OAEP, PKCS#1)
- Signature    RSA and ECDSA signing/verification
- KeyGenerator AES/HMAC keys, RSA key pairs
- Jwt          JWT sign/verify for HS, RS, ES families (RFC 7519)
- Otp          HOTP (RFC 4226) + TOTP (RFC 6238), authenticator-compatible
- Base32 / Base64Url, CryptoException

UI: com.codename1.components.OtpField -- segmented OTP input with
auto-advance, backspace stepping, paste distribution, and a complete
listener.

Impl layer:
- CodenameOneImplementation gains aesEncrypt/Decrypt, rsaEncrypt/Decrypt,
  cryptoSign/Verify, secureRandomBytes, generateRsaKeyPair,
  generateSymmetricKey. Default implementation uses java.security via
  reflection so JavaSE simulator and Android work out of the box.
- com.codename1.io.Util exposes narrow public delegates the security
  package uses; getImplementation() stays package-private.

iOS port:
- New Ports/iOSPort/nativeSources/CN1Crypto.{h,m} backs AES-CBC/GCM, RSA
  OAEP/PKCS#1, RSA/ECDSA sign+verify, RSA keypair generation, and secure
  random against Apple's Security framework + CommonCrypto.
- IOSNative + IOSImplementation override every crypto bridge method to
  route through CN1Crypto.

Tests: 32 new tests under maven/core-unittests with RFC reference vectors
(FIPS 180-4 for hashes, RFC 4231 for HMAC, RFC 4226/6238 for OTP), plus
AES-GCM tamper detection, RSA OAEP round-trip, RSA sign/verify with
tampering, and a JWT RS256 round-trip.

Developer guide: new "Cryptographic primitives" section in
docs/developer-guide/security.asciidoc with examples for every entry
point, an algorithm-constant reference table, and recommended-key-size
guidance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Gate iOS crypto bridge with CN1_INCLUDE_CRYPTO; set Info.plist exemption

Apps that don't reference com.codename1.security.* now have none of the
CommonCrypto / Security framework encryption symbols in their iOS binary
-- and in particular, the AES-GCM SPI symbols (CCCryptorGCMAddIV etc.)
stay completely out unless the app opts in.

CN1Crypto.h: declare placeholder #define toggles
  CN1_INCLUDE_CRYPTO
  CN1_INCLUDE_CRYPTO_GCM
(commented-out by default; flipped by IPhoneBuilder when relevant)

CN1Crypto.m / IOSNative.m crypto block:
  - All implementations wrapped in #ifdef CN1_INCLUDE_CRYPTO. When the
    define is absent we emit no-op stubs that always return
    CN1_CRYPTO_E_UNSUPPORTED so the IOSImplementation overrides have
    something to link against but no encryption symbols are referenced.
  - The AES-GCM SPI externs and cn1_crypto_aes_gcm body are nested
    inside a separate #ifdef CN1_INCLUDE_CRYPTO_GCM so even apps using
    AES-CBC / RSA / signatures don't pull in the private GCM symbols
    unless they ask for it.

IPhoneBuilder:
  - Adds usesCryptoAPI / usesCryptoGcm flags. usesCryptoAPI is set by
    scanClassesForPermissions when any class in com/codename1/security/
    is referenced; usesCryptoGcm is opt-in via the ios.crypto.gcm
    build hint (default false).
  - Flips the CN1Crypto.h placeholders to the active defines when
    needed.
  - When the crypto API is used, injects
    <key>ITSAppUsesNonExemptEncryption</key><false/> into Info.plist so
    App Store Connect doesn't re-prompt on every upload. We always
    route through Apple-provided crypto (and, for GCM opt-in, stable
    SPI symbols backed by libcommonCrypto), which qualifies for the
    EAR 740.17 standard-cryptography exemption.
  - ios.appUsesNonExemptEncryption build hint overrides the default
    -- pass "true" if the app links proprietary crypto on top of ours,
    or "" to omit the key and answer in App Store Connect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Move crypto bridge defaults out of core; fix CLDC11 + iOS compile

The previous default implementation used java.security via reflection
inside CodenameOneImplementation. The core compiles against the CLDC11
java.lang.Class stub (no getMethod / getConstructor) and the iOS port
goes through ParparVM (no java.lang.reflect.Method symbols), so the
reflection broke the Ant test-javase build, the iOS native compile,
and every transitively-failing job (build, build-ios, build-ios-metal,
native-ios, packaging, build-test 8/17/21).

Refactor:
- CodenameOneImplementation: crypto methods now throw a clear
  "not supported on this platform" RuntimeException by default;
  zero java.security / javax.crypto references in core.
- JavaSEPort: real implementations using direct java.security /
  javax.crypto calls (full JDK is on the classpath at compile time
  and at runtime in the simulator).
- AndroidImplementation: same direct-JCE overrides; Android ships
  the standard JCE provider.
- TestCodenameOneImplementation (unit-test fixture in core-unittests):
  same overrides so the existing 32 crypto tests still exercise the
  bridge end-to-end.
- iOS port already overrides via IOSNative + CN1Crypto.{h,m}; no
  change there.

Also fixes vale-linter alerts on the new developer guide section:
- "and so on" -> rewritten without the suggestion
- "it is" -> "it's"
- "very deliberately" -> "have decided"
- "auto-advancing" -> "advances to the next box"

32/32 tests pass on the JavaSE bridge (Hash, Hmac, OtpField, Jwt,
Cipher, Signature, KeyGenerator, SecureRandom round-trips).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Satisfy PMD quickstart ruleset on new com.codename1.security code

CI's static-analysis quality gate fails the build on a set of forbidden
PMD rules. Bring the new package into compliance:

- ControlStatementBraces: add { ... } around single-statement if / else
  / for / while bodies that the previous draft wrote inline.
- MissingOverride: annotate every method that overrides one of
  MessageDigestImpl's abstract / virtual hooks (reset, digest,
  digestLength, update, processBlock, writeStateBigEndian) and the
  Block64 base-class update overrides. Also annotate the
  DataChangedListener.dataChanged inner class in OtpField.
- LiteralsFirstInComparisons: flip s.equals("X") -> "X".equals(s) in
  MessageDigestImpl.create and Hmac.blockSizeFor.
- EmptyControlStatement: drop the empty if-truncated branch in
  Sha512Family.digest; collapse to a single !truncated check.
- ForLoopCanBeForeach: convert the array index-loops to enhanced for
  in Hash.toHex, Base32.encode and OtpField.fireCompleteIfFull.
- OneDeclarationPerLine: split chained int/long h0,h1,h2,...
  declarations in the SHA inner classes onto separate lines.
- ClassNamingConventions: rename inner classes Sha2_32 -> Sha256Family
  and Sha2_64 -> Sha512Family (the underscore violated the rule).

Local pmd:check on the new files now reports 0 violations from this PR.

Also pulls in the vale-asciidoc fixes for security.asciidoc that the
previous commit dropped (and that the build-docs job had already
flagged before this).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Satisfy Checkstyle on new com.codename1.security code

A second CI gate -- Checkstyle, also bound to the verify phase of
core-unittests -- flagged 109 brace-placement, whitespace, and
continuation-indent issues across the new files. Bring them to zero:

- LeftCurlyCheck (94 instances): split single-line `if (x) { y; }` and
  single-line method definitions like
  `public byte[] md5(byte[] d) { return create(MD5).digest(d); }`
  into the canonical three-line form. Codenames One's checkstyle
  config requires a newline immediately after the opening brace.
- WhitespaceAfterCheck (15 instances): add spaces after commas in
  Hash.HEX array literal and after primitive `(long)` casts in
  SecureRandom.longBelow.
- IndentationCheck (16 instances): bump SHA-512 word-mixing
  continuation lines from 23 to 24 columns to match the
  expected level (the `^` operator was one column shy of the
  canonical four-space continuation).

Local mvn checkstyle:check + pmd:check both report 0 violations from
this PR after these changes; 32/32 security tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* crypto API review feedback: reuse Base64, abstract Key, OTP tutorial, device test

Code-review feedback on PR #4994:

1. Reuse the existing util.Base64 instead of adding a parallel Base64Url
   class. util.Base64 is already SIMD-optimized; layer URL-safe encode/decode
   on top of its encodeNoNewline path and inherit the speedup.
   - util.Base64: new encodeUrlSafe(byte[]) / decodeUrlSafe(String) that
     run through the same fast encoder, then map +-> / -> _ and strip
     trailing = padding (RFC 4648 sec5).
   - Jwt, JwtTest, OtpTest, package-info: switch to the new helpers.
   - Drop security/Base64Url.java entirely.

2. Pull the common key fields (algorithm, encoded, format) into a new
   abstract Key base class. PublicKey/PrivateKey/SecretKey now extend it
   and only carry the type-specific extras (static factories for the
   asymmetric pair; getBitLength on the symmetric one).

3. Otp.otpauthUri(issuer, accountName, secret, ...): builds the canonical
   otpauth://totp/... URI per the Google Authenticator KeyUri spec so the
   secret can be displayed as a QR code on enrolment screens. Six-digit /
   30-second / SHA-1 default matches what every authenticator app expects.

4. Developer guide: expand the OTP section into a real two-factor-auth
   tutorial -- enrolment via otpauthUri + QR rendering, verification via
   OtpField + Otp.verifyTotp, server-side guidance, rate-limiting note.
   Documents three approaches for actually rendering the QR (server-side
   render URL, QR cn1lib, plain Base32 typed entry) since core doesn't
   ship a QR encoder yet.

5. CryptoApiTest: device-side coverage in scripts/hellocodenameone that
   mirrors the JUnit assertions (hash + HMAC RFC vectors, HOTP/TOTP RFC
   vectors, AES-GCM round-trip + tamper detection, RSA-OAEP round-trip,
   RSA-SHA-256 sign/verify, JWT HS256 + RS256 round-trips, otpauthUri
   shape). Registered in Cn1ssDeviceRunner; skipped on HTML5 (no crypto
   bridge on the JS port yet).

SIMD acceleration for hashing was investigated but declined: SHA-2's
round state machine has data dependencies that block single-message
vectorization, and the current Simd surface doesn't expose 32/64-bit
rotation. The natural SIMD beneficiary in this PR -- Base64 -- is
already covered by util.Base64.

Local: pmd:check, checkstyle:check both 0 violations; 34 JUnit tests
pass (32 from prior commits + 2 new otpauthUri tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Strip non-ASCII section sign from Base64 URL-safe docs

CI's javac on JDK 8 / 17 / 21 uses the US-ASCII default file.encoding
(file.encoding=UTF-8 is only the JDK 18+ default per JEP 400) and
choked on the section-sign character in two new doc comments:

  RFC 4648 §5

Swap the symbol for the spelled-out "sec" abbreviation, mirroring the
ASCII-only convention noted in the previous biometrics commit
(#4987) that fixed the same family of failures for the em-dash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* iOS: emit ParparVM _R_int wrappers for the new crypto natives

The crypto bridge methods on IOSNative -- aesCbc, aesGcm, rsaEncrypt,
rsaDecrypt, sign, verify, generateRsaKeyPair -- all return an int.
ParparVM emits two C entry points for every non-void native: the
unmangled implementation (com_..._methodName___paramTypes) and a
_R_<returnType>-suffixed forwarder that the bytecode dispatcher
actually calls.

The earlier commits provided only the first half, so the previous
build-ios job (which transpiled the framework but didn't transitively
include the crypto code path) passed -- the symbols were dead-code-
eliminated. Adding CryptoApiTest to scripts/hellocodenameone now keeps
those methods alive, the linker hunts for the _R_int wrappers, and
the iOS packaging job fails with:

    Undefined symbols for architecture arm64:
      "_com_codename1_impl_ios_IOSNative_aesCbc___..._R_int"
      "_com_codename1_impl_ios_IOSNative_aesGcm___..._R_int"
      ...

Forward each wrapper to the matching base implementation. The base
itself is either the real CommonCrypto-backed version (when
CN1_INCLUDE_CRYPTO is defined by IPhoneBuilder) or the
CN1_CRYPTO_E_UNSUPPORTED stub, so this single set of wrappers serves
both build configurations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants