yubikey enrollment

pull/1541/head
antelle 2020-05-31 14:13:08 +02:00
parent b520c58ef4
commit 143f7c6a7a
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
7 changed files with 132 additions and 14 deletions

View File

@ -13,7 +13,7 @@ const ChalRespCalculator = {
cache: {},
getCacheKey(params) {
return `${params.vid}:${params.pid}:${params.serial}`;
return `${params.vid}:${params.pid}:${params.serial}:${params.slot}`;
},
build(params) {
@ -152,8 +152,8 @@ const ChalRespCalculator = {
Launcher.showMainWindow();
return Alerts.alert({
header: Locale.yubiTouchRequestedHeader,
body: Locale.yubiTouchRequestedBody.replace('{}', serial),
header: Locale.yubiKeyTouchRequestedHeader,
body: Locale.yubiKeyTouchRequestedBody.replace('{}', serial),
buttons: [Alerts.buttons.cancel],
iconSvg: 'usb-token',
cancel: () => {

View File

@ -237,7 +237,6 @@
"openChalRespLoading": "Loading the list of YubiKeys",
"openChalRespSelectYubiKey": "Select a YubiKey that you would like to use",
"openChalRespErrorEmpty": "No YubiKeys found",
"openChalRespSlot": "slot",
"detAttDownload": "Shift-click the attachment button to download it or",
"detAttDelToRemove": "Delete to remove",
@ -532,6 +531,10 @@
"setFileExportRawBody": "The exported file will contain your passwords, they will not be encrypted there. Would you like to proceed?",
"setFileDeviceIntro": "One-time codes from this {} will be displayed in the app.",
"setFileDeviceSettings": "Settings",
"setFileYubiKey": "YubiKey",
"setFileDontUseYubiKey": "Don't use a YubiKey",
"setFileYubiKeyHeader": "YubiKey",
"setFileYubiKeyBody": "Using YubiKey as a part of master key is dangerous and you may lose access to your passwords if something goes wrong. Have you made a backup of your file before changing this setting?",
"setShTitle": "Shortcuts",
"setShShowAll": "show all items",
@ -675,6 +678,7 @@
"yubiKeyStuckError": "The YubiKey seems to be stuck, auto-repair can be enabled in app settings.",
"yubiKeyNoKeyHeader": "YubiKey required",
"yubiKeyNoKeyBody": "Please insert your YubiKey with serial number {}",
"yubiTouchRequestedHeader": "Touch your YubiKey",
"yubiTouchRequestedBody": "Please touch your YubiKey with serial number {}"
"yubiKeySlot": "slot",
"yubiKeyTouchRequestedHeader": "Touch your YubiKey",
"yubiKeyTouchRequestedBody": "Please touch your YubiKey with serial number {}"
}

View File

@ -895,7 +895,8 @@ class AppModel {
opts: this.getStoreOpts(file),
modified: file.dirty ? fileInfo.modified : file.modified,
editState: file.dirty ? fileInfo.editState : file.getLocalEditState(),
syncDate: file.syncDate
syncDate: file.syncDate,
chalResp: file.chalResp
});
if (this.settings.rememberKeyFiles === 'data') {
fileInfo.set({

View File

@ -556,6 +556,16 @@ class FileModel extends Model {
return daysDiff > expiryDays;
}
setChallengeResponse(chalResp) {
if (this.chalResp && !AppSettingsModel.yubiKeyRememberChalResp) {
ChalRespCalculator.clearCache(this.chalResp);
}
this.db.credentials.setChallengeResponse(ChalRespCalculator.build(chalResp));
this.db.meta.keyChanged = new Date();
this.chalResp = chalResp;
this.setModified();
}
setKeyChange(force, days) {
if (isNaN(days) || !days || days < 0) {
days = -1;

View File

@ -4,6 +4,8 @@ import { Storage } from 'storage';
import { Shortcuts } from 'comp/app/shortcuts';
import { Launcher } from 'comp/launcher';
import { Alerts } from 'comp/ui/alerts';
import { YubiKey } from 'comp/app/yubikey';
import { UsbListener } from 'comp/app/usb-listener';
import { Links } from 'const/links';
import { AppSettingsModel } from 'models/app-settings-model';
import { DateFormat } from 'util/formatting/date-format';
@ -20,6 +22,7 @@ const DefaultBackupSchedule = '1w';
class SettingsFileView extends View {
template = template;
yubiKeys = [];
events = {
'click .settings__file-button-save-default': 'saveDefault',
@ -52,7 +55,8 @@ class SettingsFileView extends View {
'change #settings__file-kdf': 'changeKdf',
'input #settings__file-key-rounds': 'changeKeyRounds',
'input #settings__file-key-change-force': 'changeKeyChangeForce',
'input .settings__input-kdf': 'changeKdfParameter'
'input .settings__input-kdf': 'changeKdfParameter',
'change #settings__file-yubikey': 'changeYubiKey'
};
constructor(model, options) {
@ -63,6 +67,8 @@ class SettingsFileView extends View {
setTimeout(() => this.render(), 0);
});
}
this.refreshYubiKeys();
}
render() {
@ -86,6 +92,39 @@ class SettingsFileView extends View {
});
storageProviders.sort((x, y) => (x.uipos || Infinity) - (y.uipos || Infinity));
const backup = this.model.backup;
const selectedYubiKey = this.model.chalResp
? `${this.model.chalResp.serial}:${this.model.chalResp.slot}`
: '';
const showYubiKeyBlock =
!!this.model.chalResp ||
(AppSettingsModel.enableUsb && AppSettingsModel.yubiKeyShowChalResp);
const yubiKeys = [];
if (showYubiKeyBlock) {
for (const yk of this.yubiKeys) {
for (const slot of [yk.slot1 ? 1 : 0, yk.slot2 ? 2 : 0].filter(s => s)) {
yubiKeys.push({
value: `${yk.serial}:${slot}`,
fullName: yk.fullName,
vid: yk.vid,
pid: yk.pid,
serial: yk.serial,
slot
});
}
}
if (selectedYubiKey && !yubiKeys.some(yk => yk.value === selectedYubiKey)) {
yubiKeys.push({
value: selectedYubiKey,
fullName: `YubiKey ${this.model.chalResp.serial}`,
vid: this.model.chalResp.vid,
pid: this.model.chalResp.pid,
serial: this.model.chalResp.serial,
slot: this.model.chalResp.slot
});
}
}
super.render({
cmd: Shortcuts.actionShortcutSymbol(true),
supportFiles: !!Launcher,
@ -115,7 +154,10 @@ class SettingsFileView extends View {
canBackup,
canSaveTo: AppSettingsModel.canSaveTo,
canExportXml: AppSettingsModel.canExportXml,
canExportHtml: AppSettingsModel.canExportHtml
canExportHtml: AppSettingsModel.canExportHtml,
showYubiKeyBlock,
selectedYubiKey,
yubiKeys
});
if (!this.model.created) {
this.$el.find('.settings__file-master-pass-warning').toggle(this.model.passwordChanged);
@ -690,6 +732,48 @@ class SettingsFileView extends View {
this.model.setKdfParameter(field, value);
}
}
refreshYubiKeys() {
if (!AppSettingsModel.enableUsb || !AppSettingsModel.yubiKeyShowChalResp) {
return;
}
if (!UsbListener.attachedYubiKeys) {
if (this.yubiKeys.length) {
this.yubiKeys = [];
this.render();
}
}
YubiKey.list((err, yubiKeys) => {
if (err) {
return;
}
this.yubiKeys = yubiKeys;
this.render();
});
}
changeYubiKey(e) {
let chalResp = null;
const value = e.target.value;
if (value) {
const option = e.target.selectedOptions[0];
const vid = +option.dataset.vid;
const pid = +option.dataset.pid;
const serial = +option.dataset.serial;
const slot = +option.dataset.slot;
chalResp = { vid, pid, serial, slot };
}
Alerts.yesno({
header: Locale.setFileYubiKeyHeader,
body: Locale.setFileYubiKeyBody,
success: () => {
this.model.setChallengeResponse(chalResp);
},
cancel: () => {
this.render();
}
});
}
}
export { SettingsFileView };

View File

@ -17,7 +17,7 @@
data-serial="{{yk.serial}}"
data-slot="{{yk.slot}}"
>
{{yk.fullName}}, {{res 'openChalRespSlot'}} {{yk.slot}}
{{yk.fullName}}, {{res 'yubiKeySlot'}} {{yk.slot}}
</div>
{{/each}}
</div>

View File

@ -76,10 +76,29 @@
</label>
<input type="password" class="settings__input input-base" id="settings__file-confirm-master-pass" autocomplete="confirm-password" />
</div>
<label for="settings__file-key-file">{{res 'setFileKeyFile'}}:</label>
<select class="settings__select settings__select-no-margin input-base" id="settings__file-key-file"></select>
<a id="settings__file-file-select-link">{{res 'setFileSelKeyFile'}}</a>
<input type="file" id="settings__file-file-select" class="hide-by-pos" />
<p>
<label for="settings__file-key-file">{{res 'setFileKeyFile'}}:</label>
<select class="settings__select settings__select-no-margin input-base" id="settings__file-key-file"></select>
<a id="settings__file-file-select-link">{{res 'setFileSelKeyFile'}}</a>
<input type="file" id="settings__file-file-select" class="hide-by-pos" />
</p>
<p>
<label for="settings__file-yubikey">{{res 'setFileYubiKey'}}:</label>
<select class="settings__select settings__select-no-margin input-base" id="settings__file-yubikey">
<option value="" {{#unless selectedYubiKey}}selected{{/unless}}>{{res 'setFileDontUseYubiKey'}}</option>
{{#each yubiKeys as |yk|}}
<option value="{{yk.value}}"
{{#ifeq ../selectedYubiKey yk.value}}selected{{/ifeq}}
data-vid="{{yk.vid}}"
data-pid="{{yk.pid}}"
data-serial="{{yk.serial}}"
data-slot="{{yk.slot}}"
>
{{yk.fullName}}, {{res 'yubiKeySlot'}} {{yk.slot}}
</option>
{{/each}}
</select>
</p>
<h2>{{res 'setFileNames'}}</h2>
<label for="settings__file-name">{{Res 'name'}}:</label>