mirror of https://github.com/keeweb/keeweb
yubikey enrollment
parent
b520c58ef4
commit
143f7c6a7a
|
@ -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: () => {
|
||||
|
|
|
@ -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 {}"
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue