Skip to content

Commit

Permalink
Pull Webauthn support from the main repo.
Browse files Browse the repository at this point in the history
  • Loading branch information
ARyaskov committed May 30, 2019
1 parent 4516364 commit c45b8a7
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 0 deletions.
4 changes: 4 additions & 0 deletions lib/bledger-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@
'use strict';

const LedgerError = require('./protocol/error');
const DeviceError = require('./devices/error');
const LedgerBcoin = require('./bcoin');
const LedgerTXInput = require('./txinput');
const U2F = require('./devices/u2f');
const WebUSB = require('./devices/webusb');
const WebAuthn = require('./device/webauthn');

exports.bledger = exports;

exports.U2F = U2F;
exports.WebUSB = WebUSB;
exports.WebAuthn = WebAuthn;
exports.LedgerError = LedgerError;
exports.DeviceError = DeviceError;

exports.LedgerBcoin = LedgerBcoin;
exports.LedgerTXInput = LedgerTXInput;
34 changes: 34 additions & 0 deletions lib/devices/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*!
* error.js - Device errors.
* Copyright (c) 2018-2019, The Bcoin Developers (MIT License).
* https://github.com/bcoin-org/bcoin
*/

'use strict';

/**
* Device error
* @alias module:device.DeviceError
* @extends {Error}
* @property {String} message - error message.
*/

class DeviceError extends Error {
/**
* Create device error.
* @param {String} reason
* @param {Function} device
*/

constructor(reason, device) {
super();

this.type = 'DeviceError';
this.message = reason;

if (Error.captureStackTrace)
Error.captureStackTrace(this, device || DeviceError);
}
}

module.exports = DeviceError;
161 changes: 161 additions & 0 deletions lib/devices/webauthn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*!
* webauthn.js - Ledger WebAuthn communication
* Copyright (c) 2018-2019, The Bcoin Developers (MIT License).
* https://github.com/bcoin-org/bcoin
*/
/* eslint-env browser */
'use strict';

const assert = require('assert');

const {Device} = require('./device');
const DeviceError = require('./error');
const {Lock} = require('bmutex');

/**
* @alias module:device.WebAuthnDevice
* @extends {device.Device}
*/

class WebAuthnDevice extends Device {
constructor(options) {
super(options);

this.lock = new Lock(false);
this.opened = false;

this.type = 'webauthn';
}

/**
* Open device.
* @throws {DeviceError}
*/

async open() {
this.enforce(this.opened === false, 'Device is already open.');
await WebAuthnDevice.ensureSupport();

this.opened = true;
}

/**
* Close device.
*/

async close() {
this.enforce(this.opened === true, 'Device is already closed.');
this.opened = false;
}

/**
* Exchange APDU commands with device.
* Lock
* @param {Buffer} apdu
* @returns {Promise<Buffer>}
* @throws {LedgerError}
*/

async exchange(apdu) {
const unlock = await this.lock.lock();

try {
return await this._exchange(apdu);
} finally {
unlock();
}
}

/**
* Exchange APDU commands with device.
* without lock.
* @param {Buffer} apdu
* @returns {Promise<Buffer>}
* @throws {LedgerError}
*/

async _exchange(apdu) {
this.enforce(this.opened === true, 'Device is not open.');
assert(Buffer.isBuffer(apdu), 'apdu is not a buffer.');

const requestOptions = {
publicKey: {
timeout: this.timeout,
challenge: Buffer.alloc(32),
allowCredentials: [{
type: 'public-key',
id: wrapAPDU(apdu, this.scrambleKey)
}]
}
};

const credential = await navigator.credentials.get(requestOptions);

return Buffer.from(credential.response.signature);
}

/**
* Assert device.
* @param {Boolean} value
* @param {String?} reason
* @throws {DeviceError}
*/

enforce(value, reason) {
if (!value)
throw new DeviceError(reason, WebAuthnDevice);
}

/**
* Check if WebAuthn is supported.
* @returns {Boolean}
*/

static async isSupported() {
return global.navigator && global.navigator.credentials;
}

/**
* Ensure WebAuthn support.
* @returns {Boolean}
* @throws {DeviceError}
*/

static async ensureSupport() {
if (!this.isSupported())
throw new DeviceError('WebAuthn is not supported.', WebAuthnDevice);
}

/**
* Get WebAuthn devices.
*/

static async getDevices() {
await this.ensureSupport();

return [new WebAuthnDevice()];
}

static async requestDevice() {
return new WebAuthnDevice();
}
}

/**
* Wrap APDU
* @ignore
* @param {Buffer} apdu
* @param {Buffer} key
* @returns {Buffer}
*/

function wrapAPDU(apdu, key) {
const result = Buffer.alloc(apdu.length);

for (let i = 0; i < apdu.length; i++)
result[i] = apdu[i] ^ key[i % key.length];

return result;
}

exports.Device = WebAuthnDevice;

0 comments on commit c45b8a7

Please sign in to comment.