diff --git a/android-client/web/app.html b/android-client/web/app.html index 8d7c746..1e7e320 100644 --- a/android-client/web/app.html +++ b/android-client/web/app.html @@ -186,6 +186,8 @@ font: inherit; outline: none; } .field-in::placeholder { color: var(--muted); } + .in-label { color: var(--muted); font-size: 12px; margin: 10px 0 -2px 4px; } + .in-label span { opacity: .7; } #log { margin: 10px 0 0; padding: 10px 12px; max-height: 34vh; overflow: auto; background: rgba(0,0,0,.20); border-radius: var(--radius-s); @@ -219,7 +221,13 @@ depot, jamais dans le coffre. --> + diff --git a/mainApk.pas b/mainApk.pas index 9e5840c..20ca97a 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -32,7 +32,7 @@ interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Diagnostics, System.Rtti, System.NetEncoding, System.DateUtils, - System.Generics.Collections, System.IOUtils, + System.Generics.Collections, System.IOUtils, System.Hash, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types, FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects, @@ -418,6 +418,14 @@ begin SetLength(FWebPending, 0); end; +/// Empreinte courte d'un secret, pour le journal. On veut savoir LEQUEL des +/// jetons est en place, jamais sa valeur : huit caracteres de son SHA-256 +/// suffisent a les distinguer et n'en revelent rien. +function Fingerprint(const ASecret: string): string; +begin + if ASecret = '' then Exit('(vide)'); + Result := Copy(THashSHA2.GetHashString(ASecret), 1, 8); +end; /// Chiffre un petit conteneur, le relit, et le laisse sur le disque prive pour /// qu'une autre implementation le verifie. Iterations volontairement basses : /// ce qu'on eprouve ici est le CABLAGE - cle, IV, place du tag - pas le cout @@ -496,8 +504,8 @@ begin TTimeZone.Local.ToUniversalTime(Now))]), FSyncPwd, 1000); Log('PUT ' + LUrl); - Log(' token ' + IntToStr(Length(FToken)) + ' chars, body ' + - IntToStr(Length(LBody)) + ' chars'); + Log(Format(' token %s (%d chars), body %d chars', + [Fingerprint(FToken), Length(FToken), Length(LBody)])); // Sha vide : on cree le fichier, ou on echoue en 409 s'il existe deja - // ce qui prouverait le controle de concurrence aussi surement. LRes := LClient.Put(LBody, '', 'write path probe from phone'); @@ -528,6 +536,8 @@ begin FUrl := LUrl; if LToken <> '' then FToken := LToken; end; + Log(Format('config loaded: token %s (%d chars), url %s', + [Fingerprint(FToken), Length(FToken), FUrl])); WebCall(Format('PM.setConfig(%s, %s, %s, %s)', [JsArg(FUrl), JsArg(FToken), JsArg(FPwd), // Replier des champs vides revient a cacher la seule chose a faire. @@ -560,7 +570,16 @@ end; procedure TForm8.OnWebNavigate(ASender: TObject; const AUrl: string); begin // Le moteur navigue aussi pour charger la page elle-meme. - if AUrl.StartsWith('cmd://') then HandleWebCommand(AUrl.Substring(6)); + // L'ACTION SEULE, jamais la requete : elle porte le jeton et le mot de passe. + // Sans cette trace on ne peut pas distinguer « la commande n'arrive pas » de + // « elle arrive et ne fait rien », et les deux se ressemblent de l'exterieur. + if AUrl.StartsWith('cmd://') then + begin + var LDbg := AUrl.Substring(6); + if LDbg.Contains('?') then LDbg := LDbg.Substring(0, LDbg.IndexOf('?')) + '?...'; + Log('cmd ' + LDbg); + HandleWebCommand(AUrl.Substring(6)); + end; end; procedure TForm8.HandleWebCommand(const AUrl: string); @@ -613,6 +632,11 @@ begin FUrl := TNetEncoding.URL.Decode(Param('u')).Trim; FToken := TNetEncoding.URL.Decode(Param('t')).Trim; FPwd := TNetEncoding.URL.Decode(Param('p')); + // L'empreinte du mot de passe AUSSI : si une saisie destinee au jeton + // atterrit dans le champ voisin - les deux sont masques - c'est celle-ci + // qui bouge, et le jeton reste inchange sans que rien ne l'explique. + Log(Format('config from page: token %s, pwd %s', + [Fingerprint(FToken), Fingerprint(FPwd)])); end else if LAction = 'openvault' then @@ -678,7 +702,10 @@ begin // Les lignes du detail portent des valeurs EN CLAIR (FRows) : les detruire // fait partie du verrouillage, pas de l'affichage. FCurrent := Default(TVaultEntry); - PushConfig; + // PAS DE PushConfig HORS DEMARRAGE. Redescendre les coordonnees du serveur + // ecrase ce que l'utilisateur vient de taper - l'auto-verrouillage le faisait + // donc sans le moindre geste de sa part, et le champ jeton semblait ne rien + // retenir. Verrouiller efface le clair ; la configuration n'a rien a y voir. ShowUnlock; end; @@ -775,7 +802,6 @@ begin // Un coffre local illisible se remplace, il ne se repare pas. Log('Local vault corrupt: ' + E.Message); ClearVault; - PushConfig; ShowUnlock; end; end; @@ -785,7 +811,6 @@ begin // ShowUnlock AVANT le message : il repositionne le statut selon ce que // l'appareil detient, et ecraserait donc l'explication. FVaultUnreadable := True; - PushConfig; ShowUnlock; Log('Local vault unreadable: ' + LastStoreError); Status('Local vault unreadable - see log'); @@ -841,7 +866,6 @@ begin FVaultUnreadable := True; Log('No usable protection on this device - vault kept but unreadable'); Log(' ' + LastBiometricError); - PushConfig; ShowUnlock; Status('Screen lock missing - download again to replace the cache'); end;