mirror of https://github.com/keeweb/keeweb
using yubikeys for opening files
parent
7394754b6f
commit
2ba9a03e2c
|
@ -60,12 +60,14 @@ const YubiKey = {
|
|||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
yubiKeys = yubiKeys.map(({ serial, pid, version, slot1, slot2 }) => {
|
||||
yubiKeys = yubiKeys.map(({ serial, vid, pid, version, slot1, slot2 }) => {
|
||||
return {
|
||||
fullName: this.getKeyFullName(pid, version, serial),
|
||||
vid,
|
||||
pid,
|
||||
serial,
|
||||
slot1,
|
||||
slot2
|
||||
slot2,
|
||||
fullName: this.getKeyFullName(pid, version, serial)
|
||||
};
|
||||
});
|
||||
callback(null, yubiKeys);
|
||||
|
@ -245,10 +247,14 @@ const YubiKey = {
|
|||
});
|
||||
},
|
||||
|
||||
calculateChalResp(serial, vid, pid, slot, challenge, callback) {
|
||||
const yubiKey = { serial, vid, pid };
|
||||
calculateChalResp(chalResp, challenge, callback) {
|
||||
const { vid, pid, serial, slot } = chalResp;
|
||||
const yubiKey = { vid, pid, serial };
|
||||
this.ykChalResp.challengeResponse(yubiKey, challenge, slot, (err, response) => {
|
||||
if (err) {
|
||||
if (err.touchRequested) {
|
||||
return;
|
||||
}
|
||||
// TODO: handle touch and missing YubiKeys
|
||||
return callback(err);
|
||||
}
|
||||
|
|
|
@ -655,7 +655,8 @@ class AppModel {
|
|||
keyFileName: params.keyFileName,
|
||||
keyFilePath: params.keyFilePath,
|
||||
backup: (fileInfo && fileInfo.backup) || null,
|
||||
fingerprint: (fileInfo && fileInfo.fingerprint) || null
|
||||
fingerprint: (fileInfo && fileInfo.fingerprint) || null,
|
||||
chalResp: params.chalResp
|
||||
});
|
||||
const openComplete = err => {
|
||||
if (err) {
|
||||
|
@ -687,7 +688,7 @@ class AppModel {
|
|||
this.fileOpened(file, data, params);
|
||||
};
|
||||
const open = () => {
|
||||
file.open(params.password, data, params.keyFileData, openComplete);
|
||||
file.open(params.password, data, params.keyFileData, params.chalResp, openComplete);
|
||||
};
|
||||
if (needLoadKeyFile) {
|
||||
Storage.file.load(params.keyFilePath, {}, (err, data) => {
|
||||
|
@ -745,7 +746,8 @@ class AppModel {
|
|||
syncDate: file.syncDate || dt,
|
||||
openDate: dt,
|
||||
backup: file.backup,
|
||||
fingerprint: file.fingerprint
|
||||
fingerprint: file.fingerprint,
|
||||
chalResp: file.chalResp
|
||||
});
|
||||
switch (this.settings.rememberKeyFiles) {
|
||||
case 'data':
|
||||
|
|
|
@ -16,7 +16,8 @@ const DefaultProperties = {
|
|||
keyFilePath: null,
|
||||
opts: null,
|
||||
backup: null,
|
||||
fingerprint: null
|
||||
fingerprint: null,
|
||||
chalResp: null
|
||||
};
|
||||
|
||||
class FileInfoModel extends Model {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { GroupModel } from 'models/group-model';
|
|||
import { IconUrlFormat } from 'util/formatting/icon-url-format';
|
||||
import { Logger } from 'util/logger';
|
||||
import { mapObject } from 'util/fn';
|
||||
import { YubiKey } from 'comp/app/yubikey';
|
||||
|
||||
const logger = new Logger('file');
|
||||
|
||||
|
@ -20,9 +21,10 @@ class FileModel extends Model {
|
|||
});
|
||||
}
|
||||
|
||||
open(password, fileData, keyFileData, callback) {
|
||||
open(password, fileData, keyFileData, chalResp, callback) {
|
||||
try {
|
||||
const credentials = new kdbxweb.Credentials(password, keyFileData);
|
||||
const challengeResponse = this.makeChallengeResponse(chalResp);
|
||||
const credentials = new kdbxweb.Credentials(password, keyFileData, challengeResponse);
|
||||
const ts = logger.ts();
|
||||
|
||||
kdbxweb.Kdbx.load(fileData, credentials)
|
||||
|
@ -56,7 +58,7 @@ class FileModel extends Model {
|
|||
logger.info(
|
||||
'Error opening file with empty password, try to open with null password'
|
||||
);
|
||||
return this.open(null, fileData, keyFileData, callback);
|
||||
return this.open(null, fileData, keyFileData, chalResp, callback);
|
||||
}
|
||||
logger.error('Error opening file', err.code, err.message, err);
|
||||
callback(err);
|
||||
|
@ -67,6 +69,26 @@ class FileModel extends Model {
|
|||
}
|
||||
}
|
||||
|
||||
makeChallengeResponse(params) {
|
||||
if (!params) {
|
||||
return null;
|
||||
}
|
||||
return challenge => {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.debug('Calculating ChalResp using a YubiKey');
|
||||
const ts = logger.ts();
|
||||
challenge = Buffer.from(challenge);
|
||||
YubiKey.calculateChalResp(params, challenge, (err, response) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
logger.info('Calculated ChalResp', logger.ts(ts));
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
kdfArgsToString(header) {
|
||||
if (header.kdfParameters) {
|
||||
return header.kdfParameters
|
||||
|
@ -707,6 +729,7 @@ FileModel.defineModelProperties({
|
|||
groupMap: null,
|
||||
keyFileName: '',
|
||||
keyFilePath: null,
|
||||
chalResp: null,
|
||||
passwordLength: 0,
|
||||
path: '',
|
||||
opts: null,
|
||||
|
|
|
@ -50,10 +50,12 @@ class OpenChalRespView extends View {
|
|||
this.error = err;
|
||||
this.yubiKeys = [];
|
||||
if (yubiKeys) {
|
||||
for (const { fullName, serial, slot1, slot2 } of yubiKeys) {
|
||||
for (const { fullName, vid, pid, serial, slot1, slot2 } of yubiKeys) {
|
||||
for (const slot of [slot1 ? 1 : 0, slot2 ? 2 : 0].filter(s => s)) {
|
||||
this.yubiKeys.push({
|
||||
fullName,
|
||||
vid,
|
||||
pid,
|
||||
serial,
|
||||
slot
|
||||
});
|
||||
|
@ -66,8 +68,11 @@ class OpenChalRespView extends View {
|
|||
|
||||
itemClick(e) {
|
||||
const el = e.target.closest('[data-serial]');
|
||||
const { serial, slot } = el.dataset;
|
||||
this.emit('select', { serial, slot });
|
||||
const vid = +el.dataset.vid;
|
||||
const pid = +el.dataset.pid;
|
||||
const serial = +el.dataset.serial;
|
||||
const slot = +el.dataset.slot;
|
||||
this.emit('select', { vid, pid, serial, slot });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -339,6 +339,12 @@ class OpenView extends View {
|
|||
this.focusInput();
|
||||
}
|
||||
|
||||
displayOpenChalResp() {
|
||||
this.$el
|
||||
.find('.open__settings-yubikey')
|
||||
.toggleClass('open__settings-yubikey--active', !!this.params.chalResp);
|
||||
}
|
||||
|
||||
setFile(file, keyFile, fileReadyCallback) {
|
||||
this.reading = 'fileData';
|
||||
this.processFile(file, success => {
|
||||
|
@ -587,8 +593,10 @@ class OpenView extends View {
|
|||
this.params.keyFilePath = fileInfo.keyFilePath;
|
||||
this.params.keyFileData = null;
|
||||
this.params.opts = fileInfo.opts;
|
||||
this.params.chalResp = fileInfo.chalResp;
|
||||
this.displayOpenFile();
|
||||
this.displayOpenKeyFile();
|
||||
this.displayOpenChalResp();
|
||||
|
||||
this.openFileWithFingerprint(fileInfo);
|
||||
|
||||
|
@ -1051,15 +1059,17 @@ class OpenView extends View {
|
|||
this.el
|
||||
.querySelector('.open__settings-yubikey')
|
||||
.classList.remove('open__settings-yubikey--active');
|
||||
this.focusInput();
|
||||
return;
|
||||
}
|
||||
|
||||
const chalRespView = new OpenChalRespView();
|
||||
chalRespView.on('select', e => {
|
||||
this.params.chalResp = { serial: e.serial, slot: e.slot };
|
||||
chalRespView.on('select', ({ vid, pid, serial, slot }) => {
|
||||
this.params.chalResp = { vid, pid, serial, slot };
|
||||
this.el
|
||||
.querySelector('.open__settings-yubikey')
|
||||
.classList.add('open__settings-yubikey--active');
|
||||
this.focusInput();
|
||||
});
|
||||
|
||||
Alerts.alert({
|
||||
|
|
|
@ -11,7 +11,12 @@
|
|||
<div class="open-chal-resp__head">{{res 'openChalRespSelectYubiKey'}}:</div>
|
||||
<div>
|
||||
{{#each yubiKeys as |yk|}}
|
||||
<div class="open-chal-resp__item" data-serial="{{yk.serial}}" data-slot="{{yk.slot}}">
|
||||
<div class="open-chal-resp__item"
|
||||
data-vid="{{yk.vid}}"
|
||||
data-pid="{{yk.pid}}"
|
||||
data-serial="{{yk.serial}}"
|
||||
data-slot="{{yk.slot}}"
|
||||
>
|
||||
{{yk.fullName}}, {{res 'openChalRespSlot'}} {{yk.slot}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
|
Loading…
Reference in New Issue