Skip to content

goodixtls55x4: support GF3268 (fw _10056) sensors, fix PSK provisioning + TLS handshake - #3

Open
jedbillyb wants to merge 14 commits into
TheWeirdDev:55b4-experimentalfrom
jedbillyb:goodix-55b4-fixes
Open

goodixtls55x4: support GF3268 (fw _10056) sensors, fix PSK provisioning + TLS handshake#3
jedbillyb wants to merge 14 commits into
TheWeirdDev:55b4-experimentalfrom
jedbillyb:goodix-55b4-fixes

Conversation

@jedbillyb

Copy link
Copy Markdown

Makes the goodixtls55x4 driver work end-to-end on a 27c6:55b4 sensor reporting firmware GF3268_RTSEC_APP_10056 that was previously provisioned by Windows, built against OpenSSL 3.x (Void Linux). Out of the box activation failed at every stage; each fix is a separate commit.

  • Build: make sigfm-tests conditional on doctest. doctest ships header-only with no pkg-config file on some distros, so the required lookup fails and blocks meson setup. Marked not-required and gated the test target on doctest.found(), so the library configures without it while people who have doctest keep the tests.
  • Firmware: accept the GF3268_RTSEC_APP_ family. The check required exactly _10041 and rejected otherwise-identical sensors on a newer minor revision (_10056). Now compares the prefix.
  • PSK: (re)provision instead of only checking. The activation SSM only read the stored PMK hash and failed if it differed; a sensor last provisioned by another OS always differs. Following goodix-fp-dump, when the hash doesn't match we write the known white-box PSK (flags 0xbb010003) and continue. Once provisioned the check matches and the write is skipped.
  • Fix goodix_send_preset_psk_write. This function appears never to have been exercised: it used the 12-byte GoodixPresetPsk struct leaving the offset field uninitialised (garbage on the wire), and passed sizeof(payload) (pointer size, 8) as the length, truncating the key. Reframed as flags + length + payload. Standalone commit, independent of the 55b4 work.
  • TLS: enable PSK ciphers. Cipher list "ALL" excludes PSK suites and modern OpenSSL's default security level rejects them, so SSL_accept failed with "cipher operation failed". Set "PSK:@SECLEVEL=0". This applies only to the in-process TLS-PSK channel to the sensor (a local socketpair); it is not a global OpenSSL or system-wide change and does not affect any other TLS.
  • sigfm: tighten matching thresholds (separable, drop if undesired). min_match 5→15, Lowe's ratio 0.75→0.70, bz3 threshold 24×3→24×5. On my unit the enrolled finger scores ~579 and a different finger scores 0 against the threshold.

Tested: full enroll + verify via the example binaries and via fprintd (TTY login + swaylock) on Void Linux.

doctest ships header-only with no pkg-config file on some distros (e.g.
Void Linux), which makes the required dependency lookup fail and blocks
meson setup for the whole project. Mark it not-required and only build
the sigfm-tests executable when doctest is found, so the library still
configures while users who have doctest keep the test target.
The firmware check required the exact string GF3268_RTSEC_APP_10041 and
rejected otherwise-identical sensors shipping a newer minor revision
(e.g. GF3268_RTSEC_APP_10056). Compare against the GF3268_RTSEC_APP_
prefix so the whole family is accepted.
The activation SSM only read the device PMK hash and failed if it did
not match goodix_55x4_psk_0. A sensor last provisioned by another OS
(e.g. Windows) reports a different hash and could never activate.

