using yubikeys for opening files

This commit is contained in:
antelle 2020-05-30 08:10:19 +02:00
parent 7394754b6f
commit 2ba9a03e2c
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
7 changed files with 70 additions and 18 deletions

View File

@ -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);
}

View File

@ -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':

View File

@ -16,7 +16,8 @@ const DefaultProperties = {
keyFilePath: null,
opts: null,
backup: null,
fingerprint: null
fingerprint: null,
chalResp: null
};
class FileInfoModel extends Model {

View File

@ -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,

View File

@ -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 });
}
}

View File

@ -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({

View File

@ -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}}