@ledgerhq/hw-transport-u2f
Version:
Ledger Hardware Wallet Web implementation of the communication layer, using U2F api
187 lines (145 loc) • 4.74 kB
JavaScript
;
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