9649dac7b2
The Keystore key now requires authentication, valid for 30 s afterwards - short enough that an unattended phone is not exploitable, long enough to decrypt and display. Time-bound rather than a per-use CryptoObject: BiometricPrompt's callback would need a Java class compiled into a jar, while FMX already bridges it. Two aliases, because the auth constraint is fixed when the key is created and cannot be added later. The file header records which key wrote it, so a read knows whether to authenticate first. A device with neither fingerprint nor lock screen cannot hold such a key, so SaveVault falls back to the plain key rather than giving up the cache. And a failed authentication no longer wipes the cache: the vault is intact, it is the user who cancelled - erasing it would cost a download and 4 s of derivation for a dismissed prompt.
104 lines
3.1 KiB
ObjectPascal
104 lines
3.1 KiB
ObjectPascal
unit PMA.Biometric;
|
|
{
|
|
Fine couche au-dessus de FMX.BiometricAuth.
|
|
|
|
On passe par le service FMX plutot que par BiometricPrompt en JNI direct :
|
|
le rappel de BiometricPrompt exige une classe Java implementant une
|
|
interface, donc un .jar a compiler et embarquer. FMX fournit deja ce pont.
|
|
|
|
MODELE RETENU : la cle du Keystore est creee avec une validite APRES
|
|
authentification (quelques dizaines de secondes). L'utilisateur s'authentifie,
|
|
puis la cle devient utilisable pendant ce delai. C'est plus simple qu'un
|
|
CryptoObject lie a chaque operation, et suffisant ici : la fenetre est courte
|
|
et l'application est au premier plan pendant ce temps.
|
|
|
|
L'APPEL DOIT SE FAIRE SUR LE THREAD PRINCIPAL - il affiche un dialogue
|
|
systeme. Le travail lourd (dechiffrement) part ensuite dans un thread.
|
|
}
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils;
|
|
|
|
type
|
|
TBioOutcome = (boOK, boUnavailable, boFailed, boCancelled);
|
|
|
|
/// L'appareil peut-il authentifier (empreinte, visage, ou code de
|
|
/// verrouillage) ? False signifie qu'aucune protection n'est configuree.
|
|
function BiometricAvailable: Boolean;
|
|
|
|
/// Demande l'authentification puis appelle AThen. Non bloquant : le resultat
|
|
/// arrive par le rappel, sur le thread principal.
|
|
/// Si l'appareil ne sait pas authentifier, AThen est appele avec boUnavailable
|
|
/// SANS refuser l'acces - a l'appelant de decider. Un telephone sans
|
|
/// verrouillage ne peut de toute facon pas proteger la cle.
|
|
procedure AuthenticateThen(const APrompt: string; const AThen: TProc<TBioOutcome>);
|
|
|
|
implementation
|
|
|
|
{$IF DEFINED(ANDROID)}
|
|
uses
|
|
FMX.BiometricAuth, FMX.Platform;
|
|
{$ENDIF}
|
|
|
|
function BiometricAvailable: Boolean;
|
|
{$IF DEFINED(ANDROID)}
|
|
var
|
|
LSvc: IFMXBiometricAuthService;
|
|
begin
|
|
Result := TPlatformServices.Current.SupportsPlatformService(
|
|
IFMXBiometricAuthService, LSvc) and LSvc.CanAuthenticate;
|
|
end;
|
|
{$ELSE}
|
|
begin
|
|
Result := False;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure AuthenticateThen(const APrompt: string; const AThen: TProc<TBioOutcome>);
|
|
{$IF DEFINED(ANDROID)}
|
|
var
|
|
LSvc: IFMXBiometricAuthService;
|
|
LProps: TBiometricPromptProperties;
|
|
begin
|
|
if not Assigned(AThen) then Exit;
|
|
|
|
if not (TPlatformServices.Current.SupportsPlatformService(
|
|
IFMXBiometricAuthService, LSvc) and LSvc.CanAuthenticate) then
|
|
begin
|
|
AThen(boUnavailable);
|
|
Exit;
|
|
end;
|
|
|
|
LProps := Default(TBiometricPromptProperties);
|
|
LProps.Title := 'Deverrouiller le coffre';
|
|
LProps.Description := APrompt;
|
|
LProps.NegativeButtonText := 'Annuler';
|
|
LSvc.SetPromptProperties(LProps);
|
|
// DeviceCredential inclus : un telephone sans empreinte enrolee mais avec
|
|
// un code de verrouillage reste utilisable, au lieu d'etre bloque.
|
|
LSvc.SetBiometricStrengths([TBiometricStrength.Strong,
|
|
TBiometricStrength.DeviceCredential]);
|
|
|
|
LSvc.Authenticate(
|
|
procedure
|
|
begin
|
|
AThen(boOK);
|
|
end,
|
|
procedure(const AFailReason: TBiometricFailReason; const AMessage: string)
|
|
begin
|
|
if AFailReason = TBiometricFailReason.Cancelled then
|
|
AThen(boCancelled)
|
|
else
|
|
AThen(boFailed);
|
|
end);
|
|
end;
|
|
{$ELSE}
|
|
begin
|
|
if Assigned(AThen) then AThen(boUnavailable);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
end.
|