UNPKG

@ledgerhq/hw-transport-u2f

Version:

Ledger Hardware Wallet Web implementation of the communication layer, using U2F api

187 lines (145 loc) 4.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _u2fApi = require("u2f-api"); var _hwTransport = _interopRequireDefault(require("@ledgerhq/hw-transport")); var _logs = require("@ledgerhq/logs"); var _errors = require("@ledgerhq/errors"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function wrapU2FTransportError(originalError, message, id) { const err = new _errors.TransportError(message, id); // $FlowFixMe err.originalError = originalError; return err; } 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; } // Convert from normal to web-safe, strip trailing "="s const webSafe64 = base64 => base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); // Convert from web-safe to normal, add trailing "="s const normal64 = base64 => base64.replace(/-/g, "+").replace(/_/g, "/") + "==".substring(0, 3 * base64.length % 4); function attemptExchange(apdu, timeoutMillis, scrambleKey, unwrap) { const keyHandle = wrapApdu(apdu, scrambleKey); const challenge = Buffer.from("0000000000000000000000000000000000000000000000000000000000000000", "hex"); const signRequest = { version: "U2F_V2", keyHandle: webSafe64(keyHandle.toString("base64")), challenge: webSafe64(challenge.toString("base64")), appId: location.origin }; (0, _logs.log)("apdu", "=> " + apdu.toString("hex")); return (0, _u2fApi.sign)(signRequest, timeoutMillis / 1000).then(response => { const { signatureData } = response; if (typeof signatureData === "string") { const data = Buffer.from(normal64(signatureData), "base64"); let result; if (!unwrap) { result = data; } else { result = data.slice(5); } (0, _logs.log)("apdu", "<= " + result.toString("hex")); return result; } else { throw response; } }); } let transportInstances = []; function emitDisconnect() { transportInstances.forEach(t => t.emit("disconnect")); transportInstances = []; } function isTimeoutU2FError(u2fError) { return u2fError.metaData.code === 5; } /** * U2F web Transport implementation * @example * import TransportU2F from "@ledgerhq/hw-transport-u2f"; * ... * TransportU2F.create().then(transport => ...) */ class TransportU2F extends _hwTransport.default { /* */ /* */ /** * static function to create a new Transport from a connected Ledger device discoverable via U2F (browser support) */ static async open(_, _openTimeout = 5000) { return new TransportU2F(); } constructor() { super(); this.scrambleKey = void 0; this.unwrap = true; transportInstances.push(this); } /** * Exchange with the device using APDU protocol. * @param apdu * @returns a promise of apdu response */ async exchange(apdu) { try { return await attemptExchange(apdu, this.exchangeTimeout, this.scrambleKey, this.unwrap); } catch (e) { const isU2FError = typeof e.metaData === "object"; if (isU2FError) { if (isTimeoutU2FError(e)) { emitDisconnect(); } // the wrapping make error more usable and "printable" to the end user. throw wrapU2FTransportError(e, "Failed to sign with Ledger device: U2F " + e.metaData.type, "U2F_" + e.metaData.code); } else { throw e; } } } /** */ setScrambleKey(scrambleKey) { this.scrambleKey = Buffer.from(scrambleKey, "ascii"); } /** */ setUnwrap(unwrap) { this.unwrap = unwrap; } close() { // u2f have no way to clean things up return Promise.resolve(); } } exports.default = TransportU2F; TransportU2F.isSupported = _u2fApi.isSupported; TransportU2F.list = () => // this transport is not discoverable but we are going to guess if it is here with isSupported() (0, _u2fApi.isSupported)().then(supported => supported ? [null] : []); TransportU2F.listen = observer => { let unsubscribed = false; (0, _u2fApi.isSupported)().then(supported => { if (unsubscribed) return; if (supported) { observer.next({ type: "add", descriptor: null }); observer.complete(); } else { observer.error(new _errors.TransportError("U2F browser support is needed for Ledger. " + "Please use Chrome, Opera or Firefox with a U2F extension. " + "Also make sure you're on an HTTPS connection", "U2FNotSupported")); } }); return { unsubscribe: () => { unsubscribed = true; } }; }; //# sourceMappingURL=TransportU2F.js.map