mirror of https://github.com/keeweb/keeweb
loading yubikeys using a native module
parent
ef627c9530
commit
b90e84be42
|
@ -4,6 +4,7 @@ import { Logger } from 'util/logger';
|
|||
import { UsbListener } from 'comp/app/usb-listener';
|
||||
import { AppSettingsModel } from 'models/app-settings-model';
|
||||
import { Timeouts } from 'const/timeouts';
|
||||
import { YubiKeyProductIds } from 'const/hardware';
|
||||
import { Locale } from 'util/locale';
|
||||
|
||||
const logger = new Logger('yubikey');
|
||||
|
@ -13,6 +14,13 @@ const YubiKey = {
|
|||
process: null,
|
||||
aborted: false,
|
||||
|
||||
get ykChalResp() {
|
||||
if (!this._ykChalResp) {
|
||||
this._ykChalResp = Launcher.reqNative('yubikey-chalresp');
|
||||
}
|
||||
return this._ykChalResp;
|
||||
},
|
||||
|
||||
checkToolStatus() {
|
||||
if (this.ykmanStatus === 'ok') {
|
||||
return Promise.resolve(this.ykmanStatus);
|
||||
|
@ -48,10 +56,41 @@ const YubiKey = {
|
|||
},
|
||||
|
||||
list(callback) {
|
||||
this._list(callback, true);
|
||||
this.ykChalResp.getYubiKeys((err, yubiKeys) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
yubiKeys = yubiKeys.map(({ serial, pid, version, slot1, slot2 }) => {
|
||||
return {
|
||||
fullName: this.getKeyFullName(pid, version, serial),
|
||||
serial,
|
||||
slot1,
|
||||
slot2
|
||||
};
|
||||
});
|
||||
callback(null, yubiKeys);
|
||||
});
|
||||
},
|
||||
|
||||
_list(callback, canRetry) {
|
||||
getKeyFullName(pid, version, serial) {
|
||||
let name = 'YubiKey';
|
||||
if (YubiKeyProductIds.Gen1.includes(pid)) {
|
||||
name += ' Gen 1';
|
||||
} else if (YubiKeyProductIds.NEO.includes(pid)) {
|
||||
name += ' NEO';
|
||||
} else if (YubiKeyProductIds.YK4.includes(pid)) {
|
||||
if (version >= '5.1.0') {
|
||||
name += ' 5';
|
||||
}
|
||||
}
|
||||
return `${name} ${serial}`;
|
||||
},
|
||||
|
||||
listWithYkman(callback) {
|
||||
this._listWithYkman(callback, true);
|
||||
},
|
||||
|
||||
_listWithYkman(callback, canRetry) {
|
||||
if (this.process) {
|
||||
return callback('Already in progress');
|
||||
}
|
||||
|
@ -125,7 +164,7 @@ const YubiKey = {
|
|||
clearTimeout(openTimeout);
|
||||
this.aborted = false;
|
||||
setTimeout(() => {
|
||||
this._list(callback, false);
|
||||
this._listWithYkman(callback, false);
|
||||
}, Timeouts.ExternalDeviceAfterReconnect);
|
||||
}
|
||||
};
|
||||
|
@ -206,18 +245,14 @@ const YubiKey = {
|
|||
});
|
||||
},
|
||||
|
||||
calculateChalResp(serial, slot, challenge, callback) {
|
||||
return Launcher.spawn({
|
||||
cmd: 'ykman',
|
||||
args: ['-d', serial, 'otp', 'calculate', slot, challenge],
|
||||
noStdOutLogging: true,
|
||||
complete: (err, stdout) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
const otp = stdout.trim();
|
||||
callback(null, otp);
|
||||
calculateChalResp(serial, vid, pid, slot, challenge, callback) {
|
||||
const yubiKey = { serial, vid, pid };
|
||||
this.ykChalResp.challengeResponse(yubiKey, challenge, slot, (err, response) => {
|
||||
if (err) {
|
||||
// TODO: handle touch and missing YubiKeys
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, response);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
// https://support.yubico.com/support/solutions/articles/15000028104-yubikey-usb-id-values
|
||||
// https://github.com/Yubico/yubikey-personalization/blob/master/ykcore/ykdef.h#L274
|
||||
|
||||
const YubiKeyVendorId = 0x1050;
|
||||
|
||||
export { YubiKeyVendorId };
|
||||
const YubiKeyProductIds = {
|
||||
Gen1: [0x0010],
|
||||
NEO: [0x0110, 0x0112, 0x0113, 0x0114, 0x0115, 0x0116],
|
||||
YK4: [0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407]
|
||||
};
|
||||
|
||||
export { YubiKeyVendorId, YubiKeyProductIds };
|
||||
|
|
|
@ -236,7 +236,6 @@
|
|||
"openChalRespHeader": "Challenge-Response",
|
||||
"openChalRespLoading": "Loading the list of YubiKeys",
|
||||
"openChalRespSelectYubiKey": "Select a YubiKey that you would like to use",
|
||||
"openChalRespErrorNotInstalled": "YubiKey integration is not configured, please go to settings to set it up",
|
||||
"openChalRespErrorEmpty": "No YubiKeys found",
|
||||
"openChalRespSlot": "slot",
|
||||
|
||||
|
@ -595,8 +594,7 @@
|
|||
"setDevicesEnableUsb": "Enable interaction with USB devices",
|
||||
"setDevicesYubiKeyIntro": "It's recommended to read {} before using a YubiKey.",
|
||||
"setDevicesYubiKeyIntroLink": "this document",
|
||||
"setDevicesYubiKeyToolsTitle": "Tools",
|
||||
"setDevicesYubiKeyToolsDesc": "To be able to use YubiKey integration, you need to install a tool called {}.",
|
||||
"setDevicesYubiKeyToolsDesc": "To be able to use YubiKey in this mode, you need to install a tool called {}.",
|
||||
"setDevicesYubiKeyToolsDesc2": "{} to get more information about this tool.",
|
||||
"setDevicesYubiKeyToolsDescLink": "Click here",
|
||||
"setDevicesYubiKeyToolsStatusChecking": "Checking if {} is installed",
|
||||
|
|
|
@ -25,7 +25,7 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
|||
};
|
||||
|
||||
open(callback) {
|
||||
YubiKey.list((err, yubiKeys) => {
|
||||
YubiKey.listWithYkman((err, yubiKeys) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
|
|
@ -13,39 +13,27 @@ class OpenChalRespView extends View {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
YubiKey.checkToolStatus().then(status => {
|
||||
if (this.removed) {
|
||||
return;
|
||||
}
|
||||
if (status === 'ok') {
|
||||
YubiKey.list((err, yubiKeys) => {
|
||||
this.error = err;
|
||||
this.yubiKeys = [];
|
||||
if (yubiKeys) {
|
||||
for (const { fullName, serial } of yubiKeys) {
|
||||
for (const slot of [1, 2]) {
|
||||
this.yubiKeys.push({
|
||||
fullName,
|
||||
serial,
|
||||
slot
|
||||
});
|
||||
}
|
||||
}
|
||||
YubiKey.list((err, yubiKeys) => {
|
||||
this.error = err;
|
||||
this.yubiKeys = [];
|
||||
if (yubiKeys) {
|
||||
for (const { fullName, serial, slot1, slot2 } of yubiKeys) {
|
||||
for (const slot of [slot1 ? 1 : 0, slot2 ? 2 : 0].filter(s => s)) {
|
||||
this.yubiKeys.push({
|
||||
fullName,
|
||||
serial,
|
||||
slot
|
||||
});
|
||||
}
|
||||
this.render();
|
||||
});
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let error = this.error;
|
||||
|
||||
if (YubiKey.ykmanStatus === 'error') {
|
||||
error = Locale.openChalRespErrorNotInstalled.replace('{}', 'ykman');
|
||||
}
|
||||
if (this.yubiKeys && !this.yubiKeys.length) {
|
||||
error = Locale.openChalRespErrorEmpty;
|
||||
}
|
||||
|
@ -53,7 +41,7 @@ class OpenChalRespView extends View {
|
|||
super.render({
|
||||
error,
|
||||
yubiKeys: this.yubiKeys,
|
||||
loading: !YubiKey.ykmanStatus || YubiKey.ykmanStatus === 'checking'
|
||||
loading: !this.yubiKeys && !this.error
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
{{#if enableUsb}}
|
||||
<h2>YubiKey</h2>
|
||||
<p>{{#res 'setDevicesYubiKeyIntro'}}<a href="{{yubiKeyManualLink}}" target="_blank">{{res 'setDevicesYubiKeyIntroLink'}}</a>{{/res}}</p>
|
||||
<h3>{{res 'setDevicesYubiKeyToolsTitle'}}</h3>
|
||||
<h3>{{res 'setDevicesYubiKeyOtpTitle'}}</h3>
|
||||
<p>{{res 'setDevicesYubiKeyOtpDesc'}}</p>
|
||||
<p>
|
||||
{{#res 'setDevicesYubiKeyToolsDesc'}}<code>ykman</code>{{/res}}
|
||||
{{#res 'setDevicesYubiKeyToolsDesc2'}}<a href="{{ykmanInstallLink}}" target="_blank">{{res 'setDevicesYubiKeyToolsDescLink'}}</a>{{/res}}
|
||||
|
@ -19,8 +20,6 @@
|
|||
{{#ifeq ykmanStatus 'ok'}}{{#res 'setDevicesYubiKeyToolsStatusOk'}}<code>ykman</code>{{/res}}{{/ifeq}}
|
||||
{{#ifeq ykmanStatus 'error'}}{{#res 'setDevicesYubiKeyToolsStatusError'}}<code>ykman</code>{{/res}}{{/ifeq}}
|
||||
</p>
|
||||
<h3>{{res 'setDevicesYubiKeyOtpTitle'}}</h3>
|
||||
<p>{{res 'setDevicesYubiKeyOtpDesc'}}</p>
|
||||
<div>
|
||||
<input type="checkbox" class="settings__input input-base settings__yubikey-show-icon" id="settings__yubikey-show-icon"
|
||||
{{#if yubiKeyShowIcon}}checked{{/if}} />
|
||||
|
|
Loading…
Reference in New Issue