App lock: unlock with security keys via the built-in CTAP2 client on Linux too (Chromium returned no PRF result, so adding a YubiKey failed with "does not support hmac-secret"); updates: show only one update dialog at a time (startup and manual check stacked two)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+45
-1
@@ -210,4 +210,48 @@ async function makeCredential(ui, { timeoutMs = 60000 } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { makeCredential, CtapError, CANCEL_CODES, PIN };
|
||||
// Entspricht WebAuthn-PRF (evalByCredential) über hmac-secret, ohne PIN (wie Chromium mit
|
||||
// userVerification "discouraged"). creds: [{ credId: Buffer, salt: Buffer }] – salt ist der rohe PRF-Eingabewert.
|
||||
// Liefert { credId: Buffer, secret: Buffer(32) }, oder null, wenn kein FIDO2-Gerät per hidraw erreichbar ist.
|
||||
async function getHmacSecret(ui, creds, { timeoutMs = 60000 } = {}) {
|
||||
let dev;
|
||||
try { dev = Device.open(); } catch { return null; }
|
||||
if (!dev) return null;
|
||||
let timer;
|
||||
try {
|
||||
await dev.init();
|
||||
const info = await dev.cbor(0x04);
|
||||
if (!(info.get(1) || []).some((v) => String(v).startsWith('FIDO_2'))) return null;
|
||||
const rpId = 'localhost';
|
||||
const desc = (c) => new Map([['id', c.credId], ['type', 'public-key']]);
|
||||
// Welches Credential liegt auf diesem Schlüssel? (Vorabprüfung ohne Berühren, up=false)
|
||||
let cred = null;
|
||||
for (const c of creds) {
|
||||
try {
|
||||
await dev.cbor(0x02, new Map([[1, rpId], [2, crypto.randomBytes(32)], [3, [desc(c)]], [5, new Map([['up', false]])]]));
|
||||
cred = c; break;
|
||||
} catch (e) { if (e.code !== 0x2e) throw e; }
|
||||
}
|
||||
if (!cred) throw new CtapError(0x2e);
|
||||
const { key, platformKey } = await sharedSecret(dev);
|
||||
const salt = crypto.createHash('sha256').update(Buffer.concat([Buffer.from('WebAuthn PRF\0', 'latin1'), cred.salt])).digest();
|
||||
const saltEnc = aes('enc', key, salt);
|
||||
const saltAuth = crypto.createHmac('sha256', key).update(saltEnc).digest().subarray(0, 16);
|
||||
ui.touch(() => dev.cancel());
|
||||
timer = setTimeout(() => dev.cancel(), timeoutMs);
|
||||
const r = await dev.cbor(0x02, new Map([
|
||||
[1, rpId], [2, crypto.randomBytes(32)], [3, [desc(cred)]],
|
||||
[4, new Map([['hmac-secret', new Map([[1, platformKey], [2, saltEnc], [3, saltAuth]])]])],
|
||||
]));
|
||||
const authData = r.get(2);
|
||||
if (!(authData[32] & 0x80)) throw new CtapError(-1);
|
||||
const ext = dec(authData, 37)[0].get('hmac-secret');
|
||||
if (!ext) throw new CtapError(-1);
|
||||
return { credId: Buffer.from(cred.credId), secret: aes('dec', key, ext).subarray(0, 32) };
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
dev.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { makeCredential, getHmacSecret, CtapError, CANCEL_CODES, PIN };
|
||||
|
||||
Reference in New Issue
Block a user