feat(android): deux secrets separes, comme sur le poste
Le mot de passe de creation scellait le conteneur local ET pretendait sceller l'instantane du depot. Un coffre cree ici publiait donc sous son propre mot de passe, que le poste ne savait plus ouvrir - et le rafraichissement echouait avec "wrong password" sur un depot pourtant intact. FVaultPwd ouvre le conteneur local et s'arrete la. Le secret du depot vit dans les reglages, doit etre le meme sur tous les appareils, et c'est lui seul qui ouvre et rescelle l'instantane. Les deux ne se rencontrent qu'a UN endroit, la reconciliation : le conteneur local s'ouvre sous le sien, puis la cle est jetee avant de toucher au depot - la garder rescellerait le depot sous le mauvais secret. Ailleurs ils ne se croisent jamais : un coffre ne ici ne publie pas, un coffre publie n'a plus de conteneur local. Migration : rien a rechiffrer. Les coffres existants ont simplement les deux valeurs egales, et cessent d'etre forces de le rester. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -395,7 +395,11 @@
|
|||||||
<div class="in-x" data-clear="user">×</div>
|
<div class="in-x" data-clear="user">×</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="in-wrap">
|
<div class="in-wrap">
|
||||||
<input class="field-in" id="pass" placeholder="master password"
|
<!-- DEUX SECRETS, DEUX NOMS. Celui-ci ouvre le coffre garde sur cet
|
||||||
|
appareil. Il s'appelait "master password" et servait aussi de
|
||||||
|
secret du depot - un coffre cree ici pretendait alors sceller le
|
||||||
|
depot avec lui, que le poste ne savait plus ouvrir. -->
|
||||||
|
<input class="field-in" id="pass" placeholder="vault password"
|
||||||
type="password" autocomplete="off">
|
type="password" autocomplete="off">
|
||||||
<div class="in-x" data-clear="pass">×</div>
|
<div class="in-x" data-clear="pass">×</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -535,12 +539,15 @@
|
|||||||
<div class="in-x" data-clear="sTok">×</div>
|
<div class="in-x" data-clear="sTok">×</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="in-wrap">
|
<div class="in-wrap">
|
||||||
<input class="field-in" id="sPwd" placeholder="master password"
|
<input class="field-in" id="sPwd" placeholder="sync password"
|
||||||
type="password" autocomplete="off">
|
type="password" autocomplete="off">
|
||||||
<div class="in-x" data-clear="sPwd">×</div>
|
<div class="in-x" data-clear="sPwd">×</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="note">The master password never leaves this device. It is what
|
<div class="note">The sync password never leaves this device. It is what
|
||||||
decrypts the vault; the token only opens the repository.</div>
|
encrypts the copy kept in the repository, so it must be the same on every
|
||||||
|
device that shares it; the token only opens the repository. It is not the
|
||||||
|
password that opens a vault created on this phone - that one is asked for
|
||||||
|
on the home screen.</div>
|
||||||
|
|
||||||
<h2>Unlock code</h2>
|
<h2>Unlock code</h2>
|
||||||
<div class="in-wrap">
|
<div class="in-wrap">
|
||||||
|
|||||||
+79
-34
@@ -134,6 +134,17 @@ type
|
|||||||
// rafraichissement de fond silencieux : sans lui, chaque modification
|
// rafraichissement de fond silencieux : sans lui, chaque modification
|
||||||
// faite sur le poste ferait apparaitre une invite au lancement.
|
// faite sur le poste ferait apparaitre une invite au lancement.
|
||||||
FSyncPwd: string;
|
FSyncPwd: string;
|
||||||
|
/// LE MOT DE PASSE DU COFFRE LOCAL, et lui seul.
|
||||||
|
///
|
||||||
|
/// Il ouvre `vault-local.json` - un coffre ne sur ce telephone et pas
|
||||||
|
/// encore publie. Il n'a RIEN a voir avec le secret du depot, meme si
|
||||||
|
/// l'utilisateur choisit la meme valeur pour les deux : ce sont deux roles,
|
||||||
|
/// et les confondre faisait qu'un coffre cree ici pretendait sceller le
|
||||||
|
/// depot avec son propre mot de passe.
|
||||||
|
///
|
||||||
|
/// Vide des que le conteneur local disparait - c'est-a-dire des que la
|
||||||
|
/// reconciliation a passe l'autorite au depot.
|
||||||
|
FVaultPwd: string;
|
||||||
|
|
||||||
/// Coordonnees du serveur. Delphi en est le detenteur ; la page ne fait
|
/// Coordonnees du serveur. Delphi en est le detenteur ; la page ne fait
|
||||||
/// que les editer et les lui renvoyer quand un champ est quitte.
|
/// que les editer et les lui renvoyer quand un champ est quitte.
|
||||||
@@ -895,9 +906,20 @@ begin
|
|||||||
RefuseChange('Busy - wait for the current operation');
|
RefuseChange('Busy - wait for the current operation');
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
if FSyncPwd = '' then
|
// DEUX SECRETS, DEUX EXIGENCES. Un coffre ne ici n'ecrit que son conteneur
|
||||||
|
// local et n'a donc pas besoin du secret du depot ; un coffre publie n'a pas
|
||||||
|
// de conteneur local et n'a donc pas besoin du sien.
|
||||||
|
if HasLocalContainer then
|
||||||
begin
|
begin
|
||||||
RefuseChange('Master password missing - open the vault first');
|
if FVaultPwd = '' then
|
||||||
|
begin
|
||||||
|
RefuseChange('Vault password missing - open the vault first');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else if FSyncPwd = '' then
|
||||||
|
begin
|
||||||
|
RefuseChange('Sync password missing - open the vault first');
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
SetBusy(True);
|
SetBusy(True);
|
||||||
@@ -929,7 +951,7 @@ begin
|
|||||||
// donc le cache avant toute chose, et le depot suit quand il peut.
|
// donc le cache avant toute chose, et le depot suit quand il peut.
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
LSw := TStopwatch.StartNew;
|
LSw := TStopwatch.StartNew;
|
||||||
if LLocalOnly then LPlain := OpenContainer(LoadLocalContainer, FSyncPwd)
|
if LLocalOnly then LPlain := OpenContainer(LoadLocalContainer, FVaultPwd)
|
||||||
else LPlain := FPlain;
|
else LPlain := FPlain;
|
||||||
if LPlain = '' then
|
if LPlain = '' then
|
||||||
raise Exception.Create('no snapshot in hand - open the vault again');
|
raise Exception.Create('no snapshot in hand - open the vault again');
|
||||||
@@ -1225,11 +1247,23 @@ begin
|
|||||||
Status('No local vault to reconcile');
|
Status('No local vault to reconcile');
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
if FSyncPwd = '' then
|
// LES DEUX SONT REQUIS ICI, ET NULLE PART AILLEURS : c'est le seul moment ou
|
||||||
|
// le conteneur local et l'instantane du depot se rencontrent.
|
||||||
|
if FVaultPwd = '' then
|
||||||
begin
|
begin
|
||||||
Status('Open the vault first');
|
Status('Open the vault first');
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
// CE QUI EST DANS LES REGLAGES FAIT FOI quand le coffre n'en porte pas encore.
|
||||||
|
// Un coffre ne ici n'a jamais vu le depot : son cache ne contient donc aucun
|
||||||
|
// secret de sync, et c'est le champ des reglages qui le donne. Meme
|
||||||
|
// precedence que DoSync, pour que les deux ne divergent pas.
|
||||||
|
if FSyncPwd = '' then FSyncPwd := FPwd;
|
||||||
|
if FSyncPwd = '' then
|
||||||
|
begin
|
||||||
|
Status('Set the sync password in Settings');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
SetBusy(True);
|
SetBusy(True);
|
||||||
Status('Reconciling...');
|
Status('Reconciling...');
|
||||||
|
|
||||||
@@ -1248,7 +1282,17 @@ begin
|
|||||||
LErr := '';
|
LErr := '';
|
||||||
try
|
try
|
||||||
try
|
try
|
||||||
LLocal := OpenContainer(LoadLocalContainer, FSyncPwd);
|
// 1. LE COFFRE LOCAL, sous SON mot de passe.
|
||||||
|
LLocal := OpenContainer(LoadLocalContainer, FVaultPwd);
|
||||||
|
|
||||||
|
// 2. ON JETTE LA CLE QUI VIENT DE SERVIR. Elle appartient au conteneur
|
||||||
|
// local ; tout ce qui suit concerne le depot, scelle par un autre
|
||||||
|
// secret. La garder ferait resceller le depot sous le mot de passe du
|
||||||
|
// coffre local - c'est exactement la confusion qu'on defait ici, et
|
||||||
|
// elle rendrait l'instantane illisible pour le poste.
|
||||||
|
FKey := nil;
|
||||||
|
FKeySalt := nil;
|
||||||
|
FKeyIters := 0;
|
||||||
|
|
||||||
for LAttempt := 1 to 3 do
|
for LAttempt := 1 to 3 do
|
||||||
begin
|
begin
|
||||||
@@ -1268,6 +1312,8 @@ begin
|
|||||||
end
|
end
|
||||||
else if LGet.Status = ssOK then
|
else if LGet.Status = ssOK then
|
||||||
begin
|
begin
|
||||||
|
// Ouvrir le distant pose la cle DU DEPOT dans FKey : c'est elle
|
||||||
|
// qui servira a le resceller quelques lignes plus bas.
|
||||||
LRemote := OpenContainer(LGet.Container, FSyncPwd);
|
LRemote := OpenContainer(LGet.Container, FSyncPwd);
|
||||||
LMerged := MergeSnapshots(LLocal, LRemote);
|
LMerged := MergeSnapshots(LLocal, LRemote);
|
||||||
end
|
end
|
||||||
@@ -1277,7 +1323,11 @@ begin
|
|||||||
Break;
|
Break;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
LBody := EncryptContainerWithKey(LMerged, FKey, FKeySalt, FKeyIters);
|
// Depot vide : aucune cle n'a ete tiree de lui, il faut donc la
|
||||||
|
// deriver du mot de passe de sync. C'est le seul chemin qui paie les
|
||||||
|
// quelques secondes, et il n'arrive qu'une fois.
|
||||||
|
if FKey = nil then LBody := EncryptContainer(LMerged, FSyncPwd)
|
||||||
|
else LBody := EncryptContainerWithKey(LMerged, FKey, FKeySalt, FKeyIters);
|
||||||
LPut := LClient.Put(LBody, LGet.ETag, 'reconciled from phone');
|
LPut := LClient.Put(LBody, LGet.ETag, 'reconciled from phone');
|
||||||
if LPut.Status = ssOK then
|
if LPut.Status = ssOK then
|
||||||
begin
|
begin
|
||||||
@@ -1298,7 +1348,11 @@ begin
|
|||||||
// PUBLIE, DONC PLUS BESOIN DU CONTENEUR LOCAL. C'est ce geste qui
|
// PUBLIE, DONC PLUS BESOIN DU CONTENEUR LOCAL. C'est ce geste qui
|
||||||
// passe l'autorite au depot ; le faire avant la publication aurait
|
// passe l'autorite au depot ; le faire avant la publication aurait
|
||||||
// perdu le coffre.
|
// perdu le coffre.
|
||||||
|
// Le conteneur local n'existe plus : son mot de passe non plus.
|
||||||
|
// L'autorite est passee au depot, et c'est le secret de sync qui
|
||||||
|
// commande desormais.
|
||||||
DropLocalContainer;
|
DropLocalContainer;
|
||||||
|
FVaultPwd := '';
|
||||||
try
|
try
|
||||||
SaveVault(LMerged, FETag, FSyncPwd, BiometricAvailable, False,
|
SaveVault(LMerged, FETag, FSyncPwd, BiometricAvailable, False,
|
||||||
FPinSealed, FKey, FKeySalt, FKeyIters);
|
FPinSealed, FKey, FKeySalt, FKeyIters);
|
||||||
@@ -1747,6 +1801,9 @@ begin
|
|||||||
FKeyIters := 0;
|
FKeyIters := 0;
|
||||||
FPlain := '';
|
FPlain := '';
|
||||||
FHistory := nil;
|
FHistory := nil;
|
||||||
|
// Le mot de passe du coffre local part avec le clair : le garder ouvrirait
|
||||||
|
// le conteneur sans rien demander.
|
||||||
|
FVaultPwd := '';
|
||||||
// Le code aussi : le retenir apres un verrouillage reviendrait a ne pas
|
// Le code aussi : le retenir apres un verrouillage reviendrait a ne pas
|
||||||
// verrouiller. Le garde de SaveVault protege le cache d'ici la.
|
// verrouiller. Le garde de SaveVault protege le cache d'ici la.
|
||||||
FPinSealed := '';
|
FPinSealed := '';
|
||||||
@@ -1827,19 +1884,12 @@ begin
|
|||||||
begin
|
begin
|
||||||
SetBusy(False);
|
SetBusy(False);
|
||||||
FUser := AUser;
|
FUser := AUser;
|
||||||
FSyncPwd := APwd;
|
// CE MOT DE PASSE NE TOUCHE PAS AU DEPOT. Il scelle le conteneur
|
||||||
// LE COFFRE QUI VIENT DE S'OUVRIR DIT LE MOT DE PASSE DE SYNC.
|
// local, rien d'autre. Le secret du depot vit dans les reglages et
|
||||||
//
|
// doit etre le meme sur tous les appareils - le remplacer par
|
||||||
// Sans cette ligne, FPwd gardait la valeur de premier lancement -
|
// celui-ci ferait publier un instantane que le poste ne saurait
|
||||||
// en debogage, une constante ; en release, du vide - et les
|
// plus ouvrir.
|
||||||
// reglages affichaient donc un mot de passe qui n'etait pas celui
|
FVaultPwd := APwd;
|
||||||
// du coffre detenu. Pire, DoSync la PREFERE a celle du coffre au
|
|
||||||
// nom de "ce qui est tape l'emporte" : or rien n'avait ete tape.
|
|
||||||
// Les deux chemins de synchronisation travaillaient ainsi avec
|
|
||||||
// deux mots de passe differents sur le meme coffre.
|
|
||||||
FPwd := APwd;
|
|
||||||
WebCall(Format('PM.setConfig(%s, %s, %s, false)',
|
|
||||||
[JsArg(FUrl), JsArg(FToken), JsArg(FPwd)]));
|
|
||||||
Log('Vault created locally for ' + AUser);
|
Log('Vault created locally for ' + AUser);
|
||||||
AdoptVault(LPlain);
|
AdoptVault(LPlain);
|
||||||
end);
|
end);
|
||||||
@@ -1887,6 +1937,7 @@ begin
|
|||||||
LDoc: TJSONObject;
|
LDoc: TJSONObject;
|
||||||
begin
|
begin
|
||||||
try
|
try
|
||||||
|
FVaultPwd := APwd; // avant l'ouverture : c'est lui qui la fait
|
||||||
LPlain := OpenContainer(LoadLocalContainer, APwd);
|
LPlain := OpenContainer(LoadLocalContainer, APwd);
|
||||||
LOwner := '';
|
LOwner := '';
|
||||||
LDoc := TJSONObject.ParseJSONValue(LPlain) as TJSONObject;
|
LDoc := TJSONObject.ParseJSONValue(LPlain) as TJSONObject;
|
||||||
@@ -1910,19 +1961,9 @@ begin
|
|||||||
begin
|
begin
|
||||||
SetBusy(False);
|
SetBusy(False);
|
||||||
if LOwner <> '' then FUser := LOwner else FUser := AUser;
|
if LOwner <> '' then FUser := LOwner else FUser := AUser;
|
||||||
FSyncPwd := APwd;
|
// Meme regle qu'a la creation : ce mot de passe ouvre le conteneur
|
||||||
// LE COFFRE QUI VIENT DE S'OUVRIR DIT LE MOT DE PASSE DE SYNC.
|
// local et s'arrete la.
|
||||||
//
|
FVaultPwd := APwd;
|
||||||
// Sans cette ligne, FPwd gardait la valeur de premier lancement -
|
|
||||||
// en debogage, une constante ; en release, du vide - et les
|
|
||||||
// reglages affichaient donc un mot de passe qui n'etait pas celui
|
|
||||||
// du coffre detenu. Pire, DoSync la PREFERE a celle du coffre au
|
|
||||||
// nom de "ce qui est tape l'emporte" : or rien n'avait ete tape.
|
|
||||||
// Les deux chemins de synchronisation travaillaient ainsi avec
|
|
||||||
// deux mots de passe differents sur le meme coffre.
|
|
||||||
FPwd := APwd;
|
|
||||||
WebCall(Format('PM.setConfig(%s, %s, %s, false)',
|
|
||||||
[JsArg(FUrl), JsArg(FToken), JsArg(FPwd)]));
|
|
||||||
|
|
||||||
AdoptVault(LPlain);
|
AdoptVault(LPlain);
|
||||||
end);
|
end);
|
||||||
@@ -2815,7 +2856,9 @@ begin
|
|||||||
ClearUnlockCode;
|
ClearUnlockCode;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
if (FPlain = '') or (FSyncPwd = '') then
|
// Le secret du depot n'entre pas ici : le code scelle le CACHE, et un coffre
|
||||||
|
// ne sur ce telephone n'a pas encore de depot.
|
||||||
|
if FPlain = '' then
|
||||||
begin
|
begin
|
||||||
WebCall('PM.toast("Open the vault first")');
|
WebCall('PM.toast("Open the vault first")');
|
||||||
Exit;
|
Exit;
|
||||||
@@ -2855,7 +2898,9 @@ begin
|
|||||||
WebCall('PM.toast("No unlock code to remove")');
|
WebCall('PM.toast("No unlock code to remove")');
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
if (FPlain = '') or (FSyncPwd = '') then
|
// Le secret du depot n'entre pas ici : le code scelle le CACHE, et un coffre
|
||||||
|
// ne sur ce telephone n'a pas encore de depot.
|
||||||
|
if FPlain = '' then
|
||||||
begin
|
begin
|
||||||
WebCall('PM.toast("Open the vault first")');
|
WebCall('PM.toast("Open the vault first")');
|
||||||
Exit;
|
Exit;
|
||||||
|
|||||||
Reference in New Issue
Block a user