feat(android): an unlock code that survives a screen-lock change
The reported pain: changing the pattern destroys the local cache every time. That is Android doing exactly what it promises - a key created with setUserAuthenticationRequired is invalidated the moment the screen lock changes, so a new lock cannot open data sealed under the old one. No storage format changes that, which is why SQLite would not have helped: the problem is what the key is tied to, not what the file looks like. So the cache gains a third sealing format, VER_PIN, with two layers: inner - AES-GCM under PBKDF2(code, 100k) : it takes the code outer - the Keystore AES key WITHOUT the auth constraint : it takes the device The outer key is never invalidated, so the file survives any change to the screen lock; the code restores the protection that removing the constraint would otherwise cost. 100k iterations rather than 600k because a four to six digit code carries little entropy - more rounds mostly slow the honest user, and the real barrier is the outer layer requiring this phone. The code lives in a fourth settings field and is NEVER stored: empty at every launch, typed to open, and used to re-seal on every write. That is what separates it from the token, whose persistence is a convenience with no consequence since it only opens ciphertext. Built but NOT installed - the phone went off the network. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -261,6 +261,14 @@
|
||||
type="password" autocomplete="off">
|
||||
<div class="in-x" data-clear="pwd">×</div>
|
||||
</div>
|
||||
<!-- JAMAIS PRE-REMPLI, contrairement aux trois autres : ce code protege
|
||||
le cache local, et le retenir reviendrait a ne rien proteger. Il se
|
||||
retape a chaque lancement. -->
|
||||
<div class="in-wrap">
|
||||
<input class="field-in" id="pin" placeholder="unlock code (local cache)"
|
||||
type="password" inputmode="numeric" autocomplete="off">
|
||||
<div class="in-x" data-clear="pin">×</div>
|
||||
</div>
|
||||
</div>
|
||||
<pre id="log" hidden></pre>
|
||||
</div>
|
||||
@@ -345,7 +353,8 @@ var entries = [], folders = [], folder = '', query = '', view = 'unlock';
|
||||
// --- deverrouillage --------------------------------------------------------
|
||||
var elUrl = document.getElementById('url'),
|
||||
elTok = document.getElementById('tok'),
|
||||
elPwd = document.getElementById('pwd');
|
||||
elPwd = document.getElementById('pwd'),
|
||||
elPin = document.getElementById('pin');
|
||||
|
||||
// Les trois valeurs remontent A LA PERTE DU FOCUS, pas a chaque touche : c'est
|
||||
// une navigation par champ modifie au lieu d'une par caractere. Delphi les
|
||||
@@ -353,14 +362,16 @@ var elUrl = document.getElementById('url'),
|
||||
function pushConfig() {
|
||||
send('cfg?u=' + encodeURIComponent(elUrl.value.trim())
|
||||
+ '&t=' + encodeURIComponent(elTok.value.trim())
|
||||
+ '&p=' + encodeURIComponent(elPwd.value));
|
||||
+ '&p=' + encodeURIComponent(elPwd.value)
|
||||
+ '&c=' + encodeURIComponent(elPin.value));
|
||||
}
|
||||
[elUrl, elTok, elPwd].forEach(function (el) {
|
||||
[elUrl, elTok, elPwd, elPin].forEach(function (el) {
|
||||
el.addEventListener('change', pushConfig);
|
||||
el.addEventListener('blur', pushConfig);
|
||||
});
|
||||
|
||||
document.getElementById('btnOpen').addEventListener('click', function () {
|
||||
pushConfig(); // le code vient peut-etre d'etre tape
|
||||
send('openvault');
|
||||
});
|
||||
document.getElementById('btnSync').addEventListener('click', function () {
|
||||
@@ -640,6 +651,7 @@ var PM = {
|
||||
s.style.opacity = busy ? .4 : 1;
|
||||
},
|
||||
setConfig: function (url, tok, pwd, show) {
|
||||
// elPin volontairement absent : rien ne le restaure, jamais.
|
||||
elUrl.value = url; elTok.value = tok; elPwd.value = pwd;
|
||||
// Replier des champs vides revient a cacher la seule chose a faire.
|
||||
document.getElementById('cfg').hidden = !show;
|
||||
|
||||
Reference in New Issue
Block a user