Added a yubikey workaround

This commit is contained in:
antelle 2020-05-06 18:37:37 +02:00
parent 235ceae228
commit 5a535bb07a
No known key found for this signature in database
GPG Key ID: 094A2F2D6136A4EE
7 changed files with 57 additions and 11 deletions

View File

@ -41,6 +41,7 @@ const DefaultAppSettings = {
yubiKeyAutoOpen: true, // auto-load one-time codes when there are open files
yubiKeyMatchEntries: true, // show matching one-time codes in entries
yubiKeyShowChalResp: true, // show YubiKey challenge-response option
yubiKeyOathWorkaround: false, // enable the workaround for YubiKey OATH issues
canOpen: true, // can select and open new files
canOpenDemo: true, // can open a demo file

View File

@ -11,7 +11,8 @@ const Timeouts = {
PopupWaitTime: 1000,
AutoUpdatePluginsAfterStart: 500,
LinkDownloadRevoke: 10 * 1000 * 60,
DefaultHttpRequest: 60000
DefaultHttpRequest: 60000,
ExternalDeviceReconnect: 3000
};
export { Timeouts };

View File

@ -522,8 +522,8 @@
"setFileCloseNoSave": "Close and lose changes",
"setFileDontClose": "Don't close",
"setFileFormatVersion": "File format",
"saveFileExportRaw": "Exporting your passwords",
"saveFileExportRawBody": "The exported file will contain your passwords, they will not be encrypted there. Would you like to proceed?",
"setFileExportRaw": "Exporting your passwords",
"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",
@ -603,6 +603,7 @@
"setDevicesYubiKeyChalRespTitle": "Challenge-Response",
"setDevicesYubiKeyChalRespDesc": "It's also possible to use a YubiKey in challenge-response mode, so that a piece of private key used to encrypt files resides on a YubiKey.",
"setDevicesYubiKeyChalRespShow": "Show an option to use a YubiKey when opening files",
"setDevicesYubiKeyOathWorkaround": "Reconnect the YubiKey if it hangs when loading one-time codes",
"setAboutTitle": "About",
"setAboutBuilt": "This app is built with these awesome tools",

View File

@ -2,10 +2,15 @@ import { Events } from 'framework/events';
import { ExternalOtpDeviceModel } from 'models/external/external-otp-device-model';
import { ExternalOtpEntryModel } from 'models/external/external-otp-entry-model';
import { Launcher } from 'comp/launcher';
import { Logger } from 'util/logger';
import { UsbListener } from 'comp/app/usb-listener';
import { AppSettingsModel } from 'models/app-settings-model';
import { Timeouts } from 'const/timeouts';
let ykmanStatus;
const logger = new Logger('yubikey');
class YubiKeyOtpModel extends ExternalOtpDeviceModel {
constructor(props) {
super({
@ -32,27 +37,51 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
}
_open(callback, canRetry) {
logger.info('Opening');
this.openProcess = Launcher.spawn({
cmd: 'ykman',
args: ['oath', 'code'],
noStdOutLogging: true,
complete: (err, stdout, code, stderr) => {
logger.info('Open complete with code', code);
this.openProcess = null;
if (this.openAborted) {
return callback('Open aborted');
}
const isStuck =
code === 2 && stderr && stderr.includes('Make sure the application');
if (isStuck && canRetry) {
this.openProcess = Launcher.spawn({
if (isStuck) {
logger.info('The YubiKey is probably stuck');
}
if (isStuck && canRetry && AppSettingsModel.yubiKeyOathWorkaround) {
logger.info('Repairing a stuck YubiKey');
let openTimeout;
const countYubiKeys = UsbListener.attachedYubiKeys.length;
const onDevicesChangedDuringRepair = () => {
if (UsbListener.attachedYubiKeys.length === countYubiKeys) {
logger.info('YubiKey was reconnected');
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
clearTimeout(openTimeout);
this.openAborted = false;
this._open(callback, false);
}
};
Events.on('usb-devices-changed', onDevicesChangedDuringRepair);
Launcher.spawn({
cmd: 'ykman',
args: ['config', 'usb', '-e', 'oath', '-f'],
noStdOutLogging: true,
complete: err => {
logger.info('Repair complete', err ? 'with error' : 'OK');
if (err) {
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
return callback(err);
}
this._open(callback, false);
openTimeout = setTimeout(() => {
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
}, Timeouts.ExternalDeviceReconnect);
}
});
return;
@ -87,10 +116,12 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
}
cancelOpen() {
logger.info('Cancel open');
Events.off('usb-devices-changed', this.onUsbDevicesChanged);
this.openAborted = true;
if (this.openProcess) {
this.openProcess.kill();
logger.info('Killed the process');
}
}

View File

@ -13,7 +13,8 @@ class SettingsDevicesView extends View {
'change .settings__yubikey-show-icon': 'changeYubiKeyShowIcon',
'change .settings__yubikey-auto-open': 'changeYubiKeyAutoOpen',
'change .settings__yubikey-match-entries': 'changeYubiKeyMatchEntries',
'change .settings__yubikey-chalresp-show': 'changeYubiKeyShowChalResp'
'change .settings__yubikey-chalresp-show': 'changeYubiKeyShowChalResp',
'change .settings__yubikey-oath-workaround': 'changeYubiKeyOathWorkaround'
};
constructor(...args) {
@ -36,6 +37,7 @@ class SettingsDevicesView extends View {
yubiKeyAutoOpen: AppSettingsModel.yubiKeyAutoOpen,
yubiKeyMatchEntries: AppSettingsModel.yubiKeyMatchEntries,
yubiKeyShowChalResp: AppSettingsModel.yubiKeyShowChalResp,
yubiKeyOathWorkaround: AppSettingsModel.yubiKeyOathWorkaround,
yubiKeyManualLink: Links.YubiKeyManual,
ykmanInstallLink: Links.YubiKeyManagerInstall
});
@ -65,6 +67,11 @@ class SettingsDevicesView extends View {
AppSettingsModel.yubiKeyShowChalResp = e.target.checked;
this.render();
}
changeYubiKeyOathWorkaround(e) {
AppSettingsModel.yubiKeyOathWorkaround = e.target.checked;
this.render();
}
}
export { SettingsDevicesView };

View File

@ -257,8 +257,8 @@ class SettingsFileView extends View {
saveToXml() {
Alerts.yesno({
header: Locale.saveFileExportRaw,
body: Locale.saveFileExportRawBody,
header: Locale.setFileExportRaw,
body: Locale.setFileExportRawBody,
success: () => {
this.model.getXml(xml => {
const blob = new Blob([xml], { type: 'text/xml' });
@ -270,8 +270,8 @@ class SettingsFileView extends View {
saveToHtml() {
Alerts.yesno({
header: Locale.saveFileExportRaw,
body: Locale.saveFileExportRawBody,
header: Locale.setFileExportRaw,
body: Locale.setFileExportRawBody,
success: () => {
this.model.getHtml(html => {
const blob = new Blob([html], { type: 'text/html' });

View File

@ -36,6 +36,11 @@
{{#if yubiKeyMatchEntries}}checked{{/if}} />
<label for="settings__yubikey-match-entries">{{res 'setDevicesYubiKeyOtpMatchEntries'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__yubikey-oath-workaround" id="settings__yubikey-oath-workaround"
{{#if yubiKeyOathWorkaround}}checked{{/if}} />
<label for="settings__yubikey-oath-workaround">{{res 'setDevicesYubiKeyOathWorkaround'}}</label>
</div>
<h3>{{res 'setDevicesYubiKeyChalRespTitle'}}</h3>
<p>{{res 'setDevicesYubiKeyChalRespDesc'}}</p>
<div>