loading yubikeys using a native module

pull/1541/head
antelle 2020-05-29 23:15:10 +02:00
parent ef627c9530
commit b90e84be42
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
6 changed files with 76 additions and 48 deletions

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
};
open(callback) {
YubiKey.list((err, yubiKeys) => {
YubiKey.listWithYkman((err, yubiKeys) => {
if (err) {
return callback(err);
}

View File

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

View File

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