fix(android): the unlock code no longer changes by itself
It went from 0000 to 00000000 with nobody deciding it. The typed code and the code the cache is sealed under were the same variable, so every write of the cache re-sealed under whatever sat in the field - a sync, which asks for no code at all, was enough to change it, silently. They are two things now. Only opening the vault touches the seal, because that is the one moment we know the current code is known: the decryption just proved it. Everything else - sync, background refresh, saving an entry - rewrites the cache under the code already in place. Hence a second field, shown only once a code exists: the current one must open the vault for the new one to take. Without it this fix would have locked the device under the code set by accident, which is exactly the harm it is meant to prevent.
This commit is contained in:
@@ -813,6 +813,24 @@ Deux corrections antérieures, posées par raisonnement sans instrument,
|
||||
reposaient toutes deux sur une mesure qui vaut zéro ici. La leçon vaut d'être
|
||||
écrite : sur cet écran, **instrumenter d'abord**.
|
||||
|
||||
### Le code se changeait tout seul — corrigé 2026-08-21
|
||||
|
||||
Le code du cache est passé de `0000` à `00000000` sans que personne ne le
|
||||
décide. Cause : **le code tapé et le code sous lequel le cache est scellé
|
||||
étaient la même variable**, et toute écriture du cache rescellait donc sous ce
|
||||
qui traînait dans le champ. Une synchronisation — qui ne demande pourtant aucun
|
||||
code — suffisait à le changer, en silence.
|
||||
|
||||
Les deux sont séparés maintenant. Seule **l'ouverture du coffre** touche au
|
||||
sceau, parce que c'est le seul moment où l'on sait que l'utilisateur connaît le
|
||||
code courant : le déchiffrement vient de le prouver. Tout le reste — sync,
|
||||
rafraîchissement de fond, enregistrement d'entrée — réécrit le cache sous le
|
||||
code déjà en place.
|
||||
|
||||
D'où un second champ, *new unlock code*, visible seulement quand un code existe :
|
||||
il faut ouvrir avec l'actuel pour que le nouveau prenne. Sans lui, la correction
|
||||
aurait enfermé l'appareil sous le code posé par accident.
|
||||
|
||||
### Ce qui devient facultatif
|
||||
|
||||
Sans dépôt configuré, *Sync* n'a rien à faire : les réglages du serveur
|
||||
|
||||
@@ -289,6 +289,18 @@
|
||||
type="password" inputmode="numeric" autocomplete="off">
|
||||
<div class="in-x" data-clear="pin">×</div>
|
||||
</div>
|
||||
<!-- CHANGER LE CODE demande de connaitre l'actuel : le champ du dessus
|
||||
doit ouvrir le coffre pour que celui-ci prenne effet. Sans cette
|
||||
regle, toute ecriture du cache rescellait sous ce qui trainait dans
|
||||
le champ - une synchronisation, qui ne demande pourtant aucun code,
|
||||
suffisait a le changer sans qu'un mot le dise.
|
||||
Cache tant qu'aucun code n'est pose : on n'en change pas un qui
|
||||
n'existe pas, on le pose avec le champ du dessus. -->
|
||||
<div class="in-wrap" id="pin2wrap" hidden>
|
||||
<input class="field-in" id="pin2" placeholder="new unlock code"
|
||||
type="password" inputmode="numeric" autocomplete="off">
|
||||
<div class="in-x" data-clear="pin2">×</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- UNE CROIX PAR CHAMP. Vider par la touche retour demande une quarantaine
|
||||
d'appuis sur un jeton, et laisse facilement un caractere - ce qui donne
|
||||
@@ -423,9 +435,10 @@ function pushConfig() {
|
||||
send('cfg?u=' + encodeURIComponent(elUrl.value.trim())
|
||||
+ '&t=' + encodeURIComponent(elTok.value.trim())
|
||||
+ '&p=' + encodeURIComponent(elPwd.value)
|
||||
+ '&c=' + encodeURIComponent(elPin.value));
|
||||
+ '&c=' + encodeURIComponent(elPin.value)
|
||||
+ '&c2=' + encodeURIComponent(document.getElementById('pin2').value));
|
||||
}
|
||||
[elUrl, elTok, elPwd, elPin].forEach(function (el) {
|
||||
[elUrl, elTok, elPwd, elPin, document.getElementById('pin2')].forEach(function (el) {
|
||||
el.addEventListener('change', pushConfig);
|
||||
el.addEventListener('blur', pushConfig);
|
||||
});
|
||||
@@ -954,6 +967,13 @@ var PM = {
|
||||
setPinNeeded: function (on) {
|
||||
document.getElementById('pin').placeholder =
|
||||
on ? 'unlock code' : 'set an unlock code (optional)';
|
||||
document.getElementById('pin2wrap').hidden = !on;
|
||||
},
|
||||
/// Le code vient d'etre change : le champ du nouveau n'a plus rien a dire,
|
||||
/// et le laisser rempli le reproposerait a chaque ouverture.
|
||||
pinChanged: function () {
|
||||
document.getElementById('pin2').value = '';
|
||||
document.getElementById('pin').value = '';
|
||||
},
|
||||
focusPin: function () { document.getElementById('pin').focus(); },
|
||||
log: function (line) {
|
||||
|
||||
+49
-8
@@ -150,6 +150,20 @@ type
|
||||
/// Il remplace la protection par verrouillage d'ecran, qu'Android detruit
|
||||
/// des qu'on change le schema - par conception, et sans recours.
|
||||
FPin: string;
|
||||
/// LE CODE SOUS LEQUEL LE CACHE EST REELLEMENT SCELLE - pas celui qui est
|
||||
/// tape a l'instant.
|
||||
///
|
||||
/// Les deux etaient confondus, et toute ecriture du cache rescellait donc
|
||||
/// sous ce qui trainait dans le champ : une synchronisation, qui ne demande
|
||||
/// pourtant aucun code, changeait le code. Une frappe de trop suffisait a
|
||||
/// remplacer 0000 par 00000000, sans que personne ne l'ait decide et sans
|
||||
/// qu'un mot le dise.
|
||||
///
|
||||
/// Seule l'ouverture du coffre y touche desormais - c'est le seul moment ou
|
||||
/// l'on sait que l'utilisateur connait le code courant.
|
||||
FPinSealed: string;
|
||||
/// Le NOUVEAU code demande, quand on veut en changer. Vide sinon.
|
||||
FPinNew: string;
|
||||
|
||||
// --- l'interface entiere, rendue par le moteur du systeme
|
||||
FLayWeb: TLayout;
|
||||
@@ -733,8 +747,8 @@ begin
|
||||
Log(Format('save: re-encrypt in %d ms', [LSw.ElapsedMilliseconds]));
|
||||
SaveLocalContainer(LBody);
|
||||
try
|
||||
SaveVault(LNew, FETag, FSyncPwd, BiometricAvailable, False, FPin,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
SaveVault(LNew, FETag, FSyncPwd, BiometricAvailable, False,
|
||||
FPinSealed, FKey, FKeySalt, FKeyIters);
|
||||
except
|
||||
on E: Exception do Log(' cache not written: ' + E.Message);
|
||||
end;
|
||||
@@ -807,8 +821,8 @@ begin
|
||||
// prochaine ouverture pour retrouver ce qu'on vient d'ecrire.
|
||||
FETag := LPut.ETag;
|
||||
try
|
||||
SaveVault(LNew, FETag, FSyncPwd, BiometricAvailable, False, FPin,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
SaveVault(LNew, FETag, FSyncPwd, BiometricAvailable, False,
|
||||
FPinSealed, FKey, FKeySalt, FKeyIters);
|
||||
except
|
||||
on E: Exception do Log(' cache not written: ' + E.Message);
|
||||
end;
|
||||
@@ -1057,6 +1071,7 @@ begin
|
||||
FToken := TNetEncoding.URL.Decode(Param('t')).Trim;
|
||||
FPwd := TNetEncoding.URL.Decode(Param('p'));
|
||||
FPin := TNetEncoding.URL.Decode(Param('c'));
|
||||
FPinNew := TNetEncoding.URL.Decode(Param('c2'));
|
||||
// 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.
|
||||
@@ -1164,6 +1179,9 @@ begin
|
||||
FKeySalt := nil;
|
||||
FKeyIters := 0;
|
||||
FPlain := '';
|
||||
// Le code aussi : le retenir apres un verrouillage reviendrait a ne pas
|
||||
// verrouiller. Le garde de SaveVault protege le cache d'ici la.
|
||||
FPinSealed := '';
|
||||
ShowUnlock;
|
||||
end;
|
||||
|
||||
@@ -1235,6 +1253,7 @@ begin
|
||||
// un coffre vide.
|
||||
SaveVault(LPlain, '', APwd, BiometricAvailable, False, FPin,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
FPinSealed := FPin; // coffre neuf : ce code est desormais le sien
|
||||
TThread.Queue(nil,
|
||||
procedure
|
||||
begin
|
||||
@@ -1304,7 +1323,7 @@ begin
|
||||
TThread.Queue(nil, procedure
|
||||
begin Log('Vault belongs to ' + LOwner + ', not ' + AUser); end);
|
||||
|
||||
SaveVault(LPlain, FETag, APwd, BiometricAvailable, False, FPin,
|
||||
SaveVault(LPlain, FETag, APwd, BiometricAvailable, False, FPinSealed,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
TThread.Queue(nil,
|
||||
procedure
|
||||
@@ -1372,11 +1391,33 @@ begin
|
||||
// synchroniser ne suffisait pas : une synchronisation qui repond 304
|
||||
// n'ecrit rien, donc le cache restait dans son ancienne forme et le code
|
||||
// semblait sans effet. L'ouverture, elle, tient le clair a chaque fois.
|
||||
if (FPin <> '') and (not VaultNeedsPin) then
|
||||
// Le cache vient de s'ouvrir : le code tape est donc le bon, ou il n'y en
|
||||
// avait pas. C'est la seule certitude de ce genre dans toute l'application.
|
||||
if VaultNeedsPin then FPinSealed := FPin;
|
||||
|
||||
// CHANGER LE CODE - ici, et ici seulement. Il faut avoir ouvert le coffre,
|
||||
// donc connaitre le code courant : sans cela une frappe distraite suffirait
|
||||
// a rendre le cache inouvrable pour son proprietaire.
|
||||
if (FPinNew <> '') and (FPinNew <> FPinSealed) then
|
||||
begin
|
||||
try
|
||||
SaveVault(LPlain, LETag, LPwd, False, False, FPinNew,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
FPinSealed := FPinNew;
|
||||
FPinNew := '';
|
||||
Log('Unlock code changed');
|
||||
WebCall('PM.toast("Unlock code changed")');
|
||||
WebCall('PM.pinChanged()');
|
||||
except
|
||||
on E: Exception do Log('Unlock code not changed: ' + E.Message);
|
||||
end;
|
||||
end
|
||||
else if (FPin <> '') and (not VaultNeedsPin) then
|
||||
begin
|
||||
try
|
||||
SaveVault(LPlain, LETag, LPwd, False, False, FPin,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
FPinSealed := FPin;
|
||||
if VaultNeedsPin then
|
||||
Log('Cache re-sealed under the unlock code')
|
||||
else
|
||||
@@ -1392,7 +1433,7 @@ begin
|
||||
// protection a tres probablement invalide l'ancienne, et le
|
||||
// chiffrement ne saurait pas le voir - on scellerait un fichier
|
||||
// impossible a rouvrir.
|
||||
SaveVault(LPlain, LETag, LPwd, True, True, FPin,
|
||||
SaveVault(LPlain, LETag, LPwd, True, True, FPinSealed,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
if VaultNeedsAuth then
|
||||
Log('Cache re-sealed under authentication')
|
||||
@@ -1744,7 +1785,7 @@ begin
|
||||
try
|
||||
// Le mot de passe part avec le coffre : c'est lui qui permettra au
|
||||
// prochain lancement de rafraichir sans rien demander.
|
||||
SaveVault(LPlain, FETag, APwd, LCanAuth, False, FPin,
|
||||
SaveVault(LPlain, FETag, APwd, LCanAuth, False, FPinSealed,
|
||||
FKey, FKeySalt, FKeyIters);
|
||||
Log(' cache now holds sha: ' + FETag);
|
||||
if VaultNeedsAuth then
|
||||
|
||||
Reference in New Issue
Block a user