mirror of https://github.com/keeweb/keeweb.git
176 lines
5.7 KiB
JavaScript
176 lines
5.7 KiB
JavaScript
import { Events } from 'framework/events';
|
|
import { Logger } from 'util/logger';
|
|
import { YubiKey } from 'comp/app/yubikey';
|
|
import { UsbListener } from 'comp/app/usb-listener';
|
|
import { Alerts } from 'comp/ui/alerts';
|
|
import { Locale } from 'util/locale';
|
|
import { Timeouts } from 'const/timeouts';
|
|
import { Launcher } from 'comp/launcher';
|
|
|
|
const logger = new Logger('chal-resp');
|
|
|
|
const ChalRespCalculator = {
|
|
cache: {},
|
|
|
|
getCacheKey(params) {
|
|
return `${params.vid}:${params.pid}:${params.serial}:${params.slot}`;
|
|
},
|
|
|
|
build(params) {
|
|
if (!params) {
|
|
return null;
|
|
}
|
|
return (challenge) => {
|
|
return new Promise((resolve, reject) => {
|
|
challenge = Buffer.from(challenge);
|
|
const hexChallenge = challenge.toString('hex');
|
|
|
|
const cacheKey = this.getCacheKey(params);
|
|
const respFromCache = this.cache[cacheKey]?.[hexChallenge];
|
|
if (respFromCache) {
|
|
logger.debug('Found ChalResp in cache');
|
|
return resolve(Buffer.from(respFromCache, 'hex'));
|
|
}
|
|
|
|
logger.debug('Calculating ChalResp using a YubiKey', params);
|
|
|
|
this._calc(params, challenge, (err, response) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
resolve(response);
|
|
});
|
|
});
|
|
};
|
|
},
|
|
|
|
_calc(params, challenge, callback) {
|
|
let touchAlert = null;
|
|
let userCanceled = false;
|
|
|
|
YubiKey.calculateChalResp(params, challenge, (err, response) => {
|
|
if (userCanceled) {
|
|
userCanceled = false;
|
|
return;
|
|
}
|
|
if (touchAlert) {
|
|
touchAlert.closeWithoutResult();
|
|
touchAlert = null;
|
|
}
|
|
if (err) {
|
|
if (err.noKey) {
|
|
logger.info('YubiKey ChalResp: no key');
|
|
this._showNoKeyAlert(params.serial, (err) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
this._calc(params, challenge, callback);
|
|
});
|
|
return;
|
|
} else if (err.touchRequested) {
|
|
logger.info('YubiKey ChalResp: touch requested');
|
|
touchAlert = this._showTouchAlert(params.serial, (err) => {
|
|
touchAlert = null;
|
|
userCanceled = true;
|
|
|
|
logger.info('YubiKey ChalResp canceled');
|
|
YubiKey.cancelChalResp();
|
|
|
|
callback(err);
|
|
});
|
|
return;
|
|
} else {
|
|
logger.error('YubiKey ChalResp error', err);
|
|
}
|
|
return callback(err);
|
|
}
|
|
|
|
const cacheKey = this.getCacheKey(params);
|
|
if (!this.cache[cacheKey]) {
|
|
this.cache[cacheKey] = {};
|
|
}
|
|
|
|
const hexChallenge = challenge.toString('hex');
|
|
this.cache[cacheKey][hexChallenge] = response.toString('hex');
|
|
|
|
logger.info('Calculated YubiKey ChalResp');
|
|
callback(null, response);
|
|
});
|
|
},
|
|
|
|
_showNoKeyAlert(serial, callback) {
|
|
Launcher.showMainWindow();
|
|
|
|
let noKeyAlert = null;
|
|
let deviceEnumerationTimer;
|
|
|
|
const onUsbDevicesChanged = () => {
|
|
if (UsbListener.attachedYubiKeys.length === 0) {
|
|
return;
|
|
}
|
|
deviceEnumerationTimer = setTimeout(() => {
|
|
YubiKey.list((err, list) => {
|
|
if (err) {
|
|
logger.error('YubiKey list error', err);
|
|
return;
|
|
}
|
|
const isAttached = list.some((yk) => yk.serial === serial);
|
|
logger.info(isAttached ? 'YubiKey found' : 'YubiKey not found');
|
|
if (isAttached) {
|
|
Events.off('usb-devices-changed', onUsbDevicesChanged);
|
|
if (noKeyAlert) {
|
|
noKeyAlert.closeWithoutResult();
|
|
}
|
|
callback();
|
|
}
|
|
});
|
|
}, Timeouts.ExternalDeviceAfterReconnect);
|
|
};
|
|
|
|
Events.on('usb-devices-changed', onUsbDevicesChanged);
|
|
|
|
noKeyAlert = Alerts.alert({
|
|
header: Locale.yubiKeyNoKeyHeader,
|
|
body: Locale.yubiKeyNoKeyBody.replace('{}', serial),
|
|
buttons: [Alerts.buttons.cancel],
|
|
iconSvg: 'usb-token',
|
|
cancel: () => {
|
|
logger.info('No key alert closed');
|
|
|
|
clearTimeout(deviceEnumerationTimer);
|
|
Events.off('usb-devices-changed', onUsbDevicesChanged);
|
|
|
|
const err = new Error('User canceled the YubiKey no key prompt');
|
|
err.userCanceled = true;
|
|
|
|
return callback(err);
|
|
}
|
|
});
|
|
},
|
|
|
|
_showTouchAlert(serial, callback) {
|
|
Launcher.showMainWindow();
|
|
|
|
return Alerts.alert({
|
|
header: Locale.yubiKeyTouchRequestedHeader,
|
|
body: Locale.yubiKeyTouchRequestedBody.replace('{}', serial),
|
|
buttons: [Alerts.buttons.cancel],
|
|
iconSvg: 'usb-token',
|
|
cancel: () => {
|
|
logger.info('Touch alert closed');
|
|
|
|
const err = new Error('User canceled the YubiKey touch prompt');
|
|
err.userCanceled = true;
|
|
|
|
return callback(err);
|
|
}
|
|
});
|
|
},
|
|
|
|
clearCache(params) {
|
|
delete this.cache[this.getCacheKey(params)];
|
|
}
|
|
};
|
|
|
|
export { ChalRespCalculator };
|