UNPKG

4.4 kBJavaScriptView Raw
1import { sign, isSupported } from "u2f-api";
2import Transport from "@ledgerhq/hw-transport";
3import { log } from "@ledgerhq/logs";
4import { TransportError } from "@ledgerhq/errors";
5
6function wrapU2FTransportError(originalError, message, id) {
7 const err = new TransportError(message, id); // $FlowFixMe
8
9 err.originalError = originalError;
10 return err;
11}
12
13function wrapApdu(apdu, key) {
14 const result = Buffer.alloc(apdu.length);
15
16 for (let i = 0; i < apdu.length; i++) {
17 result[i] = apdu[i] ^ key[i % key.length];
18 }
19
20 return result;
21} // Convert from normal to web-safe, strip trailing "="s
22
23
24const webSafe64 = base64 => base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); // Convert from web-safe to normal, add trailing "="s
25
26
27const normal64 = base64 => base64.replace(/-/g, "+").replace(/_/g, "/") + "==".substring(0, 3 * base64.length % 4);
28
29function attemptExchange(apdu, timeoutMillis, scrambleKey, unwrap) {
30 const keyHandle = wrapApdu(apdu, scrambleKey);
31 const challenge = Buffer.from("0000000000000000000000000000000000000000000000000000000000000000", "hex");
32 const signRequest = {
33 version: "U2F_V2",
34 keyHandle: webSafe64(keyHandle.toString("base64")),
35 challenge: webSafe64(challenge.toString("base64")),
36 appId: location.origin
37 };
38 log("apdu", "=> " + apdu.toString("hex"));
39 return sign(signRequest, timeoutMillis / 1000).then(response => {
40 const {
41 signatureData
42 } = response;
43
44 if (typeof signatureData === "string") {
45 const data = Buffer.from(normal64(signatureData), "base64");
46 let result;
47
48 if (!unwrap) {
49 result = data;
50 } else {
51 result = data.slice(5);
52 }
53
54 log("apdu", "<= " + result.toString("hex"));
55 return result;
56 } else {
57 throw response;
58 }
59 });
60}
61
62let transportInstances = [];
63
64function emitDisconnect() {
65 transportInstances.forEach(t => t.emit("disconnect"));
66 transportInstances = [];
67}
68
69function isTimeoutU2FError(u2fError) {
70 return u2fError.metaData.code === 5;
71}
72/**
73 * U2F web Transport implementation
74 * @example
75 * import TransportU2F from "@ledgerhq/hw-transport-u2f";
76 * ...
77 * TransportU2F.create().then(transport => ...)
78 */
79
80
81export default class TransportU2F extends Transport {
82 /*
83 */
84
85 /*
86 */
87
88 /**
89 * static function to create a new Transport from a connected Ledger device discoverable via U2F (browser support)
90 */
91 static async open(_, _openTimeout = 5000) {
92 return new TransportU2F();
93 }
94
95 constructor() {
96 super();
97 this.scrambleKey = void 0;
98 this.unwrap = true;
99 transportInstances.push(this);
100 }
101 /**
102 * Exchange with the device using APDU protocol.
103 * @param apdu
104 * @returns a promise of apdu response
105 */
106
107
108 async exchange(apdu) {
109 try {
110 return await attemptExchange(apdu, this.exchangeTimeout, this.scrambleKey, this.unwrap);
111 } catch (e) {
112 const isU2FError = typeof e.metaData === "object";
113
114 if (isU2FError) {
115 if (isTimeoutU2FError(e)) {
116 emitDisconnect();
117 } // the wrapping make error more usable and "printable" to the end user.
118
119
120 throw wrapU2FTransportError(e, "Failed to sign with Ledger device: U2F " + e.metaData.type, "U2F_" + e.metaData.code);
121 } else {
122 throw e;
123 }
124 }
125 }
126 /**
127 */
128
129
130 setScrambleKey(scrambleKey) {
131 this.scrambleKey = Buffer.from(scrambleKey, "ascii");
132 }
133 /**
134 */
135
136
137 setUnwrap(unwrap) {
138 this.unwrap = unwrap;
139 }
140
141 close() {
142 // u2f have no way to clean things up
143 return Promise.resolve();
144 }
145
146}
147TransportU2F.isSupported = isSupported;
148
149TransportU2F.list = () => // this transport is not discoverable but we are going to guess if it is here with isSupported()
150isSupported().then(supported => supported ? [null] : []);
151
152TransportU2F.listen = observer => {
153 let unsubscribed = false;
154 isSupported().then(supported => {
155 if (unsubscribed) return;
156
157 if (supported) {
158 observer.next({
159 type: "add",
160 descriptor: null
161 });
162 observer.complete();
163 } else {
164 observer.error(new 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"));
165 }
166 });
167 return {
168 unsubscribe: () => {
169 unsubscribed = true;
170 }
171 };
172};
173//# sourceMappingURL=TransportU2F.js.map
\No newline at end of file