From 773e47330fd297792bedc475ebcfaf26324844d3 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sat, 15 Aug 2026 18:35:30 +0100 Subject: [PATCH] fix(android): ask for the device credential alone when no biometric is enrolled The re-seal never ran and left no trace, because a branch not taken logs nothing. It logs now, verdict and reason, whether or not anything happens - the absence of a line was itself the thing that cost time. And the verdict may have been wrong: asking canAuthenticate for BIOMETRIC_STRONG together with DEVICE_CREDENTIAL can fail outright on a device with a pattern and no enrolled fingerprint, instead of keeping what exists. It now falls back to the credential alone, which is all the key needs, and AuthenticateThen presents whichever set answered - offering something else would draw the same refusal. Co-Authored-By: Claude Opus 5 --- android-client/PMA.Biometric.pas | 30 ++++++++++++++++++++++++++++-- mainApk.pas | 7 +++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/android-client/PMA.Biometric.pas b/android-client/PMA.Biometric.pas index 5ce4a59..16b9c59 100644 --- a/android-client/PMA.Biometric.pas +++ b/android-client/PMA.Biometric.pas @@ -72,6 +72,18 @@ const // sans empreinte enrolee, doit rester utilisable. C'est le cas courant. WANTED_STRENGTHS = [TBiometricStrength.Strong, TBiometricStrength.DeviceCredential]; + // Repli quand la combinaison ci-dessus est refusee. Observe sur un appareil + // avec schema et AUCUNE empreinte : selon les versions, demander + // BIOMETRIC_STRONG en meme temps que DEVICE_CREDENTIAL fait echouer + // l'ensemble au lieu de retenir ce qui existe. On redemande alors juste ce + // dont on a besoin. + CREDENTIAL_ONLY = [TBiometricStrength.DeviceCredential]; + +var + // Le jeu retenu par la derniere interrogation reussie. AuthenticateThen doit + // presenter EXACTEMENT celui-la : demander autre chose que ce qui a repondu + // « disponible » ramenerait le meme refus. + FStrengths: TBiometricStrengths = WANTED_STRENGTHS; /// Recupere le service ET pose les forces d'authentification. /// @@ -86,7 +98,7 @@ begin Result := TPlatformServices.Current.SupportsPlatformService( IFMXBiometricAuthService, ASvc); if Result then - ASvc.SetBiometricStrengths(WANTED_STRENGTHS); + ASvc.SetBiometricStrengths(FStrengths); end; {$ENDIF} @@ -102,6 +114,7 @@ begin // ouverture du coffre. FLastError := ''; try + FStrengths := WANTED_STRENGTHS; if not AcquireService(LSvc) then begin FLastError := 'biometric service missing from the platform'; @@ -109,7 +122,20 @@ begin end; Result := LSvc.CanAuthenticate; if not Result then - FLastError := 'CanAuthenticate: no usable protection'; + begin + // Second essai sans la biometrie forte : le schema seul suffit a + // deverrouiller la cle, et c'est tout ce qu'on demande. + FStrengths := CREDENTIAL_ONLY; + LSvc.SetBiometricStrengths(FStrengths); + Result := LSvc.CanAuthenticate; + if Result then + FLastError := 'device credential only (no biometric enrolled)' + else + begin + FStrengths := WANTED_STRENGTHS; + FLastError := 'CanAuthenticate: no usable protection'; + end; + end; except on E: Exception do begin diff --git a/mainApk.pas b/mainApk.pas index 90370e9..b0631f1 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -839,6 +839,13 @@ begin // pendant que l'appareil n'avait pas de verrouillage restait en clair // indefiniment, meme apres en avoir remis un. On le rescelle ici, ou l'on // tient justement le clair. + // Journalise le verdict MEME quand rien n'est fait : sans cette ligne, un + // rescellement qui n'a pas lieu ne laisse aucune trace, et on ne sait pas + // s'il a echoue ou s'il n'a jamais ete tente. + if not VaultNeedsAuth then + Log('Plain cache; device authentication available: ' + + BoolToStr(BiometricAvailable, True) + ' ' + LastBiometricError); + if (not VaultNeedsAuth) and BiometricAvailable then begin try