feat(android): protect the local vault with biometric or device credential
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.
This commit is contained in:
+43
-3
@@ -53,6 +53,7 @@ type
|
||||
FETag: string;
|
||||
procedure RunSelfTest(Sender: TObject);
|
||||
procedure RunFetch(Sender: TObject);
|
||||
procedure DoFetch(const LUrl, LTok, LPwd: string);
|
||||
procedure CountEntries(const APlainJson: string);
|
||||
procedure Log(const AMsg: string);
|
||||
public
|
||||
@@ -65,7 +66,7 @@ var
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.JSON, PMA.Crypto, PMA.TestVector, PMA.SyncClient, PMA.Store;
|
||||
System.JSON, PMA.Crypto, PMA.TestVector, PMA.SyncClient, PMA.Store, PMA.Biometric;
|
||||
|
||||
{$R *.fmx}
|
||||
|
||||
@@ -170,6 +171,39 @@ begin
|
||||
LTok := FToken.Text.Trim;
|
||||
LPwd := FPwd.Text;
|
||||
|
||||
// L'authentification affiche un dialogue systeme : elle doit rester sur le
|
||||
// thread principal, et PRECEDER toute lecture du coffre. La declencher
|
||||
// depuis le thread de travail ne montrerait rien.
|
||||
if VaultNeedsAuth then
|
||||
begin
|
||||
Log('Coffre local protege - authentification requise');
|
||||
AuthenticateThen('Acceder au coffre',
|
||||
procedure(AOutcome: TBioOutcome)
|
||||
begin
|
||||
case AOutcome of
|
||||
boOK: begin Log(' authentifie'); DoFetch(LUrl, LTok, LPwd); end;
|
||||
boCancelled: begin Log(' annule'); FBtnFetch.Enabled := True; end;
|
||||
boUnavailable:
|
||||
begin
|
||||
// La protection a ete retiree du telephone depuis l'ecriture du
|
||||
// cache : il est devenu illisible pour toujours.
|
||||
Log(' plus aucune protection sur l''appareil - cache efface');
|
||||
ClearVault;
|
||||
DoFetch(LUrl, LTok, LPwd);
|
||||
end;
|
||||
else
|
||||
Log(' echec de l''authentification');
|
||||
FBtnFetch.Enabled := True;
|
||||
end;
|
||||
end);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
DoFetch(LUrl, LTok, LPwd);
|
||||
end;
|
||||
|
||||
procedure TForm8.DoFetch(const LUrl, LTok, LPwd: string);
|
||||
begin
|
||||
TThread.CreateAnonymousThread(
|
||||
procedure
|
||||
var
|
||||
@@ -247,8 +281,14 @@ begin
|
||||
// Ecrit APRES un dechiffrement reussi seulement, et avec son ETag :
|
||||
// les deux doivent rester coherents.
|
||||
try
|
||||
SaveVault(LPlain, FETag);
|
||||
Log(' coffre enregistre localement (chiffre par le Keystore)');
|
||||
// True : on DEMANDE la protection par authentification. PMA.Store
|
||||
// retombe tout seul sur une cle simple si l'appareil n'a ni
|
||||
// empreinte ni code, plutot que de renoncer au cache.
|
||||
SaveVault(LPlain, FETag, True);
|
||||
if VaultNeedsAuth then
|
||||
Log(' coffre enregistre - protege par biometrie/code')
|
||||
else
|
||||
Log(' coffre enregistre - AUCUNE protection sur cet appareil');
|
||||
except
|
||||
on E: Exception do
|
||||
Log(' cache non ecrit : ' + E.Message);
|
||||
|
||||
Reference in New Issue
Block a user