Follow goodix-fp-dump's approach: when the stored hash differs, write the
known white-box PSK (flags 0xbb010003) via a new ACTIVATE_WRITE_PSK state
and continue. Once provisioned the device reports the expected hash, so
the check passes and the write is skipped on subsequent activations.
goodix_send_preset_psk_write built its payload with the 12-byte
GoodixPresetPsk struct but never initialised the trailing offset field,
sending garbage on the wire, and passed sizeof(payload) (the pointer
size, 8) plus length as the protocol length, truncating the key by the
difference. The device therefore stored a wrong, non-deterministic PMK
hash. Build the payload explicitly as flags + length + psk and send the
correct total length.
The TLS server used cipher list "ALL", which excludes PSK cipher suites,
and on modern OpenSSL the default security level rejects the PSK ciphers
the sensor offers, so SSL_accept failed with "cipher operation failed".
Set "PSK:@SECLEVEL=0" on the sensor's TLS context and SSL object. This
only affects the local in-process TLS-PSK channel to the fingerprint
sensor (a socketpair), not any other OpenSSL usage.
Raise the minimum match count from 5 to 15, tighten Lowe's ratio from
0.75 to 0.70, and raise the goodixtls55x4 bz3 match-score threshold from
24*3 to 24*5 so different fingers are less likely to be accepted. On a
55b4 unit the enrolled finger scores ~579 and a different finger scores 0
against the bz3 threshold. These values are subjective and this commit is
separable from the rest of the series.
jedbillyb and others added 8 commits June 15, 2026 20:51
pam_fprintd deactivates and re-verifies on every failed scan, and the
driver was running the full activate sequence (chip enable, fw check, PSK
read, reset, MCU config) plus a fresh TLS handshake on each one, with a
sleep + TLS shutdown on the way out. That down-up cycle was ~3s per retry.

Track an "active" (warm) flag: set it once the TLS session is up, make
deactivate a no-op that completes immediately, and make activate skip the
whole sequence + handshake while warm (reusing the cached background frame
too). The real teardown still happens on device close (dev_deinit), and the
flag is reset on open/close so each fresh claim cold-handshakes once.

Result: only the first scan per claim pays the handshake; subsequent retries
are near-instant.
The binding constraint on a marginal press was sigfm's min_match, not
bz3_threshold: sigfm_match_score() returns 0 outright when fewer than
min_match keypoints survive the ratio test, or when fewer than min_match
consistent angle pairs are found, so bz3_threshold never gets consulted.
Lowering bz3 alone had stopped helping for that reason.

  sigfm min_match      15   -> 10    (hard keypoint/angle floor)
  sigfm distance_match 0.70 -> 0.78  (Lowe ratio, accept more matches)
  bz3_threshold        24*5 -> 16    (final score gate)
  nr_enroll_stages     10   -> 24    (restores the tuned value)

nr_enroll_stages is restored to 24 to match the existing enrolled template,
so no re-enroll is required. bz3/min_match/distance are verify-time only.

These constants were previously carried as uncommitted working-tree edits
and were lost; committing them so a checkout cannot silently revert to the
finicky values.
Still rejecting too many genuine presses at min_match=10. Reader contention
was ruled out (fprintd-verify blocks waiting for a finger rather than
failing to claim), so the matcher is the limiter.

  sigfm min_match      10   -> 6
  sigfm distance_match 0.78 -> 0.82
  bz3_threshold        16   -> 10

Verify-time only, no re-enroll. This trades false-reject for false-accept;
if false accepts appear, raise min_match first.
Records what each knob does, which symptom each one addresses, how to measure
real match scores instead of guessing, and the ldconfig backup trap that made
a day of tuning silently no-op.
An unenrolled finger unlocked the machine at min_match=6 / distance=0.82 /
bz3=10. Raise to 12 / 0.74 / 48: still well short of the 15 / 0.70 / 120 that
rejected too many genuine presses, but no longer accepting the wrong finger.
Record both observed points in TUNING.md so neither has to be rediscovered.

Also re-apply the warm TLS session reverted in 0d1081f, with the auto-recovery
it was missing. The driver tore down TLS and re-ran the full activate sequence
on every pam_fprintd retry -- about 3s per attempt, which is the delay after a
wrong finger. It now stays warm within a claim.

The original was reverted because a warm session goes stale when the sensor
re-enumerates after idle, so every later scan failed instantly and looped
forever. scan_complete now clears the warm flag on any scan failure, so a
stale session costs one cold re-activate and heals itself.
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.

1 participant