mirror of
https://github.com/arx-research/libhalo.git
synced 2026-01-08 20:58:02 -05:00
Migrate to TypeScript (#345)
This commit is contained in:
committed by
GitHub
parent
ee5ac7be0f
commit
6514bca727
1
.github/workflows/check_lib.yml
vendored
1
.github/workflows/check_lib.yml
vendored
@@ -32,6 +32,7 @@ jobs:
|
||||
run: |
|
||||
cd core
|
||||
./node_modules/.bin/tsc
|
||||
./node_modules/.bin/tsc -p tsconfig.commonjs.json
|
||||
- name: Run webpack (root)
|
||||
run: |
|
||||
cd core
|
||||
|
||||
1
.github/workflows/prod_build_lib.yml
vendored
1
.github/workflows/prod_build_lib.yml
vendored
@@ -148,6 +148,7 @@ jobs:
|
||||
cd core
|
||||
yarn install --frozen-lockfile --production=false
|
||||
./node_modules/.bin/tsc
|
||||
./node_modules/.bin/tsc -p tsconfig.commonjs.json
|
||||
- name: Publish package to npmjs
|
||||
run: yarn publish
|
||||
env:
|
||||
|
||||
14
cli/eslint.config.js
Normal file
14
cli/eslint.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -44,6 +44,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@arx-research/libhalo": "../core",
|
||||
"argparse": "^2.0.1",
|
||||
"bufferutil": "^4.0.8",
|
||||
"express": "^4.19.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
@@ -53,10 +54,18 @@
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.7.0",
|
||||
"@types/argparse": "^2.0.16",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/nunjucks": "^3.2.6",
|
||||
"@types/ws": "^8.5.11",
|
||||
"@yao-pkg/pkg": "^5.12.0",
|
||||
"eslint": "^9.7.0",
|
||||
"resedit": "^2.0.2",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^7.16.0",
|
||||
"webpack": "^5.92.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
import {Action, ArgumentError} from "argparse";
|
||||
import {Action, ActionConstructorOptions, ArgumentError, ArgumentParser, Namespace} from "argparse";
|
||||
|
||||
class JSONParseAction extends Action {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
constructor(options: ActionConstructorOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
call(parser, namespace, values, option_string) {
|
||||
call(parser: ArgumentParser, namespace: Namespace, values: string, option_string: string | null) {
|
||||
try {
|
||||
namespace[this.dest] = JSON.parse(values);
|
||||
} catch (e) {
|
||||
throw ArgumentError(this, e.message);
|
||||
throw new ArgumentError(this, (<Error> e).message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ subparsers.add_parser("version", {help: "Get tag version."});
|
||||
subparsers.add_parser("cli_version", {help: "Get halocli build version."});
|
||||
|
||||
if (process.env.__UNSAFE_ENABLE_TESTS === "1") {
|
||||
let testParser = subparsers.add_parser("test", {help: "Run test suite against the tag. Please do not use this command."});
|
||||
const testParser = subparsers.add_parser("test", {help: "Run test suite against the tag. Please do not use this command."});
|
||||
testParser.add_argument("--unsafe", {
|
||||
help: "I understand that this command might reconfigure the tag in undesired way and that it might be " +
|
||||
"not possible to rollback certain changes made by this command.",
|
||||
@@ -33,7 +33,7 @@ if (process.env.__UNSAFE_ENABLE_TESTS === "1") {
|
||||
|
||||
subparsers.add_parser("read_ndef", {help: "Read dynamic URL on the tag."});
|
||||
|
||||
let signParser = subparsers.add_parser("sign", {help: "Sign message using ECDSA/Keccak algorithm."});
|
||||
const signParser = subparsers.add_parser("sign", {help: "Sign message using ECDSA/Keccak algorithm."});
|
||||
signParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
help: "Number of the key slot to use.",
|
||||
@@ -62,7 +62,7 @@ signParser.add_argument("--legacy-sign-command", {
|
||||
"default": false
|
||||
});
|
||||
|
||||
let signRandomParser = subparsers.add_parser("sign_random", {help: "Sign random digest using key slot #2."});
|
||||
const signRandomParser = subparsers.add_parser("sign_random", {help: "Sign random digest using key slot #2."});
|
||||
signRandomParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
help: "Number of the key slot to use.",
|
||||
@@ -70,7 +70,7 @@ signRandomParser.add_argument("-k", "--key-no", {
|
||||
'default': 2
|
||||
});
|
||||
|
||||
let signChallenge = subparsers.add_parser("sign_challenge", {help: "Sign challenge using ECDSA/Keccak algorithm."});
|
||||
const signChallenge = subparsers.add_parser("sign_challenge", {help: "Sign challenge using ECDSA/Keccak algorithm."});
|
||||
signChallenge.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
help: "Number of the key slot to use.",
|
||||
@@ -79,7 +79,7 @@ signChallenge.add_argument("-k", "--key-no", {
|
||||
});
|
||||
signChallenge.add_argument("-c", "--challenge", {help: "Challenge to be signed (32 bytes hex)."});
|
||||
|
||||
let writeLatchParser = subparsers.add_parser("write_latch", {help: "Write value into the latch slot."});
|
||||
const writeLatchParser = subparsers.add_parser("write_latch", {help: "Write value into the latch slot."});
|
||||
writeLatchParser.add_argument("-n", "--latch-no", {
|
||||
dest: "latchNo",
|
||||
help: "Number of the latch slot to use.",
|
||||
@@ -91,7 +91,7 @@ writeLatchParser.add_argument("-d", "--data", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let setNDEFCfgParser = subparsers.add_parser("cfg_ndef", {help: "Configure the tag's NDEF data."});
|
||||
const setNDEFCfgParser = subparsers.add_parser("cfg_ndef", {help: "Configure the tag's NDEF data."});
|
||||
setNDEFCfgParser.add_argument("--flag-use-text", {
|
||||
dest: "flagUseText",
|
||||
help: "Use text NDEF record instead of the URL record.",
|
||||
@@ -148,7 +148,7 @@ setNDEFCfgParser.add_argument("--flag-show-latch2-sig", {
|
||||
});
|
||||
setNDEFCfgParser.add_argument("--flag-hide-rndsig", {
|
||||
dest: "flagHideRNDSIG",
|
||||
help: "Hide \"rnd\" and \"rndsig\" fields. The counter\'s signature will be generated only upon manual request.",
|
||||
help: "Hide \"rnd\" and \"rndsig\" fields. The counter's signature will be generated only upon manual request.",
|
||||
action: 'store_true',
|
||||
required: false
|
||||
});
|
||||
@@ -189,7 +189,7 @@ setNDEFCfgParser.add_argument("--pkn", {
|
||||
required: false
|
||||
});
|
||||
|
||||
let genKeyParser = subparsers.add_parser("gen_key", {help: "Perform the first step of the key generation procedure."});
|
||||
const genKeyParser = subparsers.add_parser("gen_key", {help: "Perform the first step of the key generation procedure."});
|
||||
genKeyParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -202,7 +202,7 @@ genKeyParser.add_argument("--entropy", {
|
||||
required: false
|
||||
});
|
||||
|
||||
let genKeyConfirmParser = subparsers.add_parser("gen_key_confirm", {help: "Confirm the public key generated by gen_key command."});
|
||||
const genKeyConfirmParser = subparsers.add_parser("gen_key_confirm", {help: "Confirm the public key generated by gen_key command."});
|
||||
genKeyConfirmParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -215,7 +215,7 @@ genKeyConfirmParser.add_argument("--public-key", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let genKeyFinalizeParser = subparsers.add_parser("gen_key_finalize", {help: "Finalize key generation procedure."});
|
||||
const genKeyFinalizeParser = subparsers.add_parser("gen_key_finalize", {help: "Finalize key generation procedure."});
|
||||
genKeyFinalizeParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -228,7 +228,7 @@ genKeyFinalizeParser.add_argument("--password", {
|
||||
required: false
|
||||
});
|
||||
|
||||
let fullGenKeyParser = subparsers.add_parser("full_gen_key", {help: "Perform the full key generation procedure."});
|
||||
const fullGenKeyParser = subparsers.add_parser("full_gen_key", {help: "Perform the full key generation procedure."});
|
||||
fullGenKeyParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -246,7 +246,7 @@ fullGenKeyParser.add_argument("--password", {
|
||||
required: false
|
||||
});
|
||||
|
||||
let setPasswordParser = subparsers.add_parser("set_password", {help: "Set password for slot #3."});
|
||||
const setPasswordParser = subparsers.add_parser("set_password", {help: "Set password for slot #3."});
|
||||
setPasswordParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
'default': 3,
|
||||
@@ -259,7 +259,7 @@ setPasswordParser.add_argument("--password", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let replacePasswordParser = subparsers.add_parser("replace_password", {help: "Replace password for slot #3."});
|
||||
const replacePasswordParser = subparsers.add_parser("replace_password", {help: "Replace password for slot #3."});
|
||||
replacePasswordParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
'default': 3,
|
||||
@@ -277,7 +277,7 @@ replacePasswordParser.add_argument("--new-password", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let unsetPasswordParser = subparsers.add_parser("unset_password", {help: "Unset password for slot #3."});
|
||||
const unsetPasswordParser = subparsers.add_parser("unset_password", {help: "Unset password for slot #3."});
|
||||
unsetPasswordParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
'default': 3,
|
||||
@@ -292,7 +292,7 @@ unsetPasswordParser.add_argument("--password", {
|
||||
|
||||
subparsers.add_parser("get_pkeys", {help: "Get tag's public keys #1, #2 and #3."});
|
||||
|
||||
let getKeyInfoParser = subparsers.add_parser("get_key_info", {help: "Get key information."});
|
||||
const getKeyInfoParser = subparsers.add_parser("get_key_info", {help: "Get key information."});
|
||||
getKeyInfoParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -301,14 +301,14 @@ getKeyInfoParser.add_argument("-k", "--key-no", {
|
||||
|
||||
subparsers.add_parser("get_transport_pk", {help: "[Key backup] Get secure transport credentials from the target HaLo tag."});
|
||||
|
||||
let loadTransportPKParser = subparsers.add_parser("load_transport_pk", {help: "[Key backup] Load target tag's secure transport credentials into source tag."});
|
||||
const loadTransportPKParser = subparsers.add_parser("load_transport_pk", {help: "[Key backup] Load target tag's secure transport credentials into source tag."});
|
||||
loadTransportPKParser.add_argument("--data", {
|
||||
dest: 'data',
|
||||
help: "Source tag's secure transport credentials.",
|
||||
required: true
|
||||
});
|
||||
|
||||
let exportKeyParser = subparsers.add_parser("export_key", {help: "[Key backup] Export encrypted key pair from the source HaLo tag."});
|
||||
const exportKeyParser = subparsers.add_parser("export_key", {help: "[Key backup] Export encrypted key pair from the source HaLo tag."});
|
||||
exportKeyParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -326,7 +326,7 @@ exportKeyParser.add_argument("--data", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let importKeyInitParser = subparsers.add_parser("import_key_init", {help: "[Key backup] Initialize import of the encrypted key pair into the target HaLo tag."});
|
||||
const importKeyInitParser = subparsers.add_parser("import_key_init", {help: "[Key backup] Initialize import of the encrypted key pair into the target HaLo tag."});
|
||||
importKeyInitParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -339,7 +339,7 @@ importKeyInitParser.add_argument("--data", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let importKeyParser = subparsers.add_parser("import_key", {help: "[Key backup] Finalize import of the encrypted key pair into the target HaLo tag."});
|
||||
const importKeyParser = subparsers.add_parser("import_key", {help: "[Key backup] Finalize import of the encrypted key pair into the target HaLo tag."});
|
||||
importKeyParser.add_argument("-k", "--key-no", {
|
||||
dest: 'keyNo',
|
||||
type: 'int',
|
||||
@@ -347,7 +347,7 @@ importKeyParser.add_argument("-k", "--key-no", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let getDataStructParser = subparsers.add_parser("get_data_struct", {help: "Get certain data from the tag."});
|
||||
const getDataStructParser = subparsers.add_parser("get_data_struct", {help: "Get certain data from the tag."});
|
||||
getDataStructParser.add_argument("-s", "--spec", {
|
||||
dest: 'spec',
|
||||
type: 'str',
|
||||
@@ -355,7 +355,7 @@ getDataStructParser.add_argument("-s", "--spec", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let getGraffitiParser = subparsers.add_parser("get_graffiti", {help: "Get graffiti data from the tag."});
|
||||
const getGraffitiParser = subparsers.add_parser("get_graffiti", {help: "Get graffiti data from the tag."});
|
||||
getGraffitiParser.add_argument("-n", "--slot-no", {
|
||||
dest: 'slotNo',
|
||||
type: 'int',
|
||||
@@ -363,7 +363,7 @@ getGraffitiParser.add_argument("-n", "--slot-no", {
|
||||
required: true
|
||||
});
|
||||
|
||||
let storeGraffitiParser = subparsers.add_parser("store_graffiti", {help: "Store graffiti data to the tag."});
|
||||
const storeGraffitiParser = subparsers.add_parser("store_graffiti", {help: "Store graffiti data to the tag."});
|
||||
storeGraffitiParser.add_argument("-n", "--slot-no", {
|
||||
dest: 'slotNo',
|
||||
type: 'int',
|
||||
@@ -380,7 +380,7 @@ storeGraffitiParser.add_argument("--data", {
|
||||
subparsers.add_parser("pcsc_detect", {help: "Detect PC/SC readers and HaLo tags (for debugging)."});
|
||||
|
||||
function parseArgs() {
|
||||
let args = parser.parse_args();
|
||||
const args = parser.parse_args();
|
||||
|
||||
if (!args.name) {
|
||||
printVersionInfo();
|
||||
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
import {NFC} from 'nfc-pcsc';
|
||||
import open from 'open';
|
||||
|
||||
@@ -19,16 +21,18 @@ import {
|
||||
wsEventReaderDisconnected
|
||||
} from "./ws_server.js";
|
||||
import {execHaloCmdPCSC} from "@arx-research/libhalo/api/desktop";
|
||||
import {HaloCommandObject, Reader} from "@arx-research/libhalo/types";
|
||||
import {Namespace} from "argparse";
|
||||
|
||||
const nfc = new NFC();
|
||||
let stopPCSCTimeout = null;
|
||||
let stopPCSCTimeout: number | null = null;
|
||||
let isConnected = false;
|
||||
let isClosing = false;
|
||||
|
||||
async function checkCard(reader) {
|
||||
async function checkCard(reader: Reader) {
|
||||
// try to select Halo ETH Core Layer
|
||||
try {
|
||||
let resSelect = await reader.transmit(Buffer.from("00A4040007481199130E9F0100", "hex"), 255);
|
||||
const resSelect = await reader.transmit(Buffer.from("00A4040007481199130E9F0100", "hex"), 255);
|
||||
return resSelect.compare(Buffer.from([0x90, 0x00])) === 0;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -36,8 +40,8 @@ async function checkCard(reader) {
|
||||
}
|
||||
}
|
||||
|
||||
function runHalo(entryMode, args) {
|
||||
nfc.on('reader', reader => {
|
||||
function runHalo(entryMode: string, args: Namespace) {
|
||||
nfc.on('reader', (reader: Reader) => {
|
||||
reader.autoProcessing = false;
|
||||
|
||||
reader.on('error', err => {
|
||||
@@ -70,11 +74,11 @@ function runHalo(entryMode, args) {
|
||||
console.log("Tag inserted:", reader.reader.name, '(Type: ' + card.type + ', ATR: ' + card.atr.toString('hex').toUpperCase() + ')');
|
||||
}
|
||||
|
||||
clearTimeout(stopPCSCTimeout);
|
||||
clearTimeout(stopPCSCTimeout!);
|
||||
stopPCSCTimeout = setTimeout(stopPCSC, 4000, "timeout", args.output);
|
||||
|
||||
(async () => {
|
||||
let res = await checkCard(reader);
|
||||
const res = await checkCard(reader);
|
||||
|
||||
if (res) {
|
||||
clearTimeout(stopPCSCTimeout);
|
||||
@@ -86,7 +90,7 @@ function runHalo(entryMode, args) {
|
||||
res = {"status": "ok"};
|
||||
} else if (args.name === "test") {
|
||||
res = await __runTestSuite({"__this_is_unsafe": true},
|
||||
"pcsc", async (command) => await execHaloCmdPCSC(command, reader));
|
||||
"pcsc", async (command: HaloCommandObject) => await execHaloCmdPCSC(command, reader));
|
||||
} else {
|
||||
try {
|
||||
res = await execHaloCmdPCSC(args, reader);
|
||||
@@ -94,7 +98,7 @@ function runHalo(entryMode, args) {
|
||||
if (args.output === "color") {
|
||||
console.error(e);
|
||||
} else {
|
||||
console.log(JSON.stringify({"_exception": {"message": String(e), "stack": e.stack}}));
|
||||
console.log(JSON.stringify({"_exception": {"message": String(e), "stack": (<Error> e).stack}}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,14 +134,14 @@ function runHalo(entryMode, args) {
|
||||
});
|
||||
});
|
||||
|
||||
nfc.on('error', err => {
|
||||
nfc.on('error', (err: Error) => {
|
||||
if (!isClosing) {
|
||||
console.log('an error occurred', err);
|
||||
}
|
||||
});
|
||||
|
||||
function stopPCSC(code, output) {
|
||||
clearTimeout(stopPCSCTimeout);
|
||||
function stopPCSC(code: string, output: string) {
|
||||
clearTimeout(stopPCSCTimeout!);
|
||||
|
||||
if (code === "error" && output === "color") {
|
||||
console.error('Command execution failed.');
|
||||
@@ -149,7 +153,7 @@ function runHalo(entryMode, args) {
|
||||
}
|
||||
}
|
||||
|
||||
for (let rdrName in nfc.readers) {
|
||||
for (const rdrName in nfc.readers) {
|
||||
nfc.readers[rdrName].close();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {parseArgs} from './args_bridge.js';
|
||||
import {runHalo} from "./cli.js";
|
||||
import {printVersionInfo} from "./version.js";
|
||||
|
||||
let args = parseArgs();
|
||||
const args = parseArgs();
|
||||
|
||||
if (!args) {
|
||||
process.exit(0);
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
import {webcrypto as crypto} from 'crypto';
|
||||
import express from "express";
|
||||
import {WebSocketServer} from 'ws';
|
||||
import {RawData, WebSocket, WebSocketServer} from 'ws';
|
||||
import {IncomingMessage} from "http";
|
||||
import queryString from 'query-string';
|
||||
import nunjucks from "nunjucks";
|
||||
import {parse} from "url";
|
||||
@@ -14,27 +15,35 @@ import {parse} from "url";
|
||||
import {parseArgs} from './args_gateway.js';
|
||||
import {printVersionInfo, getBuildInfo} from "./version.js";
|
||||
import {dirname} from "./util.js";
|
||||
import {Namespace} from "argparse";
|
||||
|
||||
let buildInfo = getBuildInfo();
|
||||
const buildInfo = getBuildInfo();
|
||||
|
||||
const REQUESTOR_SESS_LIMIT = 10 * 60 * 1000;
|
||||
const MAX_SESSION_LIMIT = 1000;
|
||||
|
||||
let args = parseArgs();
|
||||
const args = parseArgs();
|
||||
|
||||
if (!args) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let sessionIds = {};
|
||||
interface SocketState {
|
||||
requestor: WebSocket,
|
||||
executor: WebSocket | null
|
||||
requestUID: null
|
||||
reconnects: number
|
||||
}
|
||||
|
||||
function processRequestor(ws, req) {
|
||||
const sessionIds: Record<string, SocketState> = {};
|
||||
|
||||
function processRequestor(ws: WebSocket, req: IncomingMessage) {
|
||||
if (Object.keys(sessionIds).length >= MAX_SESSION_LIMIT) {
|
||||
ws.close(4053, "Too many connections.");
|
||||
return;
|
||||
}
|
||||
|
||||
let sessionId = Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString('base64url');
|
||||
const sessionId = Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString('base64url');
|
||||
let ipStr = 'IP: ' + req.socket.remoteAddress;
|
||||
|
||||
if (req.headers['x-forwarded-for']) {
|
||||
@@ -50,7 +59,7 @@ function processRequestor(ws, req) {
|
||||
"reconnects": 0
|
||||
};
|
||||
|
||||
let sobj = sessionIds[sessionId];
|
||||
const sobj = sessionIds[sessionId];
|
||||
|
||||
setTimeout(() => {
|
||||
ws.close(4080, "Session timed out.");
|
||||
@@ -68,7 +77,7 @@ function processRequestor(ws, req) {
|
||||
});
|
||||
|
||||
ws.on('message', function message(data) {
|
||||
let obj = JSON.parse(data);
|
||||
const obj = JSON.parse(data.toString('utf-8'));
|
||||
|
||||
if (obj.type === "keepalive") {
|
||||
// ignore
|
||||
@@ -101,13 +110,13 @@ function processRequestor(ws, req) {
|
||||
}));
|
||||
}
|
||||
|
||||
function processExecutor(ws, req, sessionId) {
|
||||
if (!sessionIds.hasOwnProperty(sessionId)) {
|
||||
function processExecutor(ws: WebSocket, req: IncomingMessage, sessionId: string) {
|
||||
if (!Object.prototype.hasOwnProperty.call(sessionIds, sessionId)) {
|
||||
ws.close(4052, "No such session ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
let sobj = sessionIds[sessionId];
|
||||
const sobj = sessionIds[sessionId];
|
||||
let ipStr = 'IP: ' + req.socket.remoteAddress;
|
||||
|
||||
if (req.headers['x-forwarded-for']) {
|
||||
@@ -138,8 +147,8 @@ function processExecutor(ws, req, sessionId) {
|
||||
sobj.requestor.send(JSON.stringify({"type": "executor_disconnected"}));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
let obj = JSON.parse(data);
|
||||
ws.on('message', (data: RawData) => {
|
||||
const obj = JSON.parse(data.toString('utf-8'));
|
||||
|
||||
if (obj.type === "keepalive") {
|
||||
// ignore
|
||||
@@ -165,11 +174,11 @@ function processExecutor(ws, req, sessionId) {
|
||||
ws.send(JSON.stringify({"type": "ping"}));
|
||||
}
|
||||
|
||||
function createServer(args) {
|
||||
function createServer(args: Namespace) {
|
||||
const app = express();
|
||||
const server = app.listen(args.listenPort, args.listenHost);
|
||||
|
||||
let wss = new WebSocketServer({
|
||||
const wss = new WebSocketServer({
|
||||
noServer: true,
|
||||
maxPayload: 6 * 1024 // max packet size - 6 kB
|
||||
});
|
||||
@@ -199,7 +208,7 @@ function createServer(args) {
|
||||
});
|
||||
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
const { pathname } = parse(request.url);
|
||||
const { pathname } = parse(request.url!);
|
||||
|
||||
if (pathname === "/ws") {
|
||||
wss.handleUpgrade(request, socket, head, socket => {
|
||||
@@ -212,13 +221,13 @@ function createServer(args) {
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
try {
|
||||
let query = req.url.split('?', 2)[1];
|
||||
let qs = queryString.parse(query);
|
||||
const query = req.url!.split('?', 2)[1];
|
||||
const qs = queryString.parse(query);
|
||||
|
||||
if (qs.side === "requestor") {
|
||||
processRequestor(ws, req);
|
||||
} else if (qs.side === "executor") {
|
||||
processExecutor(ws, req, qs.sessionId);
|
||||
processExecutor(ws, req, qs.sessionId as string);
|
||||
} else {
|
||||
ws.close(4050, "Invalid query string parameters specified.");
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname as path_dirname } from 'node:path';
|
||||
import { dirname as path_dirname, join as path_join } from 'node:path';
|
||||
import crypto from "crypto";
|
||||
|
||||
function randomBuffer() {
|
||||
return Buffer.from(crypto.getRandomValues(new Uint8Array(32)));
|
||||
}
|
||||
|
||||
let dirname;
|
||||
let dirname: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
if (process.pkg && process.pkg.entrypoint) {
|
||||
dirname = __dirname;
|
||||
} else {
|
||||
const filename = fileURLToPath(import.meta.url);
|
||||
dirname = path_dirname(filename);
|
||||
dirname = path_join(path_dirname(filename), '..');
|
||||
console.log(dirname);
|
||||
}
|
||||
|
||||
export {dirname, randomBuffer};
|
||||
@@ -10,7 +10,7 @@ function getVersionInfo() {
|
||||
}
|
||||
|
||||
function getBuildInfo() {
|
||||
let versionInfo = getVersionInfo();
|
||||
const versionInfo = getVersionInfo();
|
||||
|
||||
return {
|
||||
tagName: versionInfo ? versionInfo.tagName : 'SNAPSHOT',
|
||||
@@ -20,7 +20,7 @@ function getBuildInfo() {
|
||||
}
|
||||
|
||||
function printVersionInfo() {
|
||||
let versionInfo = getVersionInfo();
|
||||
const versionInfo = getVersionInfo();
|
||||
|
||||
if (versionInfo) {
|
||||
console.log(versionInfo.name + ' (' + versionInfo.tagName + '; ' + versionInfo.commitId + ')');
|
||||
@@ -1,26 +1,33 @@
|
||||
import express from 'express';
|
||||
import nunjucks from "nunjucks";
|
||||
import {WebSocketServer} from 'ws';
|
||||
import {dirname, randomBuffer} from "./util.js";
|
||||
import {WebSocket, WebSocketServer} from 'ws';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import https from "https";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import util from "util";
|
||||
import {execHaloCmdPCSC} from "@arx-research/libhalo/api/desktop";
|
||||
|
||||
import {dirname, randomBuffer} from "./util.js";
|
||||
import {getBuildInfo} from "./version.js";
|
||||
|
||||
import {execHaloCmdPCSC} from "@arx-research/libhalo/api/desktop";
|
||||
import {NFCOperationError} from "@arx-research/libhalo/api/common";
|
||||
import {Reader} from "@arx-research/libhalo/types";
|
||||
import {Namespace} from "argparse";
|
||||
|
||||
let wss = null;
|
||||
let wss: WebSocketServer | null = null;
|
||||
|
||||
let currentWsClient = null;
|
||||
let currentState = null;
|
||||
let currentWsClient: WebSocket | null = null;
|
||||
let currentState: {
|
||||
handle: string
|
||||
reader: Reader
|
||||
} | null = null;
|
||||
|
||||
let jwtSigningKey = randomBuffer().toString('hex');
|
||||
let userConsentOrigins = new Set();
|
||||
const jwtSigningKey = randomBuffer().toString('hex');
|
||||
const userConsentOrigins = new Set();
|
||||
|
||||
let buildInfo = getBuildInfo();
|
||||
const buildInfo = getBuildInfo();
|
||||
|
||||
function generateHandle() {
|
||||
return randomBuffer().toString('base64');
|
||||
@@ -38,13 +45,13 @@ async function makeCSRFToken() {
|
||||
});
|
||||
}
|
||||
|
||||
async function validateCSRFToken(token) {
|
||||
async function validateCSRFToken(token: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.verify(token, jwtSigningKey, (err, decoded) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (decoded.purpose !== 'csrf-consent') {
|
||||
if ((<Record<string, string>> decoded).purpose !== 'csrf-consent') {
|
||||
reject(new Error('Incorrect token purpose.'));
|
||||
} else {
|
||||
resolve(decoded);
|
||||
@@ -54,7 +61,7 @@ async function validateCSRFToken(token) {
|
||||
});
|
||||
}
|
||||
|
||||
function sendToCurrentWs(ws, data) {
|
||||
function sendToCurrentWs(ws: WebSocket | null, data: unknown) {
|
||||
console.log('send', util.inspect(data, {showHidden: false, depth: null, colors: true}));
|
||||
|
||||
if (currentWsClient !== null && (ws === null || currentWsClient === ws)) {
|
||||
@@ -65,7 +72,7 @@ function sendToCurrentWs(ws, data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function wsEventCardConnected(reader) {
|
||||
function wsEventCardConnected(reader: Reader) {
|
||||
if (currentState) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_removed",
|
||||
@@ -77,7 +84,7 @@ function wsEventCardConnected(reader) {
|
||||
});
|
||||
}
|
||||
|
||||
let handle = generateHandle();
|
||||
const handle = generateHandle();
|
||||
currentState = {"handle": handle, "reader": reader};
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_added",
|
||||
@@ -89,7 +96,7 @@ function wsEventCardConnected(reader) {
|
||||
});
|
||||
}
|
||||
|
||||
function wsEventCardIncompatible(reader) {
|
||||
function wsEventCardIncompatible(reader: Reader) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_not_compatible",
|
||||
"uid": null,
|
||||
@@ -100,7 +107,7 @@ function wsEventCardIncompatible(reader) {
|
||||
});
|
||||
}
|
||||
|
||||
function wsEventCardDisconnected(reader) {
|
||||
function wsEventCardDisconnected(reader: Reader) {
|
||||
if (currentState !== null && currentState.reader === reader) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_removed",
|
||||
@@ -114,7 +121,7 @@ function wsEventCardDisconnected(reader) {
|
||||
}
|
||||
}
|
||||
|
||||
function wsEventReaderConnected(reader) {
|
||||
function wsEventReaderConnected(reader: Reader) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "reader_added",
|
||||
"uid": null,
|
||||
@@ -124,7 +131,7 @@ function wsEventReaderConnected(reader) {
|
||||
});
|
||||
}
|
||||
|
||||
function wsEventReaderDisconnected(reader) {
|
||||
function wsEventReaderDisconnected(reader: Reader) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "reader_removed",
|
||||
"uid": null,
|
||||
@@ -156,7 +163,7 @@ function readTLSData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function wsCreateServer(args, getReaderNames) {
|
||||
function wsCreateServer(args: Namespace, getReaderNames: () => string[]) {
|
||||
const tlsData = readTLSData();
|
||||
let serverTLS = null;
|
||||
|
||||
@@ -199,8 +206,16 @@ function wsCreateServer(args, getReaderNames) {
|
||||
});
|
||||
|
||||
app.get('/consent', async (req, res) => {
|
||||
let url = new URL(req.query.website);
|
||||
let csrfToken = await makeCSRFToken();
|
||||
let url;
|
||||
|
||||
try {
|
||||
url = new URL(req.query.website as string);
|
||||
} catch (e) {
|
||||
res.status(400).send('Bad request.');
|
||||
return;
|
||||
}
|
||||
|
||||
const csrfToken = await makeCSRFToken();
|
||||
|
||||
res.render('consent.html', {
|
||||
csrfToken,
|
||||
@@ -227,28 +242,28 @@ function wsCreateServer(args, getReaderNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = new URL(req.body.website);
|
||||
const url = new URL(req.body.website);
|
||||
userConsentOrigins.add(url.protocol + '//' + url.host);
|
||||
|
||||
res.render('consent_close.html');
|
||||
});
|
||||
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
wss.handleUpgrade(request, socket, head, socket => {
|
||||
wss.emit('connection', socket, request);
|
||||
wss && wss.handleUpgrade(request, socket, head, socket => {
|
||||
wss && wss.emit('connection', socket, request);
|
||||
});
|
||||
});
|
||||
|
||||
if (serverTLS) {
|
||||
serverTLS.on('upgrade', (request, socket, head) => {
|
||||
wss.handleUpgrade(request, socket, head, socket => {
|
||||
wss.emit('connection', socket, request);
|
||||
wss && wss.handleUpgrade(request, socket, head, socket => {
|
||||
wss && wss.emit('connection', socket, request);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
let parts = req.url.split('?');
|
||||
const parts = req.url!.split('?');
|
||||
|
||||
if (parts.length === 2 && parts[1] === "ping=1") {
|
||||
ws.close(4090, "Pong.");
|
||||
@@ -259,14 +274,14 @@ function wsCreateServer(args, getReaderNames) {
|
||||
let originHostname;
|
||||
|
||||
try {
|
||||
originHostname = new URL(req.headers.origin).hostname;
|
||||
originHostname = new URL(req.headers.origin!).hostname;
|
||||
} catch (e) {
|
||||
ws.close(4003, "Failed to parse origin URL.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.allowOrigins) {
|
||||
let allowedOrigins = args.allowOrigins.split(';');
|
||||
const allowedOrigins = args.allowOrigins.split(';');
|
||||
|
||||
if (allowedOrigins.includes(req.headers.origin)) {
|
||||
permitted = true;
|
||||
@@ -299,7 +314,7 @@ function wsCreateServer(args, getReaderNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
let packet = JSON.parse(data);
|
||||
const packet = JSON.parse(data.toString('utf-8'));
|
||||
console.log('recv', util.inspect(packet, {showHidden: false, depth: null, colors: true}));
|
||||
|
||||
if (packet.type === "exec_halo") {
|
||||
@@ -308,7 +323,7 @@ function wsCreateServer(args, getReaderNames) {
|
||||
throw new NFCOperationError("Invalid handle.");
|
||||
}
|
||||
|
||||
let res = await execHaloCmdPCSC(packet.command, currentState.reader);
|
||||
const res = await execHaloCmdPCSC(packet.command, currentState.reader);
|
||||
sendToCurrentWs(ws, {
|
||||
"event": "exec_success",
|
||||
"uid": packet.uid,
|
||||
@@ -316,7 +331,8 @@ function wsCreateServer(args, getReaderNames) {
|
||||
"res": res
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
sendToCurrentWs(ws, {
|
||||
"event": "exec_exception",
|
||||
"uid": packet.uid,
|
||||
@@ -345,9 +361,9 @@ function wsCreateServer(args, getReaderNames) {
|
||||
}
|
||||
});
|
||||
|
||||
let readerNames = getReaderNames();
|
||||
const readerNames = getReaderNames();
|
||||
|
||||
for (let readerName of readerNames) {
|
||||
for (const readerName of readerNames) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "reader_added",
|
||||
"uid": null,
|
||||
@@ -6,8 +6,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importHelpers": true,
|
||||
"lib": [
|
||||
"es2020",
|
||||
"es5",
|
||||
"es2022",
|
||||
"dom"
|
||||
],
|
||||
"moduleResolution": "node16",
|
||||
@@ -16,7 +15,7 @@
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedLocals": false,
|
||||
"preserveSymlinks": true,
|
||||
"preserveWatchOutput": true,
|
||||
"pretty": false,
|
||||
@@ -29,7 +28,7 @@
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/**/*.tsx",
|
||||
"./src.ts/**/*.ts",
|
||||
"./src.ts/**/*.js"
|
||||
],
|
||||
"exclude": []
|
||||
|
||||
@@ -6,9 +6,9 @@ const __dirname = dirname(__filename);
|
||||
|
||||
export default {
|
||||
entry: {
|
||||
entry_cli: './src.ts/entry_cli.js',
|
||||
entry_bridge: './src.ts/entry_bridge.js',
|
||||
entry_gateway: './src.ts/entry_gateway.js',
|
||||
entry_cli: './src.ts/entry_cli.ts',
|
||||
entry_bridge: './src.ts/entry_bridge.ts',
|
||||
entry_gateway: './src.ts/entry_gateway.ts',
|
||||
},
|
||||
output: {
|
||||
filename: '[name].bundle.cjs',
|
||||
@@ -19,14 +19,17 @@ export default {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
test: /\.ts$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
extensions: ['.ts', '.js'],
|
||||
extensionAlias: {
|
||||
'.js': ['.js', '.ts'],
|
||||
},
|
||||
fallback: {
|
||||
vm: false,
|
||||
chokidar: false,
|
||||
|
||||
583
cli/yarn.lock
583
cli/yarn.lock
@@ -10,7 +10,6 @@
|
||||
"@arx-research/libhalo@../core":
|
||||
version "1.4.3"
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
buffer "^6.0.3"
|
||||
elliptic "^6.5.5"
|
||||
ethers "^6.13.1"
|
||||
@@ -69,6 +68,62 @@
|
||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
||||
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.11.0":
|
||||
version "4.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
|
||||
integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
|
||||
|
||||
"@eslint/config-array@^0.17.0":
|
||||
version "0.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.17.0.tgz#ff305e1ee618a00e6e5d0485454c8d92d94a860d"
|
||||
integrity sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==
|
||||
dependencies:
|
||||
"@eslint/object-schema" "^2.1.4"
|
||||
debug "^4.3.1"
|
||||
minimatch "^3.1.2"
|
||||
|
||||
"@eslint/eslintrc@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6"
|
||||
integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
debug "^4.3.2"
|
||||
espree "^10.0.1"
|
||||
globals "^14.0.0"
|
||||
ignore "^5.2.0"
|
||||
import-fresh "^3.2.1"
|
||||
js-yaml "^4.1.0"
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@9.7.0", "@eslint/js@^9.7.0":
|
||||
version "9.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.7.0.tgz#b712d802582f02b11cfdf83a85040a296afec3f0"
|
||||
integrity sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==
|
||||
|
||||
"@eslint/object-schema@^2.1.4":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843"
|
||||
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
|
||||
|
||||
"@humanwhocodes/module-importer@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
|
||||
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
|
||||
|
||||
"@humanwhocodes/retry@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570"
|
||||
integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
|
||||
@@ -134,7 +189,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
|
||||
"@nodelib/fs.walk@^1.2.3":
|
||||
"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||
@@ -150,6 +205,26 @@
|
||||
bindings "^1.5.0"
|
||||
nan "^2.14.0"
|
||||
|
||||
"@types/argparse@^2.0.16":
|
||||
version "2.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-2.0.16.tgz#3bb7ccd2844b3a8bcd6efbd217f6c0ea06a80d22"
|
||||
integrity sha512-aMqBra2JlqpFeCWOinCtpRpiCkPIXH8hahW2+FkGzvWjfE5sAqtOcrjN5DRcMnTQqFDe6gb1CVYuGnBH0lhXwA==
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
|
||||
integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==
|
||||
dependencies:
|
||||
"@types/connect" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/connect@*":
|
||||
version "3.4.38"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
|
||||
integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/eslint-scope@^3.7.3":
|
||||
version "3.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5"
|
||||
@@ -171,11 +246,48 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||
|
||||
"@types/express-serve-static-core@^4.17.33":
|
||||
version "4.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6"
|
||||
integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/qs" "*"
|
||||
"@types/range-parser" "*"
|
||||
"@types/send" "*"
|
||||
|
||||
"@types/express@^4.17.21":
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
|
||||
integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "^4.17.33"
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/http-errors@*":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
|
||||
integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.8":
|
||||
version "7.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||
|
||||
"@types/jsonwebtoken@^9.0.6":
|
||||
version "9.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3"
|
||||
integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mime@^1":
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
|
||||
integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
|
||||
|
||||
"@types/node@*":
|
||||
version "20.14.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a"
|
||||
@@ -188,6 +300,126 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469"
|
||||
integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==
|
||||
|
||||
"@types/nunjucks@^3.2.6":
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/nunjucks/-/nunjucks-3.2.6.tgz#6d6e0363719545df8b9a024279902edf68b2caa9"
|
||||
integrity sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.9.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce"
|
||||
integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==
|
||||
|
||||
"@types/range-parser@*":
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
|
||||
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
|
||||
|
||||
"@types/send@*":
|
||||
version "0.17.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
|
||||
integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==
|
||||
dependencies:
|
||||
"@types/mime" "^1"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.15.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714"
|
||||
integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==
|
||||
dependencies:
|
||||
"@types/http-errors" "*"
|
||||
"@types/node" "*"
|
||||
"@types/send" "*"
|
||||
|
||||
"@types/ws@^8.5.11":
|
||||
version "8.5.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.11.tgz#90ad17b3df7719ce3e6bc32f83ff954d38656508"
|
||||
integrity sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz#b3563927341eca15124a18c6f94215f779f5c02a"
|
||||
integrity sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.10.0"
|
||||
"@typescript-eslint/scope-manager" "7.16.0"
|
||||
"@typescript-eslint/type-utils" "7.16.0"
|
||||
"@typescript-eslint/utils" "7.16.0"
|
||||
"@typescript-eslint/visitor-keys" "7.16.0"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.3.1"
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^1.3.0"
|
||||
|
||||
"@typescript-eslint/parser@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.16.0.tgz#53fae8112f8c912024aea7b499cf7374487af6d8"
|
||||
integrity sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "7.16.0"
|
||||
"@typescript-eslint/types" "7.16.0"
|
||||
"@typescript-eslint/typescript-estree" "7.16.0"
|
||||
"@typescript-eslint/visitor-keys" "7.16.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz#eb0757af5720c9c53c8010d7a0355ae27e17b7e5"
|
||||
integrity sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "7.16.0"
|
||||
"@typescript-eslint/visitor-keys" "7.16.0"
|
||||
|
||||
"@typescript-eslint/type-utils@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz#ec52b1932b8fb44a15a3e20208e0bd49d0b6bd00"
|
||||
integrity sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "7.16.0"
|
||||
"@typescript-eslint/utils" "7.16.0"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^1.3.0"
|
||||
|
||||
"@typescript-eslint/types@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.16.0.tgz#60a19d7e7a6b1caa2c06fac860829d162a036ed2"
|
||||
integrity sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==
|
||||
|
||||
"@typescript-eslint/typescript-estree@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz#98ac779d526fab2a781e5619c9250f3e33867c09"
|
||||
integrity sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "7.16.0"
|
||||
"@typescript-eslint/visitor-keys" "7.16.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^1.3.0"
|
||||
|
||||
"@typescript-eslint/utils@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.16.0.tgz#b38dc0ce1778e8182e227c98d91d3418449aa17f"
|
||||
integrity sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "7.16.0"
|
||||
"@typescript-eslint/types" "7.16.0"
|
||||
"@typescript-eslint/typescript-estree" "7.16.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@7.16.0":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz#a1d99fa7a3787962d6e0efd436575ef840e23b06"
|
||||
integrity sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "7.16.0"
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb"
|
||||
@@ -387,7 +619,12 @@ acorn-import-attributes@^1.9.5:
|
||||
resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
|
||||
integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
|
||||
|
||||
acorn@^8.7.1, acorn@^8.8.2:
|
||||
acorn-jsx@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
acorn@^8.12.0, acorn@^8.7.1, acorn@^8.8.2:
|
||||
version "8.12.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
|
||||
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
|
||||
@@ -409,7 +646,7 @@ ajv-keywords@^3.5.2:
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||
|
||||
ajv@^6.12.5:
|
||||
ajv@^6.12.4, ajv@^6.12.5:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
@@ -534,6 +771,14 @@ body-parser@1.20.2:
|
||||
type-is "~1.6.18"
|
||||
unpipe "1.0.0"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
@@ -619,6 +864,11 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
|
||||
get-intrinsic "^1.2.4"
|
||||
set-function-length "^1.2.1"
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
camelcase@^5.0.0:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
@@ -629,7 +879,7 @@ caniuse-lite@^1.0.30001640:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz#6aa6610eb24067c246d30c57f055a9d0a7f8d05f"
|
||||
integrity sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==
|
||||
|
||||
chalk@^4.1.0, chalk@^4.1.2:
|
||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@@ -719,6 +969,11 @@ commander@^5.1.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
content-disposition@0.5.4:
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
|
||||
@@ -769,7 +1024,7 @@ create-hmac@^1.1.4:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
cross-spawn@^7.0.3:
|
||||
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
@@ -812,7 +1067,7 @@ debug@2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4:
|
||||
debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.5"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
|
||||
integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==
|
||||
@@ -841,6 +1096,11 @@ deep-extend@^0.6.0:
|
||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||
|
||||
deep-is@^0.1.3:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
default-browser-id@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26"
|
||||
@@ -1073,6 +1333,11 @@ escape-html@~1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
eslint-scope@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||
@@ -1081,6 +1346,80 @@ eslint-scope@5.1.1:
|
||||
esrecurse "^4.3.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^8.0.2:
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.2.tgz#5cbb33d4384c9136083a71190d548158fe128f94"
|
||||
integrity sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==
|
||||
dependencies:
|
||||
esrecurse "^4.3.0"
|
||||
estraverse "^5.2.0"
|
||||
|
||||
eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
|
||||
eslint-visitor-keys@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
|
||||
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
|
||||
|
||||
eslint@^9.7.0:
|
||||
version "9.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.7.0.tgz#bedb48e1cdc2362a0caaa106a4c6ed943e8b09e4"
|
||||
integrity sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/regexpp" "^4.11.0"
|
||||
"@eslint/config-array" "^0.17.0"
|
||||
"@eslint/eslintrc" "^3.1.0"
|
||||
"@eslint/js" "9.7.0"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@humanwhocodes/retry" "^0.3.0"
|
||||
"@nodelib/fs.walk" "^1.2.8"
|
||||
ajv "^6.12.4"
|
||||
chalk "^4.0.0"
|
||||
cross-spawn "^7.0.2"
|
||||
debug "^4.3.2"
|
||||
escape-string-regexp "^4.0.0"
|
||||
eslint-scope "^8.0.2"
|
||||
eslint-visitor-keys "^4.0.0"
|
||||
espree "^10.1.0"
|
||||
esquery "^1.5.0"
|
||||
esutils "^2.0.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
file-entry-cache "^8.0.0"
|
||||
find-up "^5.0.0"
|
||||
glob-parent "^6.0.2"
|
||||
ignore "^5.2.0"
|
||||
imurmurhash "^0.1.4"
|
||||
is-glob "^4.0.0"
|
||||
is-path-inside "^3.0.3"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
lodash.merge "^4.6.2"
|
||||
minimatch "^3.1.2"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.9.3"
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
espree@^10.0.1, espree@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56"
|
||||
integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==
|
||||
dependencies:
|
||||
acorn "^8.12.0"
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^4.0.0"
|
||||
|
||||
esquery@^1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
|
||||
integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
|
||||
dependencies:
|
||||
estraverse "^5.1.0"
|
||||
|
||||
esrecurse@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
|
||||
@@ -1093,11 +1432,16 @@ estraverse@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||
|
||||
estraverse@^5.2.0:
|
||||
estraverse@^5.1.0, estraverse@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
@@ -1163,7 +1507,7 @@ express@^4.19.2:
|
||||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
@@ -1184,6 +1528,11 @@ fast-json-stable-stringify@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
fast-levenshtein@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||
|
||||
fastest-levenshtein@^1.0.12:
|
||||
version "1.0.16"
|
||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
|
||||
@@ -1196,6 +1545,13 @@ fastq@^1.6.0:
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
file-entry-cache@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
|
||||
integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
|
||||
dependencies:
|
||||
flat-cache "^4.0.0"
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
@@ -1234,11 +1590,32 @@ find-up@^4.0.0, find-up@^4.1.0:
|
||||
locate-path "^5.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-up@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
|
||||
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
||||
dependencies:
|
||||
locate-path "^6.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
flat-cache@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
|
||||
integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
|
||||
dependencies:
|
||||
flatted "^3.2.9"
|
||||
keyv "^4.5.4"
|
||||
|
||||
flat@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
|
||||
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
|
||||
|
||||
flatted@^3.2.9:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
|
||||
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
|
||||
|
||||
for-each@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
||||
@@ -1336,11 +1713,23 @@ glob-parent@^5.1.2:
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob-parent@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
|
||||
dependencies:
|
||||
is-glob "^4.0.3"
|
||||
|
||||
glob-to-regexp@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||
|
||||
globals@^14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
|
||||
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
|
||||
|
||||
globalthis@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236"
|
||||
@@ -1373,6 +1762,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11,
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
graphemer@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
|
||||
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
|
||||
|
||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
||||
@@ -1476,11 +1870,19 @@ ieee754@^1.1.13, ieee754@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
||||
ignore@^5.2.0:
|
||||
ignore@^5.2.0, ignore@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
|
||||
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
|
||||
|
||||
import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
|
||||
dependencies:
|
||||
parent-module "^1.0.0"
|
||||
resolve-from "^4.0.0"
|
||||
|
||||
import-local@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
|
||||
@@ -1489,6 +1891,11 @@ import-local@^3.0.2:
|
||||
pkg-dir "^4.2.0"
|
||||
resolve-cwd "^3.0.0"
|
||||
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
||||
|
||||
inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
@@ -1597,7 +2004,7 @@ is-fullwidth-code-point@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-glob@^4.0.1:
|
||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
@@ -1628,6 +2035,11 @@ is-number@^7.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
is-path-inside@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||
|
||||
is-plain-object@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||
@@ -1724,11 +2136,23 @@ js-sha256@^0.11.0:
|
||||
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.11.0.tgz#256a921d9292f7fe98905face82e367abaca9576"
|
||||
integrity sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsesc@^2.5.1:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
||||
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
|
||||
|
||||
json-buffer@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
|
||||
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
|
||||
|
||||
json-parse-even-better-errors@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
|
||||
@@ -1739,6 +2163,11 @@ json-schema-traverse@^0.4.1:
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-stable-stringify-without-jsonify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
@@ -1781,11 +2210,26 @@ jws@^3.2.2:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
keyv@^4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
|
||||
dependencies:
|
||||
json-buffer "3.0.1"
|
||||
|
||||
kind-of@^6.0.2:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
loader-runner@^4.2.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
|
||||
@@ -1798,6 +2242,13 @@ locate-path@^5.0.0:
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
locate-path@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
||||
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
@@ -1828,6 +2279,11 @@ lodash.isstring@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
@@ -1914,6 +2370,20 @@ minimatch@9.0.4:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^9.0.4:
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
|
||||
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
@@ -1957,6 +2427,11 @@ napi-build-utils@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
|
||||
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
negotiator@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||
@@ -2051,6 +2526,18 @@ open@^10.1.0:
|
||||
is-inside-container "^1.0.0"
|
||||
is-wsl "^3.1.0"
|
||||
|
||||
optionator@^0.9.3:
|
||||
version "0.9.4"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
||||
integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
|
||||
dependencies:
|
||||
deep-is "^0.1.3"
|
||||
fast-levenshtein "^2.0.6"
|
||||
levn "^0.4.1"
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "^0.4.0"
|
||||
word-wrap "^1.2.5"
|
||||
|
||||
p-is-promise@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971"
|
||||
@@ -2063,6 +2550,13 @@ p-limit@^2.2.0:
|
||||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-limit@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
|
||||
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
|
||||
dependencies:
|
||||
yocto-queue "^0.1.0"
|
||||
|
||||
p-locate@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
|
||||
@@ -2070,11 +2564,25 @@ p-locate@^4.1.0:
|
||||
dependencies:
|
||||
p-limit "^2.2.0"
|
||||
|
||||
p-locate@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
|
||||
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
|
||||
dependencies:
|
||||
p-limit "^3.0.2"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
@@ -2166,6 +2674,11 @@ prebuild-install@7.1.1:
|
||||
tar-fs "^2.0.0"
|
||||
tunnel-agent "^0.6.0"
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
@@ -2344,6 +2857,11 @@ resolve-cwd@^3.0.0:
|
||||
dependencies:
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve-from@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
|
||||
@@ -2426,7 +2944,7 @@ schema-utils@^3.1.1, schema-utils@^3.2.0:
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
semver@^7.3.4, semver@^7.3.5, semver@^7.5.4:
|
||||
semver@^7.3.4, semver@^7.3.5, semver@^7.5.4, semver@^7.6.0:
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
|
||||
integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
|
||||
@@ -2648,6 +3166,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-json-comments@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
@@ -2719,6 +3242,11 @@ terser@^5.26.0:
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||
|
||||
to-fast-properties@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||
@@ -2741,6 +3269,11 @@ tr46@~0.0.3:
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
ts-api-utils@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
|
||||
integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
|
||||
|
||||
ts-loader@^9.5.1:
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.1.tgz#63d5912a86312f1fbe32cef0859fb8b2193d9b89"
|
||||
@@ -2764,6 +3297,13 @@ tunnel-agent@^0.6.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
|
||||
type-is@~1.6.18:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
@@ -2816,6 +3356,15 @@ typed-array-length@^1.0.6:
|
||||
is-typed-array "^1.1.13"
|
||||
possible-typed-array-names "^1.0.0"
|
||||
|
||||
typescript-eslint@^7.16.0:
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-7.16.0.tgz#8466a7c6d8b5acfdcaf53ecc8ecf240072b346d8"
|
||||
integrity sha512-kaVRivQjOzuoCXU6+hLnjo3/baxyzWVO5GrnExkFzETRYJKVHYkrJglOu2OCm8Hi9RPDWX1PTNNTpU5KRV0+RA==
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin" "7.16.0"
|
||||
"@typescript-eslint/parser" "7.16.0"
|
||||
"@typescript-eslint/utils" "7.16.0"
|
||||
|
||||
typescript@^5.5.3:
|
||||
version "5.5.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa"
|
||||
@@ -3009,6 +3558,11 @@ wildcard@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67"
|
||||
integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==
|
||||
|
||||
word-wrap@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
@@ -3094,3 +3648,8 @@ yargs@^16.2.0:
|
||||
string-width "^4.2.0"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
1
core/.gitignore
vendored
1
core/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
/lib.commonjs/
|
||||
/lib.esm/
|
||||
|
||||
14
core/eslint.config.js
Normal file
14
core/eslint.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -30,21 +30,31 @@
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./lib.commonjs/index.js",
|
||||
"import": "./lib.esm/index.js"
|
||||
},
|
||||
"./api/common": {
|
||||
"require": "./lib.commonjs/api/common.js",
|
||||
"import": "./lib.esm/api/common.js"
|
||||
},
|
||||
"./api/desktop": {
|
||||
"require": "./lib.commonjs/api/desktop.js",
|
||||
"import": "./lib.esm/api/desktop.js"
|
||||
},
|
||||
"./api/react-native": {
|
||||
"require": "./lib.commonjs/api/react-native.js",
|
||||
"import": "./lib.esm/api/react-native.js"
|
||||
},
|
||||
"./api/web": {
|
||||
"require": "./lib.commonjs/api/web.js",
|
||||
"import": "./lib.esm/api/web.js"
|
||||
},
|
||||
"./types": {
|
||||
"require": "./lib.commonjs/types.js",
|
||||
"import": "./lib.esm/types.js"
|
||||
},
|
||||
"./__tests": {
|
||||
"require": "./lib.commonjs/halo/tests.js",
|
||||
"import": "./lib.esm/halo/tests.js"
|
||||
}
|
||||
},
|
||||
@@ -52,7 +62,6 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"buffer": "^6.0.3",
|
||||
"elliptic": "^6.5.5",
|
||||
"ethers": "^6.13.1",
|
||||
@@ -64,10 +73,17 @@
|
||||
"websocket-as-promised": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.7.0",
|
||||
"@types/elliptic": "^6.4.18",
|
||||
"@types/pbkdf2": "^3.1.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"eslint": "^9.7.0",
|
||||
"process": "^0.11.10",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^7.16.0",
|
||||
"webpack": "^5.92.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
NFCBadTransportError,
|
||||
NFCBridgeConsentError
|
||||
} from "../halo/exceptions.js";
|
||||
import {BaseHaloAPI} from "../halo/cmd_exec.js";
|
||||
import {
|
||||
parsePublicKeys, convertSignature, recoverPublicKey, sigToDer,
|
||||
SECP256k1_ORDER, BJJ_ORDER
|
||||
@@ -24,6 +25,8 @@ import {
|
||||
* except the lib's index.js. The library's structure is subject to change in the next versions.
|
||||
*/
|
||||
export {
|
||||
BaseHaloAPI,
|
||||
|
||||
// exported utils
|
||||
parsePublicKeys as haloParsePublicKeys,
|
||||
convertSignature as haloConvertSignature,
|
||||
@@ -8,7 +8,7 @@ import {HaloGateway} from "../halo/gateway/requestor.js";
|
||||
import {HaloBridge} from "../halo/bridge.js";
|
||||
import {haloFindBridge} from "../web/web_utils.js";
|
||||
import {haloGateExecutorCreateWs, haloGateExecutorUserConfirm, haloGateExecutorSetHost} from "../halo/gateway/executor.js";
|
||||
import {execHaloCmdWeb, detectMethod} from "../drivers/web.js";
|
||||
import {HaloWebAPI, execHaloCmdWeb, detectMethod} from "../drivers/web.js";
|
||||
import {checkWebNFCPermission} from "../drivers/webnfc.js";
|
||||
|
||||
/**
|
||||
@@ -17,6 +17,7 @@ import {checkWebNFCPermission} from "../drivers/webnfc.js";
|
||||
*/
|
||||
export {
|
||||
// for web usage
|
||||
HaloWebAPI,
|
||||
execHaloCmdWeb,
|
||||
haloFindBridge,
|
||||
detectMethod as haloGetDefaultMethod,
|
||||
@@ -15,11 +15,13 @@ import {
|
||||
cmdGetGraffiti, cmdStoreGraffiti
|
||||
} from "../halo/commands.js";
|
||||
import {ERROR_CODES} from "../halo/errors.js";
|
||||
import {ExecHaloCmdOptions, HaloCommandObject, HaloResponseObject} from "../types.js";
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
|
||||
async function execHaloCmd(command, options) {
|
||||
async function execHaloCmd(command: HaloCommandObject, options: ExecHaloCmdOptions): Promise<HaloResponseObject> {
|
||||
command = Object.assign({}, command);
|
||||
|
||||
let commandName = command.name;
|
||||
const commandName = command.name;
|
||||
delete command['name'];
|
||||
|
||||
switch (commandName) {
|
||||
@@ -72,10 +74,10 @@ async function execHaloCmd(command, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function checkErrors(res) {
|
||||
function checkErrors(res: Buffer) {
|
||||
if (res.length === 2 && res[0] === 0xE1) {
|
||||
if (ERROR_CODES.hasOwnProperty(res[1])) {
|
||||
let err = ERROR_CODES[res[1]];
|
||||
if (Object.prototype.hasOwnProperty.call(ERROR_CODES, res[1])) {
|
||||
const err = ERROR_CODES[res[1]];
|
||||
throw new HaloTagError(err[0], "Tag responded with error: [" + err[0] + "] " + err[1]);
|
||||
} else {
|
||||
throw new HaloLogicError("Tag responded with unknown error: " + res.toString('hex'));
|
||||
@@ -7,10 +7,10 @@
|
||||
import {HaloTagError, NFCOperationError, NFCMethodNotSupported} from "../halo/exceptions.js";
|
||||
import {ERROR_CODES} from "../halo/errors.js";
|
||||
import {arr2hex, isWebDebugEnabled} from "../halo/util.js";
|
||||
import {ExecOptions, ExecReturnStruct} from "../types.js";
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
|
||||
const FLAG_USE_NEW_MODE = 0x00;
|
||||
|
||||
async function execCredential(request, options) {
|
||||
async function execCredential(request: Buffer, options: ExecOptions): Promise<ExecReturnStruct> {
|
||||
const webDebug = isWebDebugEnabled();
|
||||
|
||||
if (webDebug) {
|
||||
@@ -27,12 +27,12 @@ async function execCredential(request, options) {
|
||||
options.statusCallback = () => null;
|
||||
}
|
||||
|
||||
let challenge = new Uint8Array(32);
|
||||
const challenge = new Uint8Array(32);
|
||||
crypto.getRandomValues(challenge);
|
||||
|
||||
let encodedRequest = new Uint8Array([...request]);
|
||||
let ctrl = new AbortController();
|
||||
let u2fReq = {
|
||||
const encodedRequest = new Uint8Array([...request]);
|
||||
const ctrl = new AbortController();
|
||||
const u2fReq: CredentialRequestOptions = {
|
||||
"publicKey": {
|
||||
"allowCredentials": [
|
||||
{
|
||||
@@ -61,16 +61,16 @@ async function execCredential(request, options) {
|
||||
});
|
||||
|
||||
try {
|
||||
u2fRes = await navigator.credentials.get(u2fReq);
|
||||
u2fRes = await navigator.credentials.get(u2fReq) as PublicKeyCredential;
|
||||
} catch (e) {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execCredential() exception:', e);
|
||||
}
|
||||
|
||||
if (e.name === "NotSupportedError") {
|
||||
if ((<Error> e).name === "NotSupportedError") {
|
||||
throw new NFCMethodNotSupported("The call threw NotSupportedError. Please update your browser.");
|
||||
} else {
|
||||
throw new NFCOperationError("Failed to execute command. " + e.name + ": " + e.message);
|
||||
throw new NFCOperationError("Failed to execute command. " + (<Error> e).name + ": " + (<Error> e).message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,19 +84,20 @@ async function execCredential(request, options) {
|
||||
cancelScan: () => ctrl.abort(),
|
||||
});
|
||||
|
||||
let res = u2fRes.response.signature;
|
||||
let resBuf = new Uint8Array(res);
|
||||
const res = (u2fRes.response as AuthenticatorAssertionResponse).signature;
|
||||
const resBuf = new Uint8Array(res);
|
||||
|
||||
if (resBuf.length === 2 && resBuf[0] === 0xE1) {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execCredential() command fail:', arr2hex(resBuf));
|
||||
}
|
||||
|
||||
if (ERROR_CODES.hasOwnProperty(resBuf[1])) {
|
||||
let err = ERROR_CODES[resBuf[1]];
|
||||
if (Object.prototype.hasOwnProperty.call(ERROR_CODES, resBuf[1])) {
|
||||
const err = ERROR_CODES[resBuf[1]];
|
||||
throw new HaloTagError(err[0], err[1]);
|
||||
} else {
|
||||
throw new HaloTagError("Command returned an unknown error: " + arr2hex(resBuf));
|
||||
const errCode = arr2hex([resBuf[1]]);
|
||||
throw new HaloTagError("ERROR_CODE_" + errCode, "Command returned an unknown error: " + arr2hex(resBuf));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import {checkErrors, execHaloCmd} from "./common.js";
|
||||
import {HaloLogicError} from "../halo/exceptions.js";
|
||||
import {readNDEF} from "./read_ndef.js";
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
import {EmptyOptions, HaloCommandObject, RNNFCManager} from "../types.js";
|
||||
|
||||
async function execCoreCommandRN(nfcManager, command) {
|
||||
let selectCmd = [...Buffer.from("00A4040007481199130E9F0100", "hex")];
|
||||
let resSelect = Buffer.from(await nfcManager.isoDepHandler.transceive(selectCmd));
|
||||
async function execCoreCommandRN(nfcManager: RNNFCManager, command: Buffer) {
|
||||
const selectCmd = [...Buffer.from("00A4040007481199130E9F0100", "hex")];
|
||||
const resSelect = Buffer.from(await nfcManager.isoDepHandler.transceive(selectCmd));
|
||||
|
||||
if (resSelect.compare(Buffer.from([0x90, 0x00])) !== 0) {
|
||||
throw new HaloLogicError("Unable to initiate communication with the tag. Failed to select HaLo core.");
|
||||
@@ -24,7 +25,7 @@ async function execCoreCommandRN(nfcManager, command) {
|
||||
Buffer.from("00", "hex")
|
||||
]);
|
||||
|
||||
let res = Buffer.from(await nfcManager.isoDepHandler.transceive([...cmdBuf]));
|
||||
const res = Buffer.from(await nfcManager.isoDepHandler.transceive([...cmdBuf]));
|
||||
checkErrors(res);
|
||||
|
||||
return {
|
||||
@@ -33,16 +34,15 @@ async function execCoreCommandRN(nfcManager, command) {
|
||||
};
|
||||
}
|
||||
|
||||
async function execHaloCmdRN(nfcManager, command, options) {
|
||||
options = options ? Object.assign({}, options) : {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function execHaloCmdRN(nfcManager: RNNFCManager, command: HaloCommandObject, options?: EmptyOptions) {
|
||||
if (command.name === "read_ndef") {
|
||||
let wrappedTransceive = async (payload) => Buffer.from(await nfcManager.isoDepHandler.transceive([...payload]));
|
||||
const wrappedTransceive = async (payload: Buffer) => Buffer.from(await nfcManager.isoDepHandler.transceive([...payload]));
|
||||
return await readNDEF(wrappedTransceive);
|
||||
} else {
|
||||
return await execHaloCmd(command, {
|
||||
method: 'nfc-manager',
|
||||
exec: async (command) => await execCoreCommandRN(nfcManager, command),
|
||||
exec: async (command: Buffer) => await execCoreCommandRN(nfcManager, command),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,27 +7,33 @@
|
||||
import {readNDEF} from "./read_ndef.js";
|
||||
import {HaloLogicError, NFCOperationError} from "../halo/exceptions.js";
|
||||
import {execHaloCmd, checkErrors} from "./common.js";
|
||||
import {
|
||||
ExecHaloCmdOptions, ExecOptions,
|
||||
HaloCommandObject,
|
||||
Reader
|
||||
} from "../types.js";
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
|
||||
async function selectCore(reader) {
|
||||
async function selectCore(reader: Reader) {
|
||||
let res;
|
||||
|
||||
try {
|
||||
res = await reader.transmit(Buffer.from("00A4040007481199130E9F0100", "hex"), 255);
|
||||
} catch (e) {
|
||||
throw new NFCOperationError(e.message);
|
||||
throw new NFCOperationError((<Error> e).message);
|
||||
}
|
||||
|
||||
let statusCheck = res.slice(-2).compare(Buffer.from([0x91, 0x00])) !== 0;
|
||||
const statusCheck = res.slice(-2).compare(Buffer.from([0x91, 0x00])) !== 0;
|
||||
|
||||
if (!statusCheck) {
|
||||
throw new HaloLogicError("Unable to select HaLo core.");
|
||||
}
|
||||
}
|
||||
|
||||
async function selectU2FLayer(reader) {
|
||||
async function selectU2FLayer(reader: Reader) {
|
||||
try {
|
||||
let res = await reader.transmit(Buffer.from("00A4040008A0000006472F0001", "hex"), 255);
|
||||
let statusCheck = res.slice(-2).compare(Buffer.from([0x91, 0x00])) !== 0;
|
||||
const res = await reader.transmit(Buffer.from("00A4040008A0000006472F0001", "hex"), 255);
|
||||
const statusCheck = res.slice(-2).compare(Buffer.from([0x91, 0x00])) !== 0;
|
||||
|
||||
if (!statusCheck) {
|
||||
throw new HaloLogicError("Unable to select HaLo U2F layer.");
|
||||
@@ -38,27 +44,27 @@ async function selectU2FLayer(reader) {
|
||||
}
|
||||
}
|
||||
|
||||
async function transceive(reader, command, options) {
|
||||
async function transceive(reader: Reader, command: Buffer, options: ExecOptions) {
|
||||
options = options || {};
|
||||
|
||||
let start = performance.now();
|
||||
const start = performance.now();
|
||||
let res;
|
||||
|
||||
try {
|
||||
res = await reader.transmit(command, 255);
|
||||
} catch (e) {
|
||||
throw new NFCOperationError(e.message);
|
||||
throw new NFCOperationError((<Error> e).message);
|
||||
}
|
||||
|
||||
let end = performance.now();
|
||||
const end = performance.now();
|
||||
|
||||
if (process.env.DEBUG_PCSC === "1") {
|
||||
console.log('=> ' + command.toString('hex'));
|
||||
console.log('<= [' + Math.round(end - start) + ' ms] ' + res.toString('hex'));
|
||||
}
|
||||
|
||||
let check1 = res.slice(-2).compare(Buffer.from([0x90, 0x00])) !== 0;
|
||||
let check2 = res.slice(-2).compare(Buffer.from([0x91, 0x00])) !== 0;
|
||||
const check1 = res.slice(-2).compare(Buffer.from([0x90, 0x00])) !== 0;
|
||||
const check2 = res.slice(-2).compare(Buffer.from([0x91, 0x00])) !== 0;
|
||||
|
||||
if (!options.noCheck) {
|
||||
if (check1 && check2) {
|
||||
@@ -71,30 +77,30 @@ async function transceive(reader, command, options) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getVersion(reader) {
|
||||
let versionRes = await transceive(reader, Buffer.from("00510000010700", "hex"), {noCheck: true});
|
||||
async function getVersion(reader: Reader): Promise<string> {
|
||||
const versionRes = await transceive(reader, Buffer.from("00510000010700", "hex"), {noCheck: true});
|
||||
|
||||
if (versionRes.slice(-2).compare(Buffer.from([0x90, 0x00])) !== 0) {
|
||||
// GET_FV_VERSION command not supported, fallback to NDEF
|
||||
let wrappedTransceive = async (payload) => await transceive(reader, payload, {noCheck: true});
|
||||
let url = await readNDEF(wrappedTransceive);
|
||||
const wrappedTransceive = async (payload: Buffer) => await transceive(reader, payload, {noCheck: true});
|
||||
const url = await readNDEF(wrappedTransceive);
|
||||
|
||||
if (!url.qs.v) {
|
||||
return '01.C1.000001.00000000';
|
||||
} else if (url.qs.v.toLowerCase() === 'c2') {
|
||||
} else if ((url.qs.v as string).toLowerCase() === 'c2') {
|
||||
return '01.C2.000002.00000000';
|
||||
} else if (url.qs.v.toLowerCase() === 'c3') {
|
||||
} else if ((url.qs.v as string).toLowerCase() === 'c3') {
|
||||
return '01.C3.000003.00000000';
|
||||
} else {
|
||||
return url.qs.v;
|
||||
return (url.qs.v as string);
|
||||
}
|
||||
} else {
|
||||
return versionRes.slice(0, -2).toString();
|
||||
}
|
||||
}
|
||||
|
||||
async function getAddonVersion(reader) {
|
||||
let addonVersionRes = await transceive(reader, Buffer.from("00510000011000", "hex"), {noCheck: true});
|
||||
async function getAddonVersion(reader: Reader) {
|
||||
const addonVersionRes = await transceive(reader, Buffer.from("00510000011000", "hex"), {noCheck: true});
|
||||
|
||||
if (addonVersionRes.slice(-2).compare(Buffer.from([0x90, 0x00])) !== 0) {
|
||||
return null;
|
||||
@@ -103,8 +109,8 @@ async function getAddonVersion(reader) {
|
||||
return addonVersionRes.slice(0, -2).toString();
|
||||
}
|
||||
|
||||
function wrapCommandForU2F(command) {
|
||||
let payload = Buffer.concat([
|
||||
function wrapCommandForU2F(command: Buffer) {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from(Array(64)),
|
||||
Buffer.from([command.length]),
|
||||
command
|
||||
@@ -118,11 +124,11 @@ function wrapCommandForU2F(command) {
|
||||
]);
|
||||
}
|
||||
|
||||
function unwrapResultFromU2F(res) {
|
||||
function unwrapResultFromU2F(res: Buffer) {
|
||||
return res.slice(5);
|
||||
}
|
||||
|
||||
async function execCoreCommand(reader, command, options) {
|
||||
async function execCoreCommand(reader: Reader, command: Buffer, options?: ExecOptions) {
|
||||
options = Object.assign({}, options);
|
||||
|
||||
let cmdBuf;
|
||||
@@ -157,35 +163,36 @@ async function execCoreCommand(reader, command, options) {
|
||||
};
|
||||
}
|
||||
|
||||
function makeOptions(reader) {
|
||||
function makeOptions(reader: Reader): ExecHaloCmdOptions {
|
||||
return {
|
||||
method: 'pcsc',
|
||||
exec: async (command, options) => await execCoreCommand(reader, command, options),
|
||||
}
|
||||
}
|
||||
|
||||
async function execHaloCmdPCSC(command, reader) {
|
||||
async function execHaloCmdPCSC(command: HaloCommandObject, reader: Reader) {
|
||||
await selectCore(reader);
|
||||
let version = await getVersion(reader);
|
||||
const version = await getVersion(reader);
|
||||
|
||||
let [verMajor, verMinor, verSeq, verShortId] = version.split('.');
|
||||
verSeq = parseInt(verSeq, 10);
|
||||
const [verMajor, verMinor, verSeqStr, verShortId] = version.split('.');
|
||||
const verMajorNum = parseInt(verMajor, 10);
|
||||
const verSeq = parseInt(verSeqStr, 10);
|
||||
|
||||
if (verMajor > 1) {
|
||||
if (verMajorNum > 1) {
|
||||
throw new HaloLogicError("This version of CLI doesn't support major release version " + verMajor + ". Please update.");
|
||||
}
|
||||
|
||||
let options = makeOptions(reader);
|
||||
const options = makeOptions(reader);
|
||||
command = {...command};
|
||||
|
||||
if (command.name === "version") {
|
||||
// PCSC-specific version retrieval command
|
||||
let addonVersion = await getAddonVersion(reader);
|
||||
const addonVersion = await getAddonVersion(reader);
|
||||
let addonParts = null;
|
||||
|
||||
if (addonVersion) {
|
||||
let [verAMajor, verAMinor, verASeq, verAShortId] = addonVersion.split('.');
|
||||
verASeq = parseInt(verASeq, 10);
|
||||
const [verAMajor, verAMinor, verASeqStr, verAShortId] = addonVersion.split('.');
|
||||
const verASeq = parseInt(verASeqStr, 10);
|
||||
addonParts = {
|
||||
verAMajor,
|
||||
verAMinor,
|
||||
@@ -211,7 +218,7 @@ async function execHaloCmdPCSC(command, reader) {
|
||||
};
|
||||
} else if (command.name === "read_ndef") {
|
||||
// PCSC-specific NDEF reader command
|
||||
let wrappedTransceive = async (payload) => await transceive(reader, payload, {noCheck: true});
|
||||
const wrappedTransceive = async (payload: Buffer) => await transceive(reader, payload, {noCheck: true});
|
||||
return await readNDEF(wrappedTransceive);
|
||||
} else if (command.name === "full_gen_key") {
|
||||
await selectCore(reader);
|
||||
@@ -230,7 +237,7 @@ async function execHaloCmdPCSC(command, reader) {
|
||||
}, options);
|
||||
}
|
||||
|
||||
let subPkRes = await execHaloCmd({
|
||||
const subPkRes = await execHaloCmd({
|
||||
"name": "gen_key_finalize",
|
||||
"keyNo": command.keyNo,
|
||||
"password": command.password
|
||||
@@ -7,29 +7,30 @@
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
import queryString from 'query-string';
|
||||
import {HaloLogicError} from "../halo/exceptions.js";
|
||||
import {TransceiveFunc} from "../types.js";
|
||||
|
||||
async function readNDEF(transceive) {
|
||||
let resSelect = await transceive(Buffer.from("00A4040007D276000085010100", "hex"));
|
||||
async function readNDEF(transceive: TransceiveFunc) {
|
||||
const resSelect = await transceive(Buffer.from("00A4040007D276000085010100", "hex"));
|
||||
|
||||
if (resSelect.compare(Buffer.from([0x90, 0x00])) !== 0) {
|
||||
throw new HaloLogicError("Unable to read NDEF, failed to select NDEF application.");
|
||||
}
|
||||
|
||||
// assume that NDEF file is 0xE104
|
||||
let resSelectFile = await transceive(Buffer.from("00A4000C02E10400", "hex"));
|
||||
const resSelectFile = await transceive(Buffer.from("00A4000C02E10400", "hex"));
|
||||
|
||||
if (resSelectFile.compare(Buffer.from([0x90, 0x00])) !== 0) {
|
||||
throw new HaloLogicError("Unable to read NDEF, failed to select NDEF file.");
|
||||
}
|
||||
|
||||
let readCmdBuf = Buffer.from("00B0000002", "hex");
|
||||
let resReadLength = await transceive(readCmdBuf);
|
||||
const readCmdBuf = Buffer.from("00B0000002", "hex");
|
||||
const resReadLength = await transceive(readCmdBuf);
|
||||
|
||||
if (resReadLength.slice(-2).compare(Buffer.from([0x90, 0x00])) !== 0) {
|
||||
throw new HaloLogicError("Unable to read NDEF, failed to read length bytes.");
|
||||
}
|
||||
|
||||
let ndefLen = resReadLength.readUInt16BE(0) + 2;
|
||||
const ndefLen = resReadLength.readUInt16BE(0) + 2;
|
||||
let tmpNdefLen = ndefLen;
|
||||
let offset = 0;
|
||||
|
||||
@@ -42,7 +43,7 @@ async function readNDEF(transceive) {
|
||||
// (the same APDU is just working fine lol)
|
||||
readCmdBuf[4] = 0x30;
|
||||
|
||||
let resReadNDEF = await transceive(readCmdBuf);
|
||||
const resReadNDEF = await transceive(readCmdBuf);
|
||||
|
||||
if (resReadNDEF.slice(-2).compare(Buffer.from([0x90, 0x00])) !== 0) {
|
||||
throw new HaloLogicError("Unable to read NDEF, failed to read NDEF file.");
|
||||
@@ -67,15 +68,15 @@ async function readNDEF(transceive) {
|
||||
}
|
||||
|
||||
fullBuf = fullBuf.slice(4);
|
||||
let lengthData = fullBuf.readUInt16BE(0);
|
||||
const lengthData = fullBuf.readUInt16BE(0);
|
||||
|
||||
if (fullBuf[2] !== 0x54 && fullBuf[2] !== 0x55) {
|
||||
throw new HaloLogicError("Failed to parse NDEF, unsupported record type.");
|
||||
}
|
||||
|
||||
let skipLen = fullBuf[3];
|
||||
const skipLen = fullBuf[3];
|
||||
fullBuf = fullBuf.slice(4 + skipLen, 4 + skipLen + lengthData);
|
||||
let parsed = queryString.parseUrl(fullBuf.toString());
|
||||
const parsed = queryString.parseUrl(fullBuf.toString());
|
||||
|
||||
return {
|
||||
url: parsed.url,
|
||||
@@ -4,10 +4,19 @@ import {execWebNFC} from "./webnfc.js";
|
||||
import {execHaloCmd} from "./common.js";
|
||||
import {emulatedPromptStatusCallback} from "../web/soft_prompt.js";
|
||||
import {isWebDebugEnabled} from "../halo/util.js";
|
||||
import {
|
||||
ExecHaloCmdOptions,
|
||||
ExecHaloCmdWebOptions,
|
||||
HaloCommandObject, HaloResponseObject,
|
||||
HaloWebMethod,
|
||||
StatusCallbackDetails
|
||||
} from "../types.js";
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
import {BaseHaloAPI} from "../halo/cmd_exec.js";
|
||||
|
||||
let isCallRunning = null;
|
||||
let isCallRunning = false;
|
||||
|
||||
function makeDefault(curValue, defaultValue) {
|
||||
function makeDefault<Type>(curValue: Type | null | undefined, defaultValue: Type): Type {
|
||||
if (typeof curValue === "undefined") {
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -23,9 +32,11 @@ function makeDefault(curValue, defaultValue) {
|
||||
* Detect the best command execution method for the current device.
|
||||
* @returns {string} Either "credential" or "webnfc".
|
||||
*/
|
||||
function detectMethod() {
|
||||
export function detectMethod() {
|
||||
try {
|
||||
new NDEFReader();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
new window.NDEFReader();
|
||||
} catch (e) {
|
||||
// WebNFC not supported
|
||||
return "credential";
|
||||
@@ -34,7 +45,7 @@ function detectMethod() {
|
||||
return "webnfc";
|
||||
}
|
||||
|
||||
function defaultStatusCallback(cause, statusObj) {
|
||||
function defaultStatusCallback(cause: string, statusObj: StatusCallbackDetails) {
|
||||
if (statusObj.execMethod === "webnfc") {
|
||||
return emulatedPromptStatusCallback(cause, statusObj);
|
||||
}
|
||||
@@ -50,7 +61,7 @@ function defaultStatusCallback(cause, statusObj) {
|
||||
* @param options Additional options for the command executor.
|
||||
* @returns {Promise<*>} Command execution result.
|
||||
*/
|
||||
async function execHaloCmdWeb(command, options) {
|
||||
export async function execHaloCmdWeb(command: HaloCommandObject, options?: ExecHaloCmdWebOptions) {
|
||||
if (options && !options.noDebounce && isCallRunning) {
|
||||
throw new NFCAbortedError("The operation was debounced.");
|
||||
}
|
||||
@@ -58,8 +69,8 @@ async function execHaloCmdWeb(command, options) {
|
||||
isCallRunning = true;
|
||||
|
||||
options = options ? Object.assign({}, options) : {};
|
||||
options.method = makeDefault(options.method, detectMethod());
|
||||
options.noDebounce = makeDefault(options.noDebounce, false);
|
||||
options.method = makeDefault<HaloWebMethod>(options.method, detectMethod());
|
||||
options.noDebounce = makeDefault<boolean>(options.noDebounce, false);
|
||||
options.statusCallback = makeDefault(options.statusCallback, defaultStatusCallback);
|
||||
|
||||
command = command ? Object.assign({}, command) : {};
|
||||
@@ -70,19 +81,19 @@ async function execHaloCmdWeb(command, options) {
|
||||
}
|
||||
|
||||
try {
|
||||
let cmdOpts = {};
|
||||
let cmdOpts: ExecHaloCmdOptions;
|
||||
|
||||
if (options.method === "credential") {
|
||||
cmdOpts = {
|
||||
method: "credential",
|
||||
exec: async (command) => await execCredential(command, {
|
||||
exec: async (command: Buffer) => await execCredential(command, {
|
||||
statusCallback: options.statusCallback
|
||||
})
|
||||
};
|
||||
} else if (options.method === "webnfc") {
|
||||
cmdOpts = {
|
||||
method: "webnfc",
|
||||
exec: async (command) => await execWebNFC(command, {
|
||||
exec: async (command: Buffer) => await execWebNFC(command, {
|
||||
statusCallback: options.statusCallback
|
||||
})
|
||||
};
|
||||
@@ -104,7 +115,16 @@ async function execHaloCmdWeb(command, options) {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
execHaloCmdWeb,
|
||||
detectMethod
|
||||
};
|
||||
export class HaloWebAPI extends BaseHaloAPI {
|
||||
private readonly options: ExecHaloCmdWebOptions | undefined;
|
||||
|
||||
constructor(options?: ExecHaloCmdWebOptions) {
|
||||
super();
|
||||
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
executeCommand(args: HaloCommandObject): Promise<HaloResponseObject> {
|
||||
return execHaloCmdWeb(args, this.options);
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,12 @@ import {
|
||||
NFCAbortedError
|
||||
} from "../halo/exceptions.js";
|
||||
import {arr2hex, hex2arr, isWebDebugEnabled} from "../halo/util.js";
|
||||
import {ExecOptions, ExecReturnStruct} from "../types.js";
|
||||
import type {NDEFReader} from "../types_webnfc.js";
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
|
||||
let ndef = null;
|
||||
let ctrl = null;
|
||||
let ndef: NDEFReader | null = null;
|
||||
let ctrl: AbortController | null = null;
|
||||
let blurEventInstalled = false;
|
||||
|
||||
function detectWindowMinimized() {
|
||||
@@ -39,12 +42,14 @@ async function checkWebNFCPermission() {
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const ndef = new NDEFReader();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
const ndef = new window.NDEFReader() as NDEFReader;
|
||||
await ndef.scan({ signal: controller.signal });
|
||||
controller.abort();
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e.name === "NotAllowedError") {
|
||||
if ((<Error> e).name === "NotAllowedError") {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,7 +57,7 @@ async function checkWebNFCPermission() {
|
||||
}
|
||||
}
|
||||
|
||||
async function execWebNFC(request, options) {
|
||||
async function execWebNFC(request: Buffer, options: ExecOptions): Promise<ExecReturnStruct> {
|
||||
const webDebug = isWebDebugEnabled();
|
||||
|
||||
if (webDebug) {
|
||||
@@ -69,7 +74,7 @@ async function execWebNFC(request, options) {
|
||||
console.log('[libhalo] execWebNFC() internal error checking WebNFC permission:', e);
|
||||
}
|
||||
|
||||
throw new NFCMethodNotSupported("Internal error when checking WebNFC permission: " + e.toString());
|
||||
throw new NFCMethodNotSupported("Internal error when checking WebNFC permission: " + (<Error> e).toString());
|
||||
}
|
||||
|
||||
if (!isWebNFCGranted) {
|
||||
@@ -88,7 +93,9 @@ async function execWebNFC(request, options) {
|
||||
|
||||
if (!ndef) {
|
||||
try {
|
||||
ndef = new NDEFReader();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
ndef = new window.NDEFReader() as NDEFReader;
|
||||
} catch (e) {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execWebNFC() failed createing NDEFReader');
|
||||
@@ -121,13 +128,13 @@ async function execWebNFC(request, options) {
|
||||
options.statusCallback("init", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-write",
|
||||
cancelScan: () => ctrl.abort(),
|
||||
cancelScan: () => ctrl && ctrl.abort(),
|
||||
});
|
||||
} else if (writeStatus === "nfc-write-error") {
|
||||
options.statusCallback("retry", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-write-error",
|
||||
cancelScan: () => ctrl.abort(),
|
||||
cancelScan: () => ctrl && ctrl.abort(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,9 +153,9 @@ async function execWebNFC(request, options) {
|
||||
console.log('[libhalo] execWebNFC() write exception:', e);
|
||||
}
|
||||
|
||||
if (e.name === "NotAllowedError") {
|
||||
if ((<Error> e).name === "NotAllowedError") {
|
||||
throw new NFCPermissionRequestDenied("NFC permission request denied by the user.");
|
||||
} else if (e.name === "AbortError") {
|
||||
} else if ((<Error> e).name === "AbortError") {
|
||||
throw new NFCAbortedError("Operation restarted by the user or webpage minimized (during write).");
|
||||
} else {
|
||||
writeStatus = "nfc-write-error";
|
||||
@@ -165,11 +172,11 @@ async function execWebNFC(request, options) {
|
||||
options.statusCallback("again", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-read",
|
||||
cancelScan: () => ctrl.abort(),
|
||||
cancelScan: () => ctrl && ctrl.abort(),
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ctrl.signal.addEventListener('abort', () => {
|
||||
ctrl!.signal.addEventListener('abort', () => {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execWebNFC() operation aborted during read');
|
||||
}
|
||||
@@ -177,7 +184,7 @@ async function execWebNFC(request, options) {
|
||||
reject(new NFCAbortedError("Operation restarted by the user or webpage minimized (during read)."));
|
||||
});
|
||||
|
||||
if (ctrl.signal.aborted) {
|
||||
if (ctrl!.signal.aborted) {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execWebNFC() operation aborted during read');
|
||||
}
|
||||
@@ -185,65 +192,71 @@ async function execWebNFC(request, options) {
|
||||
reject(new NFCAbortedError("Operation restarted by the user or webpage minimized (during read)."));
|
||||
}
|
||||
|
||||
ndef.onreadingerror = (event) => {
|
||||
ndef!.onreadingerror = (event) => {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execWebNFC() read error');
|
||||
}
|
||||
|
||||
options.statusCallback("retry", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-read-error",
|
||||
cancelScan: () => ctrl.abort(),
|
||||
});
|
||||
if (options.statusCallback) {
|
||||
options.statusCallback("retry", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-read-error",
|
||||
cancelScan: () => ctrl && ctrl.abort(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ndef.onreading = (event) => {
|
||||
ndef!.onreading = (event) => {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execWebNFC() read event received, parsing');
|
||||
}
|
||||
|
||||
try {
|
||||
let out = {};
|
||||
let decoded = new TextDecoder("utf-8").decode(event.message.records[0].data);
|
||||
let url = new URL(decoded);
|
||||
const out: Record<string, string> = {};
|
||||
const decoded = new TextDecoder("utf-8").decode(event.message.records[0].data);
|
||||
const url = new URL(decoded);
|
||||
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execWebNFC() read result url:', url);
|
||||
}
|
||||
|
||||
for (let k of url.searchParams.keys()) {
|
||||
out[k] = url.searchParams.get(k);
|
||||
for (const k of url.searchParams.keys()) {
|
||||
out[k] = url.searchParams.get(k)!;
|
||||
}
|
||||
|
||||
let resBuf = hex2arr(out.res);
|
||||
const resBuf = hex2arr(out.res);
|
||||
|
||||
if (resBuf[0] === 0xE1) {
|
||||
if (webDebug) {
|
||||
console.log('[libhalo] execWebNFC() command fail:', arr2hex(resBuf));
|
||||
}
|
||||
|
||||
if (ERROR_CODES.hasOwnProperty(resBuf[1])) {
|
||||
let err = ERROR_CODES[resBuf[1]];
|
||||
ndef.onreading = () => null;
|
||||
ndef.onreadingerror = () => null;
|
||||
if (Object.prototype.hasOwnProperty.call(ERROR_CODES, resBuf[1])) {
|
||||
const err = ERROR_CODES[resBuf[1]];
|
||||
ndef!.onreading = () => null;
|
||||
ndef!.onreadingerror = () => null;
|
||||
reject(new HaloTagError(err[0], err[1]));
|
||||
} else {
|
||||
ndef.onreading = () => null;
|
||||
ndef.onreadingerror = () => null;
|
||||
reject(new HaloTagError("Command returned an unknown error: " + arr2hex(resBuf)));
|
||||
ndef!.onreading = () => null;
|
||||
ndef!.onreadingerror = () => null;
|
||||
|
||||
const errCode = arr2hex([resBuf[1]]);
|
||||
reject(new HaloTagError("ERROR_CODE_" + errCode, "Command returned an unknown error: " + arr2hex(resBuf)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
options.statusCallback("scanned", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-success",
|
||||
cancelScan: () => ctrl.abort(),
|
||||
});
|
||||
if (options.statusCallback) {
|
||||
options.statusCallback("scanned", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-success",
|
||||
cancelScan: () => ctrl && ctrl.abort(),
|
||||
});
|
||||
}
|
||||
|
||||
ndef.onreading = () => null;
|
||||
ndef.onreadingerror = () => null;
|
||||
ndef!.onreading = () => null;
|
||||
ndef!.onreadingerror = () => null;
|
||||
|
||||
delete out['res'];
|
||||
|
||||
@@ -262,11 +275,13 @@ async function execWebNFC(request, options) {
|
||||
console.log('[libhalo] execWebNFC() parse error:', e);
|
||||
}
|
||||
|
||||
options.statusCallback("retry", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-parse-error",
|
||||
cancelScan: () => ctrl.abort(),
|
||||
});
|
||||
if (options.statusCallback) {
|
||||
options.statusCallback("retry", {
|
||||
execMethod: "webnfc",
|
||||
execStep: "nfc-parse-error",
|
||||
cancelScan: () => ctrl && ctrl.abort(),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,40 +1,55 @@
|
||||
import queryString from "query-string";
|
||||
import WebSocketAsPromised from "websocket-as-promised";
|
||||
import {HaloLogicError, HaloTagError, NFCOperationError, NFCBadTransportError, NFCAbortedError, NFCBridgeConsentError} from "./exceptions.js";
|
||||
import {
|
||||
HaloLogicError,
|
||||
HaloTagError,
|
||||
NFCOperationError,
|
||||
NFCBadTransportError,
|
||||
NFCAbortedError,
|
||||
NFCBridgeConsentError,
|
||||
NFCBridgeUnexpectedError
|
||||
} from "./exceptions.js";
|
||||
import {haloFindBridge} from "../web/web_utils.js";
|
||||
import {webDebug} from "./util.js";
|
||||
import {BridgeEvent, BridgeHandleAdded, BridgeOptions, HaloCommandObject} from "../types.js";
|
||||
|
||||
class HaloBridge {
|
||||
constructor(options) {
|
||||
private isRunning: boolean;
|
||||
private lastHandle: string | null;
|
||||
private url: string | null;
|
||||
private readonly createWebSocket: (url: string) => WebSocket;
|
||||
private ws: WebSocketAsPromised | null;
|
||||
|
||||
constructor(options: BridgeOptions) {
|
||||
options = Object.assign({}, options);
|
||||
|
||||
this.isRunning = false;
|
||||
this.lastCommand = null;
|
||||
this.lastHandle = null;
|
||||
this.url = null;
|
||||
this.ws = null;
|
||||
|
||||
this.createWebSocket = options.createWebSocket
|
||||
? options.createWebSocket
|
||||
: (url) => new WebSocket(url);
|
||||
: (url: string) => new WebSocket(url);
|
||||
}
|
||||
|
||||
waitForWelcomePacket() {
|
||||
waitForWelcomePacket(): Promise<Record<string, unknown>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let welcomeWaitTimeout = setTimeout(() => {
|
||||
const welcomeWaitTimeout = setTimeout(() => {
|
||||
reject(new NFCBadTransportError("Server doesn't send ws_connected packet for 6 seconds after accepting the connection."));
|
||||
}, 6000);
|
||||
|
||||
this.ws.onClose.addListener((event) => {
|
||||
this.ws!.onClose.addListener((event) => {
|
||||
if (event.code === 4002) {
|
||||
// no user consent
|
||||
reject(new NFCBridgeConsentError());
|
||||
reject(new NFCBridgeConsentError("No user consent for this origin."));
|
||||
} else {
|
||||
reject(new NFCBadTransportError("WebSocket closed when waiting for ws_connected packet. " +
|
||||
"Reason: [" + event.code + "] " + event.reason));
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.onUnpackedMessage.addListener(data => {
|
||||
this.ws!.onUnpackedMessage.addListener(data => {
|
||||
if (data.event === "ws_connected") {
|
||||
clearTimeout(welcomeWaitTimeout);
|
||||
resolve(data);
|
||||
@@ -46,10 +61,10 @@ class HaloBridge {
|
||||
async connect() {
|
||||
this.url = await haloFindBridge({createWebSocket: this.createWebSocket});
|
||||
|
||||
this.ws = new WebSocketAsPromised(this.url, {
|
||||
this.ws = new WebSocketAsPromised(this.url!, {
|
||||
createWebSocket: url => this.createWebSocket(url),
|
||||
packMessage: data => JSON.stringify(data),
|
||||
unpackMessage: data => JSON.parse(data),
|
||||
unpackMessage: data => JSON.parse(data as string),
|
||||
attachRequestId: (data, requestId) => Object.assign({uid: requestId}, data),
|
||||
extractRequestId: data => data && data.uid
|
||||
});
|
||||
@@ -62,17 +77,17 @@ class HaloBridge {
|
||||
}
|
||||
});
|
||||
|
||||
let waitPromise = this.waitForWelcomePacket();
|
||||
const waitPromise = this.waitForWelcomePacket();
|
||||
await this.ws.open();
|
||||
let welcomeMsg = await waitPromise;
|
||||
let serverVersion = welcomeMsg.serverVersion;
|
||||
const welcomeMsg = await waitPromise;
|
||||
const serverVersion = welcomeMsg.serverVersion;
|
||||
|
||||
return {
|
||||
serverVersion: serverVersion
|
||||
};
|
||||
}
|
||||
|
||||
getConsentURL(websiteURL, options) {
|
||||
getConsentURL(websiteURL: string, options: unknown) {
|
||||
if (!this.url) {
|
||||
return null;
|
||||
}
|
||||
@@ -80,6 +95,8 @@ class HaloBridge {
|
||||
return this.url
|
||||
.replace('ws://', 'http://')
|
||||
.replace('wss://', 'https://')
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
.replace('/ws', '/consent?' + queryString.stringify({'website': websiteURL, ...options}));
|
||||
}
|
||||
|
||||
@@ -90,7 +107,7 @@ class HaloBridge {
|
||||
}
|
||||
|
||||
async waitForHandle() {
|
||||
if (!this.ws.isOpened) {
|
||||
if (this.ws === null || !this.ws.isOpened) {
|
||||
throw new NFCBadTransportError("Bridge is not open.");
|
||||
}
|
||||
|
||||
@@ -99,30 +116,36 @@ class HaloBridge {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const msgListener = data => {
|
||||
const msgListener = (data: BridgeEvent) => {
|
||||
if (data.event === "handle_added") {
|
||||
this.lastHandle = data.data.handle;
|
||||
this.lastHandle = (data as BridgeHandleAdded).data.handle;
|
||||
|
||||
this.ws.onUnpackedMessage.removeListener(msgListener);
|
||||
this.ws.onClose.removeListener(closeListener);
|
||||
if (this.ws) {
|
||||
this.ws.onUnpackedMessage.removeListener(msgListener);
|
||||
this.ws.onClose.removeListener(closeListener);
|
||||
}
|
||||
|
||||
resolve(this.lastHandle);
|
||||
}
|
||||
};
|
||||
|
||||
const closeListener = data => {
|
||||
this.ws.onUnpackedMessage.removeListener(msgListener);
|
||||
this.ws.onClose.removeListener(closeListener);
|
||||
const closeListener = (data: never) => {
|
||||
if (this.ws) {
|
||||
this.ws.onUnpackedMessage.removeListener(msgListener);
|
||||
this.ws.onClose.removeListener(closeListener);
|
||||
}
|
||||
|
||||
reject(new NFCBadTransportError("Bridge server has disconnected."));
|
||||
};
|
||||
|
||||
this.ws.onUnpackedMessage.addListener(msgListener);
|
||||
this.ws.onClose.addListener(closeListener);
|
||||
if (this.ws) {
|
||||
this.ws.onUnpackedMessage.addListener(msgListener);
|
||||
this.ws.onClose.addListener(closeListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async execHaloCmd(command) {
|
||||
async execHaloCmd(command: HaloCommandObject) {
|
||||
webDebug('[halo-bridge] called execHaloCmd()', command);
|
||||
|
||||
if (this.isRunning) {
|
||||
@@ -130,11 +153,15 @@ class HaloBridge {
|
||||
throw new NFCAbortedError("Can not make multiple calls to execHaloCmd() in parallel.");
|
||||
}
|
||||
|
||||
if (!this.ws) {
|
||||
throw new NFCBadTransportError("Bridge was not opened.");
|
||||
}
|
||||
|
||||
this.isRunning = true;
|
||||
|
||||
try {
|
||||
webDebug('[halo-bridge] waiting for card tap');
|
||||
let handle = await this.waitForHandle();
|
||||
const handle = await this.waitForHandle();
|
||||
|
||||
webDebug('[halo-bridge] sending request to execute command', handle);
|
||||
let res;
|
||||
@@ -147,7 +174,7 @@ class HaloBridge {
|
||||
});
|
||||
} catch (e) {
|
||||
webDebug('[halo-bridge] exception when trying to sendRequest', e);
|
||||
throw new NFCBadTransportError('Failed to send request: ' + e.toString());
|
||||
throw new NFCBadTransportError('Failed to send request: ' + (<Error> e).toString());
|
||||
}
|
||||
|
||||
if (res.event === "exec_success") {
|
||||
@@ -159,23 +186,22 @@ class HaloBridge {
|
||||
|
||||
switch (res.data.exception.kind) {
|
||||
case 'HaloLogicError':
|
||||
e = new HaloLogicError(res.data.exception.message);
|
||||
e = new HaloLogicError(res.data.exception.message, res.data.exception.stack);
|
||||
break;
|
||||
case 'HaloTagError':
|
||||
e = new HaloTagError(res.data.exception.name, res.data.exception.message);
|
||||
e = new HaloTagError(res.data.exception.name, res.data.exception.message, res.data.exception.stack);
|
||||
break;
|
||||
case 'NFCOperationError':
|
||||
// allow some time for the PC/SC reader to re-poll for the card
|
||||
await new Promise((resolve, reject) => setTimeout(resolve, 500));
|
||||
e = new NFCOperationError(res.data.exception.message);
|
||||
e = new NFCOperationError(res.data.exception.message, res.data.exception.stack);
|
||||
break;
|
||||
default:
|
||||
e = new Error("Unexpected exception occurred while executing the command. " +
|
||||
res.data.exception.name + ": " + res.data.exception.message);
|
||||
e = new NFCBridgeUnexpectedError("Unexpected exception occurred while executing the command. " +
|
||||
res.data.exception.name + ": " + res.data.exception.message, res.data.exception.stack);
|
||||
break;
|
||||
}
|
||||
|
||||
e.stackOnExecutor = res.data.exception.stack;
|
||||
webDebug('[halo-bridge] throwing exception as the call result', e);
|
||||
throw e;
|
||||
} else {
|
||||
130
core/src.ts/halo/cmd_exec.ts
Normal file
130
core/src.ts/halo/cmd_exec.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* LibHaLo - Programmatically interact with HaLo tags from the web browser, mobile application or the desktop.
|
||||
* Copyright by Arx Research, Inc., a Delaware corporation
|
||||
* License: MIT
|
||||
*/
|
||||
import {
|
||||
HaloCmdExportKey,
|
||||
HaloCmdGetDataStruct,
|
||||
HaloCmdGetGraffiti, HaloCmdGetKeyInfo,
|
||||
HaloCmdGetPkeys,
|
||||
HaloCmdGetTransportPK,
|
||||
HaloCmdImportKey,
|
||||
HaloCmdImportKeyInit,
|
||||
HaloCmdLoadTransportPK, HaloCmdReplacePassword, HaloCmdSetPassword, HaloCmdSetURLSubdomain,
|
||||
HaloCmdSign,
|
||||
HaloCmdSignChallenge,
|
||||
HaloCmdSignRandom,
|
||||
HaloCmdStoreGraffiti, HaloCmdUnsetPassword,
|
||||
HaloCmdWriteLatch,
|
||||
HaloResExportKey,
|
||||
HaloResGetGraffiti, HaloResGetKeyInfo,
|
||||
HaloResGetPkeys, HaloResGetTransportPK,
|
||||
HaloResImportKey,
|
||||
HaloResImportKeyInit,
|
||||
HaloResLoadTransportPK, HaloResReplacePassword, HaloResSetPassword, HaloResSetURLSubdomain,
|
||||
HaloResSign,
|
||||
HaloResSignChallenge,
|
||||
HaloResSignRandom, HaloResUnsetPassword,
|
||||
HaloResWriteLatch
|
||||
} from "./command_types.js";
|
||||
import {
|
||||
HaloCmdCFGNDEF,
|
||||
HaloCmdGenKey, HaloCmdGenKeyConfirm, HaloCmdGenKeyFinalize,
|
||||
HaloCommandObject,
|
||||
HaloResCFGNDEF,
|
||||
HaloResGenKey, HaloResGenKeyConfirm, HaloResGenKeyFinalize,
|
||||
HaloResponseObject
|
||||
} from "../types.js";
|
||||
|
||||
export abstract class BaseHaloAPI {
|
||||
abstract executeCommand(args: HaloCommandObject): Promise<HaloResponseObject>;
|
||||
|
||||
getPkeys(args: HaloCmdGetPkeys): Promise<HaloResGetPkeys> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
sign(args: HaloCmdSign): Promise<HaloResSign> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
writeLatch(args: HaloCmdWriteLatch): Promise<HaloResWriteLatch> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
signRandom(args: HaloCmdSignRandom): Promise<HaloResSignRandom> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
signChallenge(args: HaloCmdSignChallenge): Promise<HaloResSignChallenge> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
cfgNDEF(args: HaloCmdCFGNDEF): Promise<HaloResCFGNDEF> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
genKey(args: HaloCmdGenKey): Promise<HaloResGenKey> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
genKeyConfirm(args: HaloCmdGenKeyConfirm): Promise<HaloResGenKeyConfirm> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
genKeyFinalize(args: HaloCmdGenKeyFinalize): Promise<HaloResGenKeyFinalize> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
setURLSubdomain(args: HaloCmdSetURLSubdomain): Promise<HaloResSetURLSubdomain> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
getKeyInfo(args: HaloCmdGetKeyInfo): Promise<HaloResGetKeyInfo> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
setPassword(args: HaloCmdSetPassword): Promise<HaloResSetPassword> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
unsetPassword(args: HaloCmdUnsetPassword): Promise<HaloResUnsetPassword> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
replacePassword(args: HaloCmdReplacePassword): Promise<HaloResReplacePassword> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
getTransportPK(args: HaloCmdGetTransportPK): Promise<HaloResGetTransportPK> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
loadTransportPK(args: HaloCmdLoadTransportPK): Promise<HaloResLoadTransportPK> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
exportKey(args: HaloCmdExportKey): Promise<HaloResExportKey> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
importKeyInit(args: HaloCmdImportKeyInit): Promise<HaloResImportKeyInit> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
importKey(args: HaloCmdImportKey): Promise<HaloResImportKey> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
getDataStruct(args: HaloCmdGetDataStruct): Promise<HaloCmdGetDataStruct> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
getGraffiti(args: HaloCmdGetGraffiti): Promise<HaloResGetGraffiti> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
|
||||
storeGraffiti(args: HaloCmdStoreGraffiti): Promise<HaloCmdStoreGraffiti> {
|
||||
return this.executeCommand(args);
|
||||
}
|
||||
}
|
||||
220
core/src.ts/halo/command_types.ts
Normal file
220
core/src.ts/halo/command_types.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import {ASCIIString, HexString, KeyFlags, KeySlotNo, KeyState, PublicKeyList} from "../types.js";
|
||||
import {TypedDataDomain, TypedDataField} from "ethers";
|
||||
|
||||
export interface HaloCmdGetPkeys {}
|
||||
|
||||
export interface HaloResGetPkeys {
|
||||
publicKeys: PublicKeyList
|
||||
compressedPublicKeys: PublicKeyList
|
||||
etherAddresses: PublicKeyList
|
||||
}
|
||||
|
||||
|
||||
export interface BaseHaloCmdSign {
|
||||
keyNo: KeySlotNo
|
||||
format?: "text" | "hex"
|
||||
password?: ASCIIString
|
||||
|
||||
legacySignCommand?: boolean
|
||||
}
|
||||
|
||||
export interface HaloCmdSignVariant1 extends BaseHaloCmdSign {
|
||||
digest: HexString
|
||||
message?: undefined
|
||||
typedData?: undefined
|
||||
}
|
||||
|
||||
export interface HaloCmdSignVariant2 extends BaseHaloCmdSign {
|
||||
digest?: undefined
|
||||
message: HexString
|
||||
typedData?: undefined
|
||||
}
|
||||
|
||||
export interface HaloCmdSignVariant3 extends BaseHaloCmdSign {
|
||||
digest?: undefined
|
||||
message?: undefined
|
||||
typedData: {
|
||||
domain: TypedDataDomain
|
||||
types: Record<string, Array<TypedDataField>>
|
||||
value: Record<string, never>
|
||||
}
|
||||
}
|
||||
|
||||
export type HaloCmdSign = HaloCmdSignVariant1 | HaloCmdSignVariant2 | HaloCmdSignVariant3;
|
||||
|
||||
export interface HaloResInputObj {
|
||||
keyNo: KeySlotNo
|
||||
digest: HexString
|
||||
message?: HexString
|
||||
typedData?: {
|
||||
domain: TypedDataDomain
|
||||
types: Record<string, Array<TypedDataField>>
|
||||
value: Record<string, never>
|
||||
primaryType: string
|
||||
domainHash: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface HaloResSign {
|
||||
input: HaloResInputObj
|
||||
signature: {
|
||||
der: HexString
|
||||
raw?: {
|
||||
r: string
|
||||
s: string
|
||||
v: number
|
||||
}
|
||||
ether?: string
|
||||
}
|
||||
publicKey?: string
|
||||
etherAddress?: string
|
||||
}
|
||||
|
||||
export interface HaloCmdGetDataStruct {
|
||||
spec: string
|
||||
}
|
||||
|
||||
export interface HaloResGetDataStruct {
|
||||
isPartial: boolean
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface HaloCmdGetGraffiti {
|
||||
slotNo: number
|
||||
}
|
||||
|
||||
export interface HaloResGetGraffiti {
|
||||
data: ASCIIString
|
||||
}
|
||||
|
||||
export interface HaloCmdStoreGraffiti {
|
||||
slotNo: number,
|
||||
data: ASCIIString
|
||||
}
|
||||
|
||||
export interface HaloResStoreGraffiti {
|
||||
status: "ok"
|
||||
}
|
||||
|
||||
export interface HaloCmdWriteLatch {
|
||||
data: HexString
|
||||
latchNo: number
|
||||
}
|
||||
|
||||
export interface HaloResWriteLatch {
|
||||
status: "ok"
|
||||
}
|
||||
|
||||
export interface HaloCmdSignRandom {
|
||||
keyNo: KeySlotNo
|
||||
}
|
||||
|
||||
export interface HaloResSignRandom {
|
||||
counter: number
|
||||
payload: HexString
|
||||
signature: HexString
|
||||
publicKey: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdSignChallenge {
|
||||
keyNo: KeySlotNo
|
||||
challenge: HexString
|
||||
}
|
||||
|
||||
export interface HaloResSignChallenge {
|
||||
signature: HexString
|
||||
publicKey: HexString
|
||||
attestSig: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdImportKeyInit {
|
||||
keyNo: KeySlotNo
|
||||
data: HexString
|
||||
}
|
||||
|
||||
export interface HaloResImportKeyInit {
|
||||
status: "ok"
|
||||
}
|
||||
|
||||
export interface HaloCmdImportKey {
|
||||
keyNo: KeySlotNo
|
||||
}
|
||||
|
||||
export interface HaloResImportKey {
|
||||
publicKey: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdExportKey {
|
||||
keyNo: KeySlotNo
|
||||
password: ASCIIString
|
||||
data: HexString
|
||||
}
|
||||
|
||||
export interface HaloResExportKey {
|
||||
data: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdGetTransportPK {
|
||||
|
||||
}
|
||||
|
||||
export interface HaloResGetTransportPK {
|
||||
data: HexString
|
||||
rootPublicKey: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdLoadTransportPK {
|
||||
data: HexString
|
||||
}
|
||||
|
||||
export interface HaloResLoadTransportPK {
|
||||
data: HexString
|
||||
rootPublicKey: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdSetPassword {
|
||||
password: ASCIIString
|
||||
keyNo: KeySlotNo
|
||||
}
|
||||
|
||||
export interface HaloResSetPassword {
|
||||
status: "ok"
|
||||
}
|
||||
|
||||
export interface HaloCmdUnsetPassword {
|
||||
password: ASCIIString
|
||||
keyNo: KeySlotNo
|
||||
}
|
||||
|
||||
export interface HaloResUnsetPassword {
|
||||
status: "ok"
|
||||
}
|
||||
|
||||
export interface HaloCmdReplacePassword {
|
||||
currentPassword: ASCIIString
|
||||
newPassword: ASCIIString
|
||||
keyNo: KeySlotNo
|
||||
}
|
||||
|
||||
export interface HaloResReplacePassword {
|
||||
status: "ok"
|
||||
}
|
||||
|
||||
export interface HaloCmdGetKeyInfo {
|
||||
keyNo: KeySlotNo
|
||||
}
|
||||
|
||||
export interface HaloResGetKeyInfo {
|
||||
keyState: KeyState
|
||||
publicKey: HexString
|
||||
attestSig: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdSetURLSubdomain {
|
||||
subdomain: ASCIIString
|
||||
allowSignatureDER: HexString
|
||||
}
|
||||
|
||||
export interface HaloResSetURLSubdomain {
|
||||
status: "ok"
|
||||
}
|
||||
@@ -4,17 +4,59 @@
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
import {ethers} from 'ethers';
|
||||
import {HaloLogicError, HaloTagError} from "./exceptions.js";
|
||||
import {convertSignature, mode, parseSig, parsePublicKeys, randomBuffer, SECP256k1_ORDER, BJJ_ORDER, sigToDer} from "./util.js";
|
||||
import {
|
||||
convertSignature,
|
||||
mode,
|
||||
parseSig,
|
||||
parsePublicKeys,
|
||||
randomBuffer,
|
||||
SECP256k1_ORDER,
|
||||
BJJ_ORDER,
|
||||
sigToDer
|
||||
} from "./util.js";
|
||||
import {FLAGS} from "./flags.js";
|
||||
import {sha256} from "js-sha256";
|
||||
import elliptic from 'elliptic';
|
||||
import {CMD_CODES as CMD} from './cmdcodes.js';
|
||||
import pbkdf2 from 'pbkdf2';
|
||||
import crypto from 'crypto-browserify';
|
||||
import {KEY_FLAGS, parseKeyFlags} from "./keyflags.js";
|
||||
import {
|
||||
ExecHaloCmdOptions,
|
||||
ExecReturnStruct, HaloCmdCFGNDEF, HaloCmdGenKey, HaloCmdGenKeyConfirm, HaloCmdGenKeyFinalize,
|
||||
HaloResCFGNDEF, HaloResGenKey, HaloResGenKeyConfirm, HaloResGenKeyFinalize,
|
||||
PublicKeyList
|
||||
} from "../types.js";
|
||||
import {
|
||||
HaloCmdExportKey,
|
||||
HaloCmdGetDataStruct,
|
||||
HaloCmdGetGraffiti, HaloCmdGetKeyInfo,
|
||||
HaloCmdGetPkeys,
|
||||
HaloCmdGetTransportPK,
|
||||
HaloCmdImportKey,
|
||||
HaloCmdImportKeyInit, HaloCmdLoadTransportPK, HaloCmdReplacePassword, HaloCmdSetPassword, HaloCmdSetURLSubdomain,
|
||||
HaloCmdSign,
|
||||
HaloCmdSignChallenge,
|
||||
HaloCmdSignRandom,
|
||||
HaloCmdStoreGraffiti, HaloCmdUnsetPassword,
|
||||
HaloCmdWriteLatch,
|
||||
HaloResExportKey,
|
||||
HaloResGetDataStruct,
|
||||
HaloResGetGraffiti, HaloResGetKeyInfo,
|
||||
HaloResGetPkeys, HaloResGetTransportPK,
|
||||
HaloResImportKey,
|
||||
HaloResImportKeyInit,
|
||||
HaloResInputObj, HaloResLoadTransportPK, HaloResReplacePassword, HaloResSetPassword, HaloResSetURLSubdomain,
|
||||
HaloResSign,
|
||||
HaloResSignChallenge,
|
||||
HaloResSignRandom,
|
||||
HaloResStoreGraffiti, HaloResUnsetPassword,
|
||||
HaloResWriteLatch
|
||||
} from "./command_types.js";
|
||||
|
||||
const ec = new elliptic.ec('secp256k1');
|
||||
|
||||
@@ -33,33 +75,33 @@ const ec = new elliptic.ec('secp256k1');
|
||||
* if that's possible.
|
||||
*/
|
||||
|
||||
function extractPublicKeyWebNFC(keyNo, resp) {
|
||||
function extractPublicKeyWebNFC(keyNo: number, resp: ExecReturnStruct) {
|
||||
let publicKey = null;
|
||||
let pkKey = "pk" + keyNo;
|
||||
const pkKey = "pk" + keyNo;
|
||||
|
||||
if (resp.extra.hasOwnProperty(pkKey)) {
|
||||
if (Object.prototype.hasOwnProperty.call(resp.extra, pkKey)) {
|
||||
publicKey = Buffer.from(resp.extra[pkKey], "hex");
|
||||
} else if (resp.extra.hasOwnProperty("static")) {
|
||||
let pkeys = parsePublicKeys(Buffer.from(resp.extra["static"], "hex"));
|
||||
} else if (Object.prototype.hasOwnProperty.call(resp.extra, "static")) {
|
||||
const pkeys = parsePublicKeys(Buffer.from(resp.extra["static"], "hex"));
|
||||
publicKey = pkeys[keyNo];
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
async function cmdGetPkeys(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
async function cmdGetPkeys(options: ExecHaloCmdOptions, args: HaloCmdGetPkeys): Promise<HaloResGetPkeys> {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_GET_PKEYS])
|
||||
]);
|
||||
|
||||
let resp = await options.exec(payload);
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const resp = await options.exec(payload);
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
let publicKeys = parsePublicKeys(res);
|
||||
let compressedPublicKeys = {};
|
||||
let etherAddresses = {};
|
||||
const publicKeys = parsePublicKeys(res);
|
||||
const compressedPublicKeys: PublicKeyList = {};
|
||||
const etherAddresses: PublicKeyList = {};
|
||||
|
||||
for (let pkNo of Object.keys(publicKeys)) {
|
||||
for (const pkNo of Object.keys(publicKeys).map(Number)) {
|
||||
compressedPublicKeys[pkNo] = ec.keyFromPublic(publicKeys[pkNo], 'hex').getPublic().encodeCompressed('hex');
|
||||
etherAddresses[pkNo] = ethers.computeAddress('0x' + publicKeys[pkNo]);
|
||||
}
|
||||
@@ -67,14 +109,14 @@ async function cmdGetPkeys(options, args) {
|
||||
return {publicKeys, compressedPublicKeys, etherAddresses};
|
||||
}
|
||||
|
||||
async function cmdSign(options, args) {
|
||||
let checks = [
|
||||
args.hasOwnProperty("digest") && typeof args.digest !== "undefined",
|
||||
args.hasOwnProperty("message") && typeof args.message !== "undefined",
|
||||
args.hasOwnProperty("typedData") && typeof args.typedData !== "undefined"
|
||||
async function cmdSign(options: ExecHaloCmdOptions, args: HaloCmdSign): Promise<HaloResSign> {
|
||||
const checks = [
|
||||
Object.prototype.hasOwnProperty.call(args, "digest") && typeof args.digest !== "undefined",
|
||||
Object.prototype.hasOwnProperty.call(args, "message") && typeof args.message !== "undefined",
|
||||
Object.prototype.hasOwnProperty.call(args, "typedData") && typeof args.typedData !== "undefined"
|
||||
];
|
||||
|
||||
let numDataArgs = checks.filter((x) => !!x).length;
|
||||
const numDataArgs = checks.filter((x) => !!x).length;
|
||||
|
||||
if (numDataArgs !== 1) {
|
||||
throw new HaloLogicError("One of the following arguments are required: digest, message, typedData.");
|
||||
@@ -83,7 +125,7 @@ async function cmdSign(options, args) {
|
||||
let messageBuf = null;
|
||||
let digestBuf = null;
|
||||
|
||||
if (args.hasOwnProperty("message") && typeof args.message !== "undefined") {
|
||||
if (Object.prototype.hasOwnProperty.call(args, "message") && typeof args.message !== "undefined") {
|
||||
if (args.format === "text") {
|
||||
messageBuf = Buffer.from(args.message);
|
||||
} else if (!args.format || args.format === "hex") {
|
||||
@@ -103,7 +145,7 @@ async function cmdSign(options, args) {
|
||||
}
|
||||
|
||||
digestBuf = Buffer.from(ethers.hashMessage(messageBuf).slice(2), "hex");
|
||||
} else if (args.hasOwnProperty("typedData") && typeof args.typedData !== "undefined") {
|
||||
} else if (Object.prototype.hasOwnProperty.call(args, "typedData") && typeof args.typedData !== "undefined") {
|
||||
let hashStr;
|
||||
|
||||
try {
|
||||
@@ -113,7 +155,7 @@ async function cmdSign(options, args) {
|
||||
}
|
||||
|
||||
digestBuf = Buffer.from(hashStr.slice(2), "hex");
|
||||
} else if (args.hasOwnProperty("digest") && typeof args.digest !== "undefined") {
|
||||
} else if (Object.prototype.hasOwnProperty.call(args, "digest") && typeof args.digest !== "undefined") {
|
||||
digestBuf = Buffer.from(args.digest, "hex");
|
||||
|
||||
if (args.digest.length !== digestBuf.length * 2 || digestBuf.length !== 32) {
|
||||
@@ -127,7 +169,7 @@ async function cmdSign(options, args) {
|
||||
let pwdHash = null;
|
||||
|
||||
if (args.password) {
|
||||
let derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
const derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
|
||||
pwdHash = Buffer.from(sha256(Buffer.concat([
|
||||
Buffer.from([0x19]),
|
||||
@@ -183,9 +225,9 @@ async function cmdSign(options, args) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
let sigBuf = Buffer.from(resp.result, "hex");
|
||||
let sigLen = sigBuf[1] + 2;
|
||||
let sig = sigBuf.slice(0, sigLen).toString('hex');
|
||||
const sigBuf = Buffer.from(resp.result, "hex");
|
||||
const sigLen = sigBuf[1] + 2;
|
||||
const sig = sigBuf.slice(0, sigLen).toString('hex');
|
||||
let publicKey = null;
|
||||
|
||||
if ((!args.legacySignCommand && options.method !== "webnfc") || args.keyNo > 3) {
|
||||
@@ -198,18 +240,19 @@ async function cmdSign(options, args) {
|
||||
publicKey = extractPublicKeyWebNFC(args.keyNo, resp);
|
||||
}
|
||||
|
||||
let inputObj = {
|
||||
const inputObj: HaloResInputObj = {
|
||||
"keyNo": args.keyNo,
|
||||
"digest": digestBuf.toString('hex'),
|
||||
"digest": digestBuf.toString('hex')
|
||||
};
|
||||
|
||||
if (messageBuf !== null) {
|
||||
inputObj.message = messageBuf.toString('hex');
|
||||
} else if (args.typedData) {
|
||||
inputObj.typedData = args.typedData;
|
||||
|
||||
inputObj.primaryType = ethers.TypedDataEncoder.getPrimaryType(args.typedData.types);
|
||||
inputObj.domainHash = ethers.TypedDataEncoder.hashDomain(args.typedData.domain).slice(2);
|
||||
inputObj.typedData = {
|
||||
primaryType: ethers.TypedDataEncoder.getPrimaryType(args.typedData.types),
|
||||
domainHash: ethers.TypedDataEncoder.hashDomain(args.typedData.domain).slice(2),
|
||||
...args.typedData
|
||||
};
|
||||
}
|
||||
|
||||
if (args.keyNo >= 0x60) {
|
||||
@@ -218,7 +261,7 @@ async function cmdSign(options, args) {
|
||||
"signature": {
|
||||
"der": sigToDer(parseSig(Buffer.from(sig, "hex"), BJJ_ORDER)).toString('hex')
|
||||
},
|
||||
"publicKey": publicKey.toString('hex')
|
||||
"publicKey": publicKey ? publicKey.toString('hex') : undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -239,8 +282,8 @@ async function cmdSign(options, args) {
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdWriteLatch(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
async function cmdWriteLatch(options: ExecHaloCmdOptions, args: HaloCmdWriteLatch): Promise<HaloResWriteLatch> {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_LATCH_DATA, args.latchNo]),
|
||||
Buffer.from(args.data, "hex")
|
||||
]);
|
||||
@@ -249,15 +292,15 @@ async function cmdWriteLatch(options, args) {
|
||||
return {"status": "ok"};
|
||||
}
|
||||
|
||||
async function cmdSignRandom(options, args) {
|
||||
let resp = await options.exec(Buffer.from([CMD.SHARED_CMD_SIGN_RANDOM, args.keyNo]));
|
||||
async function cmdSignRandom(options: ExecHaloCmdOptions, args: HaloCmdSignRandom): Promise<HaloResSignRandom> {
|
||||
const resp = await options.exec(Buffer.from([CMD.SHARED_CMD_SIGN_RANDOM, args.keyNo]));
|
||||
|
||||
let resBuf = Buffer.from(resp.result, 'hex');
|
||||
let digest = resBuf.slice(0, 32);
|
||||
const resBuf = Buffer.from(resp.result, 'hex');
|
||||
const digest = resBuf.slice(0, 32);
|
||||
let signature = resBuf.slice(32, 32 + resBuf[33] + 2);
|
||||
let publicKey = resBuf.slice(32 + resBuf[33] + 2);
|
||||
const publicKey = resBuf.slice(32 + resBuf[33] + 2);
|
||||
|
||||
let counter = digest.readUInt32BE(0);
|
||||
const counter = digest.readUInt32BE(0);
|
||||
|
||||
if (args.keyNo >= 0x60) {
|
||||
signature = sigToDer(parseSig(signature, BJJ_ORDER));
|
||||
@@ -273,16 +316,16 @@ async function cmdSignRandom(options, args) {
|
||||
};
|
||||
}
|
||||
|
||||
async function cmdSignChallenge(options, args) {
|
||||
let challengeBuf = Buffer.from(args.challenge, "hex");
|
||||
let resp = await options.exec(Buffer.from([CMD.SHARED_CMD_SIGN_CHALLENGE, args.keyNo, ...challengeBuf]));
|
||||
async function cmdSignChallenge(options: ExecHaloCmdOptions, args: HaloCmdSignChallenge): Promise<HaloResSignChallenge> {
|
||||
const challengeBuf = Buffer.from(args.challenge, "hex");
|
||||
const resp = await options.exec(Buffer.from([CMD.SHARED_CMD_SIGN_CHALLENGE, args.keyNo, ...challengeBuf]));
|
||||
|
||||
let resBuf = Buffer.from(resp.result, 'hex');
|
||||
let sigLen = 2 + resBuf[1];
|
||||
const resBuf = Buffer.from(resp.result, 'hex');
|
||||
const sigLen = 2 + resBuf[1];
|
||||
|
||||
let signature = resBuf.slice(0, sigLen);
|
||||
let publicKey = resBuf.slice(sigLen, sigLen + 65);
|
||||
let attestSig = resBuf.slice(sigLen + 65);
|
||||
const publicKey = resBuf.slice(sigLen, sigLen + 65);
|
||||
const attestSig = resBuf.slice(sigLen + 65);
|
||||
|
||||
if (args.keyNo >= 0x60) {
|
||||
signature = sigToDer(parseSig(signature, BJJ_ORDER));
|
||||
@@ -297,7 +340,7 @@ async function cmdSignChallenge(options, args) {
|
||||
};
|
||||
}
|
||||
|
||||
async function cmdCfgNDEF(options, args) {
|
||||
async function cmdCfgNDEF(options: ExecHaloCmdOptions, args: HaloCmdCFGNDEF): Promise<HaloResCFGNDEF> {
|
||||
if (args.flagHidePk1 && args.flagHidePk2) {
|
||||
throw new HaloLogicError("It's not allowed to use both flagHidePk1 and flagHidePk2.");
|
||||
}
|
||||
@@ -305,7 +348,7 @@ async function cmdCfgNDEF(options, args) {
|
||||
let flagBuf = Buffer.alloc(3);
|
||||
|
||||
Object.keys(args)
|
||||
.filter((k) => k.startsWith('flag') && args[k])
|
||||
.filter((k) => k.startsWith('flag') && args[k as keyof typeof args])
|
||||
.map((k) => FLAGS[k])
|
||||
.forEach((v) => {
|
||||
flagBuf[v[0]] |= v[1];
|
||||
@@ -318,7 +361,7 @@ async function cmdCfgNDEF(options, args) {
|
||||
flagBuf[2] = args.pkN;
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_SET_NDEF_MODE]),
|
||||
flagBuf
|
||||
]);
|
||||
@@ -330,7 +373,7 @@ async function cmdCfgNDEF(options, args) {
|
||||
};
|
||||
}
|
||||
|
||||
async function cmdGenKey(options, args) {
|
||||
async function cmdGenKey(options: ExecHaloCmdOptions, args: HaloCmdGenKey): Promise<HaloResGenKey> {
|
||||
if (!args.entropy) {
|
||||
if (options.method === "pcsc") {
|
||||
args.entropy = randomBuffer().toString("hex");
|
||||
@@ -339,13 +382,13 @@ async function cmdGenKey(options, args) {
|
||||
}
|
||||
}
|
||||
|
||||
let entropyBuf = Buffer.from(args.entropy, "hex");
|
||||
const entropyBuf = Buffer.from(args.entropy, "hex");
|
||||
|
||||
if (entropyBuf.length !== 32) {
|
||||
throw new HaloLogicError("The command.entropy should be exactly 32 bytes, hex encoded.");
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_GENERATE_KEY_INIT]),
|
||||
Buffer.from([args.keyNo]),
|
||||
entropyBuf
|
||||
@@ -365,25 +408,25 @@ async function cmdGenKey(options, args) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
if (res[0] === 0x00) {
|
||||
let m1Prefixed = Buffer.concat([
|
||||
const m1Prefixed = Buffer.concat([
|
||||
Buffer.from([0x19]),
|
||||
Buffer.from("Key generation sample:\n"),
|
||||
res.slice(1, 1 + 32)
|
||||
]);
|
||||
let m2Prefixed = Buffer.concat([
|
||||
const m2Prefixed = Buffer.concat([
|
||||
Buffer.from([0x19]),
|
||||
Buffer.from("Key generation sample:\n"),
|
||||
res.slice(1 + 32, 1 + 64)
|
||||
]);
|
||||
let msg1 = Buffer.from(sha256(m1Prefixed), 'hex');
|
||||
let msg2 = Buffer.from(sha256(m2Prefixed), 'hex');
|
||||
let sig = res.slice(1 + 64);
|
||||
let sig1Length = sig[1];
|
||||
let sig1 = sig.slice(0, 2 + sig1Length);
|
||||
let sig2 = sig.slice(2 + sig1Length);
|
||||
const msg1 = Buffer.from(sha256(m1Prefixed), 'hex');
|
||||
const msg2 = Buffer.from(sha256(m2Prefixed), 'hex');
|
||||
const sig = res.slice(1 + 64);
|
||||
const sig1Length = sig[1];
|
||||
const sig1 = sig.slice(0, 2 + sig1Length);
|
||||
const sig2 = sig.slice(2 + sig1Length);
|
||||
|
||||
let curveOrder = SECP256k1_ORDER;
|
||||
|
||||
@@ -391,21 +434,21 @@ async function cmdGenKey(options, args) {
|
||||
curveOrder = BJJ_ORDER;
|
||||
}
|
||||
|
||||
let candidates = [];
|
||||
const candidates: string[] = [];
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
candidates.push(ec.recoverPubKey(msg1, parseSig(sig1, curveOrder), i).encode('hex'));
|
||||
candidates.push(ec.recoverPubKey(msg2, parseSig(sig2, curveOrder), i).encode('hex'));
|
||||
}
|
||||
|
||||
let bestPk = Buffer.from(mode(candidates), 'hex');
|
||||
const bestPk = Buffer.from(mode(candidates), 'hex');
|
||||
return {
|
||||
"publicKey": bestPk.toString('hex'),
|
||||
"needsConfirmPK": true
|
||||
};
|
||||
} else if (res[0] === 0x01) {
|
||||
let rootKeyPk = res.slice(0, 65);
|
||||
let rootKeyAttest = res.slice(65);
|
||||
const rootKeyPk = res.slice(0, 65);
|
||||
const rootKeyAttest = res.slice(65);
|
||||
|
||||
return {
|
||||
"rootPublicKey": rootKeyPk.toString('hex'),
|
||||
@@ -417,18 +460,18 @@ async function cmdGenKey(options, args) {
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdGenKeyConfirm(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
async function cmdGenKeyConfirm(options: ExecHaloCmdOptions, args: HaloCmdGenKeyConfirm): Promise<HaloResGenKeyConfirm> {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_GENERATE_KEY_CONT]),
|
||||
Buffer.from([args.keyNo]),
|
||||
Buffer.from(args.publicKey, "hex")
|
||||
]);
|
||||
|
||||
let resp = await options.exec(payload);
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const resp = await options.exec(payload);
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
let rootPublicKey = res.slice(0, 65);
|
||||
let rootAttestSig = res.slice(65 + 1);
|
||||
const rootPublicKey = res.slice(0, 65);
|
||||
const rootAttestSig = res.slice(65 + 1);
|
||||
|
||||
return {
|
||||
rootPublicKey: rootPublicKey.toString('hex'),
|
||||
@@ -436,11 +479,11 @@ async function cmdGenKeyConfirm(options, args) {
|
||||
};
|
||||
}
|
||||
|
||||
async function cmdGenKeyFinalize(options, args) {
|
||||
async function cmdGenKeyFinalize(options: ExecHaloCmdOptions, args: HaloCmdGenKeyFinalize): Promise<HaloResGenKeyFinalize> {
|
||||
let payload;
|
||||
|
||||
if (args.password) {
|
||||
let derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
const derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
|
||||
payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_GENERATE_KEY_FIN_PWD]),
|
||||
@@ -454,12 +497,12 @@ async function cmdGenKeyFinalize(options, args) {
|
||||
]);
|
||||
}
|
||||
|
||||
let resp = await options.exec(payload);
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const resp = await options.exec(payload);
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
let newKeyNo = res.slice(0, 1);
|
||||
let publicKey = res.slice(1, 1 + 65);
|
||||
let attestSig = res.slice(1 + 65);
|
||||
// const newKeyNo = res.slice(0, 1);
|
||||
const publicKey = res.slice(1, 1 + 65);
|
||||
const attestSig = res.slice(1 + 65);
|
||||
|
||||
return {
|
||||
publicKey: publicKey.toString('hex'),
|
||||
@@ -467,8 +510,8 @@ async function cmdGenKeyFinalize(options, args) {
|
||||
};
|
||||
}
|
||||
|
||||
async function cmdSetURLSubdomain(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
async function cmdSetURLSubdomain(options: ExecHaloCmdOptions, args: HaloCmdSetURLSubdomain): Promise<HaloResSetURLSubdomain> {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_SET_URL_SUBDOMAIN]),
|
||||
Buffer.from([args.subdomain.length]),
|
||||
Buffer.from(args.subdomain),
|
||||
@@ -480,16 +523,16 @@ async function cmdSetURLSubdomain(options, args) {
|
||||
return {"status": "ok"};
|
||||
}
|
||||
|
||||
async function cmdGetKeyInfo(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
async function cmdGetKeyInfo(options: ExecHaloCmdOptions, args: HaloCmdGetKeyInfo): Promise<HaloResGetKeyInfo> {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_GET_KEY_INFO]),
|
||||
Buffer.from([args.keyNo]),
|
||||
]);
|
||||
|
||||
let resp = await options.exec(payload);
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const resp = await options.exec(payload);
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
let keyFlags = res.slice(1, 2)[0];
|
||||
const keyFlags = res.slice(1, 2)[0];
|
||||
let failedAuthCtr = 0;
|
||||
let off = 2;
|
||||
|
||||
@@ -499,8 +542,8 @@ async function cmdGetKeyInfo(options, args) {
|
||||
failedAuthCtr = res.slice(2, 3)[0];
|
||||
}
|
||||
|
||||
let publicKey = res.slice(off, off + 65);
|
||||
let attestSig = res.slice(off + 65);
|
||||
const publicKey = res.slice(off, off + 65);
|
||||
const attestSig = res.slice(off + 65);
|
||||
|
||||
return {
|
||||
keyState: {
|
||||
@@ -512,10 +555,10 @@ async function cmdGetKeyInfo(options, args) {
|
||||
};
|
||||
}
|
||||
|
||||
async function cmdSetPassword(options, args) {
|
||||
let derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
async function cmdSetPassword(options: ExecHaloCmdOptions, args: HaloCmdSetPassword): Promise<HaloResSetPassword> {
|
||||
const derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_SET_PASSWORD]),
|
||||
Buffer.from([args.keyNo]),
|
||||
derivedKey
|
||||
@@ -526,16 +569,16 @@ async function cmdSetPassword(options, args) {
|
||||
return {"status": "ok"};
|
||||
}
|
||||
|
||||
async function cmdUnsetPassword(options, args) {
|
||||
let derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
let authHash = Buffer.from(sha256(Buffer.concat([
|
||||
async function cmdUnsetPassword(options: ExecHaloCmdOptions, args: HaloCmdUnsetPassword): Promise<HaloResUnsetPassword> {
|
||||
const derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
const authHash = Buffer.from(sha256(Buffer.concat([
|
||||
Buffer.from([0x19]),
|
||||
Buffer.from("Unset password auth:\n"),
|
||||
Buffer.from([args.keyNo]),
|
||||
derivedKey
|
||||
])), "hex");
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_UNSET_PASSWORD]),
|
||||
Buffer.from([args.keyNo]),
|
||||
authHash
|
||||
@@ -546,20 +589,20 @@ async function cmdUnsetPassword(options, args) {
|
||||
return {"status": "ok"};
|
||||
}
|
||||
|
||||
async function cmdReplacePassword(options, args) {
|
||||
let curPassword = pbkdf2.pbkdf2Sync(args.currentPassword, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
let newPassword = pbkdf2.pbkdf2Sync(args.newPassword, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
async function cmdReplacePassword(options: ExecHaloCmdOptions, args: HaloCmdReplacePassword): Promise<HaloResReplacePassword> {
|
||||
const curPassword = pbkdf2.pbkdf2Sync(args.currentPassword, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
const newPassword = pbkdf2.pbkdf2Sync(args.newPassword, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
|
||||
let plaintext = Buffer.concat([
|
||||
const plaintext = Buffer.concat([
|
||||
Buffer.from(sha256(newPassword), "hex").slice(0, 16),
|
||||
newPassword
|
||||
]);
|
||||
|
||||
let cipher = crypto.createCipheriv('aes-128-cbc', curPassword, Buffer.alloc(16));
|
||||
const cipher = crypto.createCipheriv('aes-128-cbc', curPassword, Buffer.alloc(16));
|
||||
cipher.setAutoPadding(false);
|
||||
let ct = Buffer.from(cipher.update(plaintext, 'buffer', 'hex') + cipher.final('hex'), 'hex');
|
||||
const ct = Buffer.from(cipher.update(plaintext, undefined, 'hex') + cipher.final('hex'), 'hex');
|
||||
|
||||
let authHash = Buffer.from(sha256(Buffer.concat([
|
||||
const authHash = Buffer.from(sha256(Buffer.concat([
|
||||
Buffer.from([0x19]),
|
||||
Buffer.from("Replace password auth:\n"),
|
||||
Buffer.from([args.keyNo]),
|
||||
@@ -567,7 +610,7 @@ async function cmdReplacePassword(options, args) {
|
||||
curPassword
|
||||
])), "hex");
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_REPLACE_PASSWORD]),
|
||||
Buffer.from([args.keyNo]),
|
||||
ct,
|
||||
@@ -579,17 +622,17 @@ async function cmdReplacePassword(options, args) {
|
||||
return {"status": "ok"};
|
||||
}
|
||||
|
||||
async function _internalLoadPK(options, payload) {
|
||||
let resp = await options.exec(payload, {pcscExecLayer: "u2f"});
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
async function _internalLoadPK(options: ExecHaloCmdOptions, payload: Buffer) {
|
||||
const resp = await options.exec(payload, {pcscExecLayer: "u2f"});
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
if (res[0] !== 0x01) {
|
||||
throw new HaloLogicError("Unsupported protocol version reported by the HaLo tag.");
|
||||
}
|
||||
|
||||
let sigLen = res[2] + 2;
|
||||
let data = res.slice(1, sigLen + 1 + 65);
|
||||
let rootPK = res.slice(sigLen + 1 + 65);
|
||||
const sigLen = res[2] + 2;
|
||||
const data = res.slice(1, sigLen + 1 + 65);
|
||||
const rootPK = res.slice(sigLen + 1 + 65);
|
||||
|
||||
return {
|
||||
"data": data.toString('hex'),
|
||||
@@ -597,24 +640,24 @@ async function _internalLoadPK(options, payload) {
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdGetTransportPK(options, args) {
|
||||
async function cmdGetTransportPK(options: ExecHaloCmdOptions, args: HaloCmdGetTransportPK): Promise<HaloResGetTransportPK> {
|
||||
if (options.method !== "credential" && options.method !== "pcsc") {
|
||||
throw new HaloLogicError("Unsupported execution method. Please set options.method = 'credential'.");
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.CRED_CMD_GET_TRANSPORT_PK_ATT])
|
||||
]);
|
||||
|
||||
return await _internalLoadPK(options, payload);
|
||||
}
|
||||
|
||||
async function cmdLoadTransportPK(options, args) {
|
||||
async function cmdLoadTransportPK(options: ExecHaloCmdOptions, args: HaloCmdLoadTransportPK): Promise<HaloResLoadTransportPK> {
|
||||
if (options.method !== "credential" && options.method !== "pcsc") {
|
||||
throw new HaloLogicError("Unsupported execution method. Please set options.method = 'credential'.");
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.CRED_CMD_LOAD_TRANSPORT_PK]),
|
||||
Buffer.from(args.data, 'hex')
|
||||
]);
|
||||
@@ -622,17 +665,17 @@ async function cmdLoadTransportPK(options, args) {
|
||||
return await _internalLoadPK(options, payload);
|
||||
}
|
||||
|
||||
async function cmdExportKey(options, args) {
|
||||
async function cmdExportKey(options: ExecHaloCmdOptions, args: HaloCmdExportKey): Promise<HaloResExportKey> {
|
||||
if (options.method !== "credential" && options.method !== "pcsc") {
|
||||
throw new HaloLogicError("Unsupported execution method. Please set options.method = 'credential'.");
|
||||
}
|
||||
|
||||
let derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
let dataBuf = Buffer.from(args.data, 'hex');
|
||||
let sigLen = dataBuf[1] + 2;
|
||||
let publicKeyBuf = dataBuf.slice(sigLen);
|
||||
const derivedKey = pbkdf2.pbkdf2Sync(args.password, 'HaLoChipSalt', 5000, 16, 'sha512');
|
||||
const dataBuf = Buffer.from(args.data, 'hex');
|
||||
const sigLen = dataBuf[1] + 2;
|
||||
const publicKeyBuf = dataBuf.slice(sigLen);
|
||||
|
||||
let pwdHash = Buffer.from(sha256(Buffer.concat([
|
||||
const pwdHash = Buffer.from(sha256(Buffer.concat([
|
||||
Buffer.from([0x19]),
|
||||
Buffer.from("Key backup:\n"),
|
||||
Buffer.from([args.keyNo]),
|
||||
@@ -640,26 +683,26 @@ async function cmdExportKey(options, args) {
|
||||
publicKeyBuf,
|
||||
])), "hex");
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.CRED_CMD_EXPORT_KEY]),
|
||||
Buffer.from([args.keyNo]),
|
||||
pwdHash
|
||||
]);
|
||||
|
||||
let resp = await options.exec(payload, {pcscExecLayer: "u2f"});
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const resp = await options.exec(payload, {pcscExecLayer: "u2f"});
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
return {
|
||||
"data": res.toString('hex')
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdImportKeyInit(options, args) {
|
||||
async function cmdImportKeyInit(options: ExecHaloCmdOptions, args: HaloCmdImportKeyInit): Promise<HaloResImportKeyInit> {
|
||||
if (options.method !== "credential" && options.method !== "pcsc") {
|
||||
throw new HaloLogicError("Unsupported execution method. Please set options.method = 'credential'.");
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.CRED_CMD_IMPORT_KEY_INIT]),
|
||||
Buffer.from([args.keyNo]),
|
||||
Buffer.from(args.data, 'hex')
|
||||
@@ -672,40 +715,40 @@ async function cmdImportKeyInit(options, args) {
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdImportKey(options, args) {
|
||||
async function cmdImportKey(options: ExecHaloCmdOptions, args: HaloCmdImportKey): Promise<HaloResImportKey> {
|
||||
if (options.method !== "credential" && options.method !== "pcsc") {
|
||||
throw new HaloLogicError("Unsupported execution method. Please set options.method = 'credential'.");
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.CRED_CMD_IMPORT_KEY]),
|
||||
Buffer.from([args.keyNo])
|
||||
]);
|
||||
|
||||
let resp = await options.exec(payload, {pcscExecLayer: "u2f"});
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const resp = await options.exec(payload, {pcscExecLayer: "u2f"});
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
return {
|
||||
"publicKey": res.slice(1).toString('hex')
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdGetDataStruct(options, args) {
|
||||
let specItems = args.spec.split(',');
|
||||
specItems = specItems.map((item) => item.split(':', 2));
|
||||
async function cmdGetDataStruct(options: ExecHaloCmdOptions, args: HaloCmdGetDataStruct): Promise<HaloResGetDataStruct> {
|
||||
const specParts = args.spec.split(',');
|
||||
let specItems = specParts.map((item: string) => item.split(':', 2));
|
||||
|
||||
const TYPES = {
|
||||
"publicKey": 0x01,
|
||||
"publicKeyAttest": 0x02,
|
||||
"keySlotFlags": 0x03,
|
||||
const TYPES: Record<string, number> = {
|
||||
"publicKey": 0x01,
|
||||
"publicKeyAttest": 0x02,
|
||||
"keySlotFlags": 0x03,
|
||||
"keySlotFailedAuthCtr": 0x04,
|
||||
"latchValue": 0x20,
|
||||
"latchAttest": 0x21,
|
||||
"graffiti": 0x22,
|
||||
"firmwareVersion": 0xF0
|
||||
"latchValue": 0x20,
|
||||
"latchAttest": 0x21,
|
||||
"graffiti": 0x22,
|
||||
"firmwareVersion": 0xF0
|
||||
};
|
||||
|
||||
const SPECIAL_MSG = {
|
||||
const SPECIAL_MSG: Record<number, string> = {
|
||||
0x01: "keySlotOutOfBounds",
|
||||
0x02: "keySlotNotGenerated",
|
||||
0x03: "latchNotSet",
|
||||
@@ -714,12 +757,12 @@ async function cmdGetDataStruct(options, args) {
|
||||
|
||||
let data = Buffer.alloc(0);
|
||||
|
||||
for (let item of specItems) {
|
||||
for (const item of specItems) {
|
||||
if (!TYPES[item[0]]) {
|
||||
throw new HaloLogicError("Unsupported object type: " + item[0]);
|
||||
}
|
||||
|
||||
let val = parseInt(item[1]);
|
||||
const val = parseInt(item[1]);
|
||||
|
||||
if (val < 0 || val > 255) {
|
||||
throw new HaloLogicError("Too high index value at: " + item[0] + ":" + item[1]);
|
||||
@@ -731,19 +774,19 @@ async function cmdGetDataStruct(options, args) {
|
||||
]);
|
||||
}
|
||||
|
||||
let payload = Buffer.concat([
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_GET_DATA_STRUCT]),
|
||||
data
|
||||
]);
|
||||
|
||||
let resp = await options.exec(payload);
|
||||
const resp = await options.exec(payload);
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
|
||||
specItems = specItems.reverse();
|
||||
let out = {};
|
||||
const out: Record<string, unknown> = {};
|
||||
|
||||
while (res.length > 0) {
|
||||
let item = specItems.pop();
|
||||
const item = specItems.pop() as string[];
|
||||
|
||||
let len = res[0];
|
||||
let data;
|
||||
@@ -752,8 +795,8 @@ async function cmdGetDataStruct(options, args) {
|
||||
if (len === 0xFF) { // no value returned, special message
|
||||
len = 1;
|
||||
|
||||
let msgCode = res.slice(1, 2)[0];
|
||||
let specialMsg = SPECIAL_MSG[msgCode];
|
||||
const msgCode = res.slice(1, 2)[0];
|
||||
const specialMsg = SPECIAL_MSG[msgCode];
|
||||
|
||||
if (specialMsg) {
|
||||
value = {"error": specialMsg};
|
||||
@@ -761,7 +804,7 @@ async function cmdGetDataStruct(options, args) {
|
||||
value = {"error": 'unknown_' + msgCode.toString()};
|
||||
}
|
||||
} else if (item[0] === "keySlotFlags") {
|
||||
let keyFlags = res.slice(1, len + 1)[0];
|
||||
const keyFlags = res.slice(1, len + 1)[0];
|
||||
value = parseKeyFlags(keyFlags);
|
||||
} else if (item[0] === "keySlotFailedAuthCtr") {
|
||||
value = res.slice(1, len + 1)[0];
|
||||
@@ -786,22 +829,22 @@ async function cmdGetDataStruct(options, args) {
|
||||
};
|
||||
}
|
||||
|
||||
async function cmdGetGraffiti(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
async function cmdGetGraffiti(options: ExecHaloCmdOptions, args: HaloCmdGetGraffiti): Promise<HaloResGetGraffiti> {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_GET_GRAFFITI]),
|
||||
Buffer.from([args.slotNo])
|
||||
]);
|
||||
|
||||
let resp = await options.exec(payload);
|
||||
let res = Buffer.from(resp.result, "hex");
|
||||
const resp = await options.exec(payload);
|
||||
const res = Buffer.from(resp.result, "hex");
|
||||
|
||||
return {
|
||||
"data": res.slice(1).toString('ascii')
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdStoreGraffiti(options, args) {
|
||||
let payload = Buffer.concat([
|
||||
async function cmdStoreGraffiti(options: ExecHaloCmdOptions, args: HaloCmdStoreGraffiti): Promise<HaloResStoreGraffiti> {
|
||||
const payload = Buffer.concat([
|
||||
Buffer.from([CMD.SHARED_CMD_STORE_GRAFFITI]),
|
||||
Buffer.from([args.slotNo]),
|
||||
Buffer.from(args.data, 'ascii')
|
||||
@@ -838,3 +881,5 @@ export {
|
||||
cmdGetGraffiti,
|
||||
cmdStoreGraffiti,
|
||||
};
|
||||
|
||||
export type * from "./command_types.js";
|
||||
@@ -4,7 +4,11 @@
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
let ERROR_CODES = {
|
||||
type ErrorCodeLookup = {
|
||||
[key: number]: [ErrorCode: string, ErrorDescription: string];
|
||||
};
|
||||
|
||||
const ERROR_CODES: ErrorCodeLookup = {
|
||||
0x01: ["ERROR_CODE_UNKNOWN_CMD", "Unknown command code."],
|
||||
0x02: ["ERROR_CODE_INVALID_KEY_NO", "Invalid key number."],
|
||||
0x03: ["ERROR_CODE_INVALID_LENGTH", "Invalid length."],
|
||||
@@ -9,9 +9,12 @@
|
||||
* The "name" property will contain the exact error name (e.g. ERROR_CODE_INVALID_KEY_NO).
|
||||
*/
|
||||
class HaloTagError extends Error {
|
||||
constructor(name, message) {
|
||||
public stackOnExecutor: string | undefined;
|
||||
|
||||
constructor(name: string, message: string, stackOnExecutor?: string) {
|
||||
super("The NFC tag encountered an error when executing command: " + message);
|
||||
this.name = name;
|
||||
this.stackOnExecutor = stackOnExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +23,12 @@ class HaloTagError extends Error {
|
||||
* Check "message" property for the detailed information.
|
||||
*/
|
||||
class HaloLogicError extends Error {
|
||||
constructor(message) {
|
||||
public stackOnExecutor: string | undefined;
|
||||
|
||||
constructor(message: string, stackOnExecutor?: string) {
|
||||
super(message);
|
||||
this.name = "HaloLogicError";
|
||||
this.stackOnExecutor = stackOnExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +36,7 @@ class HaloLogicError extends Error {
|
||||
* The user has denied NFC access permission.
|
||||
*/
|
||||
class NFCPermissionRequestDenied extends Error {
|
||||
constructor(message) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "NFCPermissionRequestDenied";
|
||||
}
|
||||
@@ -42,7 +48,7 @@ class NFCPermissionRequestDenied extends Error {
|
||||
* for the detailed explanation.
|
||||
*/
|
||||
class NFCMethodNotSupported extends Error {
|
||||
constructor(message) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "NFCMethodNotSupported";
|
||||
}
|
||||
@@ -54,7 +60,7 @@ class NFCMethodNotSupported extends Error {
|
||||
* This error should be ignored on the frontend.
|
||||
*/
|
||||
class NFCAbortedError extends Error {
|
||||
constructor(message) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "NFCAbortedError";
|
||||
}
|
||||
@@ -65,9 +71,12 @@ class NFCAbortedError extends Error {
|
||||
* Check "message" property for more details.
|
||||
*/
|
||||
class NFCOperationError extends Error {
|
||||
constructor(message) {
|
||||
public stackOnExecutor: string | undefined;
|
||||
|
||||
constructor(message: string, stackOnExecutor?: string) {
|
||||
super(message);
|
||||
this.name = "NFCOperationError";
|
||||
this.stackOnExecutor = stackOnExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +85,7 @@ class NFCOperationError extends Error {
|
||||
* and can no longer be used without creating a completely new instance first.
|
||||
*/
|
||||
class NFCBadTransportError extends Error {
|
||||
constructor(message) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "NFCBadTransportError";
|
||||
}
|
||||
@@ -86,12 +95,36 @@ class NFCBadTransportError extends Error {
|
||||
* The current origin is not on the HaLo Bridge's allow list.
|
||||
*/
|
||||
class NFCBridgeConsentError extends Error {
|
||||
constructor(message) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "NFCBridgeConsentError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge has encountered an internal unexpected error.
|
||||
*/
|
||||
class NFCBridgeUnexpectedError extends Error {
|
||||
public stackOnExecutor: string;
|
||||
|
||||
constructor(message: string, stackOnExecutor: string) {
|
||||
super(message);
|
||||
this.stackOnExecutor = stackOnExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gateway has encountered an internal unexpected error.
|
||||
*/
|
||||
class NFCGatewayUnexpectedError extends Error {
|
||||
public stackOnExecutor: string;
|
||||
|
||||
constructor(message: string, stackOnExecutor: string) {
|
||||
super(message);
|
||||
this.stackOnExecutor = stackOnExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
HaloTagError,
|
||||
HaloLogicError,
|
||||
@@ -100,5 +133,7 @@ export {
|
||||
NFCAbortedError,
|
||||
NFCOperationError,
|
||||
NFCBadTransportError,
|
||||
NFCBridgeConsentError
|
||||
NFCBridgeConsentError,
|
||||
NFCBridgeUnexpectedError,
|
||||
NFCGatewayUnexpectedError,
|
||||
};
|
||||
@@ -4,7 +4,11 @@
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
const FLAGS = {
|
||||
type FlagLookup = {
|
||||
[flagName: string]: [bytePos: number, bitPos: number];
|
||||
};
|
||||
|
||||
const FLAGS: FlagLookup = {
|
||||
flagUseText: [0, 0x01],
|
||||
flagHidePk1: [0, 0x02],
|
||||
flagHidePk2: [0, 0x04],
|
||||
@@ -2,28 +2,37 @@ import {JWEUtil} from "../jwe_util.js";
|
||||
import queryString from "query-string";
|
||||
import WebSocketAsPromised from "websocket-as-promised";
|
||||
import {execHaloCmdWeb} from "../../drivers/web.js";
|
||||
import {HaloCommandObject} from "../../types.js";
|
||||
|
||||
let gatewayServerHost = null;
|
||||
let currentCmd = null;
|
||||
let jweUtil = new JWEUtil();
|
||||
let wsp;
|
||||
let gatewayServerHost: string | null = null;
|
||||
let currentCmd: HaloCommandObject | null = null;
|
||||
let wsp: WebSocketAsPromised | null = null;
|
||||
const jweUtil = new JWEUtil();
|
||||
|
||||
function createWs(url) {
|
||||
function createWs(url: string) {
|
||||
return new WebSocketAsPromised(url, {
|
||||
packMessage: data => JSON.stringify(data),
|
||||
unpackMessage: data => JSON.parse(data),
|
||||
unpackMessage: data => JSON.parse(data as string),
|
||||
attachRequestId: (data, requestId) => Object.assign({uid: requestId}, data),
|
||||
extractRequestId: data => data && data.uid
|
||||
});
|
||||
}
|
||||
|
||||
function haloGateExecutorSetHost(newGatewayServerHost) {
|
||||
function haloGateExecutorSetHost(newGatewayServerHost: string) {
|
||||
gatewayServerHost = newGatewayServerHost;
|
||||
}
|
||||
|
||||
async function haloGateExecutorCreateWs(logCallback, newCommandCallback) {
|
||||
interface ExecutorLogCallback {
|
||||
(message: string): void
|
||||
}
|
||||
|
||||
interface ExecutorNewCommandCallback {
|
||||
(command: HaloCommandObject): void
|
||||
}
|
||||
|
||||
async function haloGateExecutorCreateWs(logCallback: ExecutorLogCallback, newCommandCallback: ExecutorNewCommandCallback) {
|
||||
let serverPrefix;
|
||||
let searchParts = window.location.hash.split('/');
|
||||
const searchParts = window.location.hash.split('/');
|
||||
|
||||
if (searchParts.length !== 3 || searchParts[0] !== "#!" || searchParts[2] !== "") {
|
||||
throw new Error("Malformed executor URL provided - failed to analyse fragment part.");
|
||||
@@ -78,9 +87,9 @@ async function haloGateExecutorCreateWs(logCallback, newCommandCallback) {
|
||||
await wsp.open();
|
||||
}
|
||||
|
||||
async function haloGateExecutorUserConfirm(logCallback) {
|
||||
async function haloGateExecutorUserConfirm(logCallback: ExecutorLogCallback) {
|
||||
let res;
|
||||
let nonce = currentCmd.nonce;
|
||||
const nonce = currentCmd.nonce;
|
||||
|
||||
logCallback('Please tap HaLo tag to the back of your smartphone and hold it for a while...');
|
||||
|
||||
@@ -90,13 +99,15 @@ async function haloGateExecutorUserConfirm(logCallback) {
|
||||
output: await execHaloCmdWeb(currentCmd.command)
|
||||
};
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
|
||||
res = {
|
||||
status: "exception",
|
||||
exception: {
|
||||
kind: e.constructor.name,
|
||||
name: e.name,
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
kind: err.constructor.name,
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +115,7 @@ async function haloGateExecutorUserConfirm(logCallback) {
|
||||
logCallback('Command executed, sending result over the network...');
|
||||
|
||||
currentCmd = null;
|
||||
wsp.sendPacked({
|
||||
wsp!.sendPacked({
|
||||
"type": "executed_cmd",
|
||||
"payload": await jweUtil.encrypt({
|
||||
response: res,
|
||||
@@ -2,10 +2,19 @@ import QRCode from "qrcode";
|
||||
import WebSocketAsPromised from "websocket-as-promised";
|
||||
import crypto from "crypto";
|
||||
import {JWEUtil} from "../jwe_util.js";
|
||||
import {HaloLogicError, HaloTagError, NFCBadTransportError, NFCAbortedError, NFCOperationError} from "../exceptions.js";
|
||||
import {
|
||||
HaloLogicError,
|
||||
HaloTagError,
|
||||
NFCBadTransportError,
|
||||
NFCAbortedError,
|
||||
NFCOperationError,
|
||||
NFCGatewayUnexpectedError
|
||||
} from "../exceptions.js";
|
||||
import {webDebug} from "../util.js";
|
||||
import {GatewayWelcomeMsg, HaloCommandObject} from "../../types.js";
|
||||
|
||||
function makeQR(url) {
|
||||
|
||||
function makeQR(url: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
QRCode.toDataURL(url, function (err, url) {
|
||||
if (err) {
|
||||
@@ -18,7 +27,20 @@ function makeQR(url) {
|
||||
}
|
||||
|
||||
class HaloGateway {
|
||||
constructor(gatewayServer, options) {
|
||||
private jweUtil: JWEUtil;
|
||||
private isRunning: boolean;
|
||||
private hasExecutor: boolean;
|
||||
private closeTimeout: NodeJS.Timeout | null;
|
||||
|
||||
private lastCommand: null;
|
||||
private gatewayServer: string;
|
||||
private gatewayServerHttp: string;
|
||||
|
||||
private ws: WebSocketAsPromised;
|
||||
|
||||
constructor(gatewayServer: string, options: {
|
||||
createWebSocket?: (url: string) => WebSocket
|
||||
}) {
|
||||
this.jweUtil = new JWEUtil();
|
||||
this.isRunning = false;
|
||||
this.hasExecutor = false;
|
||||
@@ -28,9 +50,9 @@ class HaloGateway {
|
||||
this.gatewayServer = gatewayServer;
|
||||
|
||||
options = Object.assign({}, options);
|
||||
let createWebSocket = options.createWebSocket ? options.createWebSocket : (url) => new WebSocket(url);
|
||||
const createWebSocket = options.createWebSocket ? options.createWebSocket : (url: string) => new WebSocket(url);
|
||||
|
||||
let urlObj = new URL(gatewayServer);
|
||||
const urlObj = new URL(gatewayServer);
|
||||
|
||||
if (urlObj.protocol === 'wss:') {
|
||||
urlObj.protocol = 'https:';
|
||||
@@ -51,13 +73,13 @@ class HaloGateway {
|
||||
this.ws = new WebSocketAsPromised(this.gatewayServer + '/ws?side=requestor', {
|
||||
createWebSocket: url => createWebSocket(url),
|
||||
packMessage: data => JSON.stringify(data),
|
||||
unpackMessage: data => JSON.parse(data),
|
||||
unpackMessage: data => JSON.parse(data as string),
|
||||
attachRequestId: (data, requestId) => Object.assign({uid: requestId}, data),
|
||||
extractRequestId: data => data && data.uid
|
||||
});
|
||||
|
||||
this.ws.onSend.addListener(data => {
|
||||
let obj = JSON.parse(data);
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.type === "request_cmd") {
|
||||
this.lastCommand = obj;
|
||||
@@ -93,7 +115,7 @@ class HaloGateway {
|
||||
|
||||
waitForWelcomePacket() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let welcomeWaitTimeout = setTimeout(() => {
|
||||
const welcomeWaitTimeout = setTimeout(() => {
|
||||
reject(new NFCBadTransportError("Server doesn't send welcome packet for 6 seconds after accepting the connection."));
|
||||
}, 6000);
|
||||
|
||||
@@ -111,13 +133,13 @@ class HaloGateway {
|
||||
}
|
||||
|
||||
async startPairing() {
|
||||
let sharedKey = await this.jweUtil.generateKey();
|
||||
const sharedKey = await this.jweUtil.generateKey();
|
||||
|
||||
let waitPromise = this.waitForWelcomePacket();
|
||||
const waitPromise = this.waitForWelcomePacket();
|
||||
const promiseRes = await Promise.all([this.ws.open(), waitPromise]);
|
||||
const welcomeMsg = promiseRes[1];
|
||||
const welcomeMsg = promiseRes[1] as GatewayWelcomeMsg;
|
||||
|
||||
let serverVersion = welcomeMsg.serverVersion;
|
||||
const serverVersion = welcomeMsg.serverVersion;
|
||||
|
||||
/**
|
||||
* URL format in the QR Code:
|
||||
@@ -135,8 +157,8 @@ class HaloGateway {
|
||||
* example:
|
||||
* https://dev-gate.example.com/e?id=-l6QxdU3xLyDTR2oT7bjnw#!/3LKNuIJV0Ltp0dhNw09tCQ/
|
||||
*/
|
||||
let execURL = this.gatewayServerHttp + '?id=' + welcomeMsg.sessionId + '#!/' + sharedKey + '/';
|
||||
let qrCode = await makeQR(execURL);
|
||||
const execURL = this.gatewayServerHttp + '?id=' + welcomeMsg.sessionId + '#!/' + sharedKey + '/';
|
||||
const qrCode = await makeQR(execURL);
|
||||
|
||||
return {
|
||||
execURL: execURL,
|
||||
@@ -159,7 +181,7 @@ class HaloGateway {
|
||||
})
|
||||
}
|
||||
|
||||
async execHaloCmd(command) {
|
||||
async execHaloCmd(command: HaloCommandObject) {
|
||||
webDebug('[halo-requestor] called execHaloCmd()', command);
|
||||
|
||||
if (this.isRunning) {
|
||||
@@ -178,7 +200,7 @@ class HaloGateway {
|
||||
}
|
||||
|
||||
this.isRunning = true;
|
||||
let nonce = crypto.randomBytes(8).toString('hex');
|
||||
const nonce = crypto.randomBytes(8).toString('hex');
|
||||
|
||||
try {
|
||||
webDebug('[halo-requestor] sending request to execute command', nonce, command);
|
||||
@@ -194,7 +216,7 @@ class HaloGateway {
|
||||
});
|
||||
} catch (e) {
|
||||
webDebug('[halo-requestor] exception when trying to sendRequest', e);
|
||||
throw new NFCBadTransportError('Failed to send request: ' + e.toString());
|
||||
throw new NFCBadTransportError('Failed to send request: ' + (<Error> e).toString());
|
||||
}
|
||||
|
||||
if (res.type !== "result_cmd") {
|
||||
@@ -217,7 +239,7 @@ class HaloGateway {
|
||||
throw new NFCBadTransportError("Mismatched nonce in reply.");
|
||||
}
|
||||
|
||||
let resolution = out.response;
|
||||
const resolution = out.response;
|
||||
|
||||
if (resolution.status === "success") {
|
||||
webDebug('[halo-requestor] returning with success', resolution.output);
|
||||
@@ -229,21 +251,20 @@ class HaloGateway {
|
||||
|
||||
switch (resolution.exception.kind) {
|
||||
case 'HaloLogicError':
|
||||
e = new HaloLogicError(resolution.exception.message);
|
||||
e = new HaloLogicError(resolution.exception.message, resolution.exception.stack);
|
||||
break;
|
||||
case 'HaloTagError':
|
||||
e = new HaloTagError(resolution.exception.name, resolution.exception.message);
|
||||
e = new HaloTagError(resolution.exception.name, resolution.exception.message, resolution.exception.stack);
|
||||
break;
|
||||
case 'NFCOperationError':
|
||||
e = new NFCOperationError(resolution.exception.message);
|
||||
e = new NFCOperationError(resolution.exception.message, resolution.exception.stack);
|
||||
break;
|
||||
default:
|
||||
e = new Error("Unexpected exception occurred while executing the command. " +
|
||||
resolution.exception.name + ": " + resolution.exception.message);
|
||||
e = new NFCGatewayUnexpectedError("Unexpected exception occurred while executing the command. " +
|
||||
resolution.exception.name + ": " + resolution.exception.message, resolution.exception.stack);
|
||||
break;
|
||||
}
|
||||
|
||||
e.stackOnExecutor = resolution.exception.stack;
|
||||
webDebug('[halo-requestor] throwing exception as call result', e);
|
||||
throw e;
|
||||
} else {
|
||||
@@ -4,13 +4,15 @@ const subtle = crypto.webcrypto && crypto.webcrypto.subtle ? crypto.webcrypto.su
|
||||
import * as jose from 'jose';
|
||||
|
||||
class JWEUtil {
|
||||
constructor() {
|
||||
private sharedKeyObj: CryptoKey | null;
|
||||
|
||||
constructor() {
|
||||
this.sharedKeyObj = null;
|
||||
}
|
||||
|
||||
async generateKey() {
|
||||
let sharedKey = crypto.randomBytes(16)
|
||||
let sharedKeyEnc = sharedKey
|
||||
const sharedKey = crypto.randomBytes(16)
|
||||
const sharedKeyEnc = sharedKey
|
||||
.toString('base64')
|
||||
.replace('+', '-')
|
||||
.replace('/', '_')
|
||||
@@ -20,10 +22,11 @@ class JWEUtil {
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
]);
|
||||
|
||||
return sharedKeyEnc;
|
||||
}
|
||||
|
||||
async loadKey(sharedKey) {
|
||||
async loadKey(sharedKey: string) {
|
||||
// automatically add "=" padding if it's not present
|
||||
let padLen = (-sharedKey.length % 3) + 3;
|
||||
|
||||
@@ -41,14 +44,22 @@ class JWEUtil {
|
||||
]);
|
||||
}
|
||||
|
||||
async encrypt(data) {
|
||||
async encrypt(data: unknown) {
|
||||
if (this.sharedKeyObj === null) {
|
||||
throw new Error("Key is not loaded nor was generated.");
|
||||
}
|
||||
|
||||
return await new jose.CompactEncrypt(
|
||||
new TextEncoder().encode(JSON.stringify(data)))
|
||||
.setProtectedHeader({alg: 'dir', enc: 'A128GCM'})
|
||||
.encrypt(this.sharedKeyObj);
|
||||
}
|
||||
|
||||
async decrypt(jwe) {
|
||||
async decrypt(jwe: string) {
|
||||
if (this.sharedKeyObj === null) {
|
||||
throw new Error("Key is not loaded nor was generated.");
|
||||
}
|
||||
|
||||
const hdr = jose.decodeProtectedHeader(jwe);
|
||||
|
||||
if (Object.keys(hdr).length !== 2 || hdr.alg !== "dir" || hdr.enc !== "A128GCM") {
|
||||
@@ -3,6 +3,7 @@
|
||||
* Copyright by Arx Research, Inc., a Delaware corporation
|
||||
* License: MIT
|
||||
*/
|
||||
import {KeyFlags} from "../types.js";
|
||||
|
||||
const KEY_FLAGS = {
|
||||
KEYFLG_IS_PWD_PROTECTED: 0x01,
|
||||
@@ -13,7 +14,7 @@ const KEY_FLAGS = {
|
||||
KEYFLG_IS_EXPORTED: 0x20
|
||||
}
|
||||
|
||||
function parseKeyFlags(keyFlags) {
|
||||
function parseKeyFlags(keyFlags: number): KeyFlags {
|
||||
return {
|
||||
isPasswordProtected: !!(keyFlags & KEY_FLAGS.KEYFLG_IS_PWD_PROTECTED),
|
||||
hasMandatoryPassword: !!(keyFlags & KEY_FLAGS.KEYFLG_MANDATORY_PASSWORD),
|
||||
@@ -6,33 +6,34 @@
|
||||
|
||||
import {HaloLogicError, HaloTagError} from "../api/common.js";
|
||||
import elliptic from 'elliptic';
|
||||
import {HaloCommandObject, HaloResponseObject} from "../types.js";
|
||||
|
||||
const ec = new elliptic.ec('secp256k1');
|
||||
|
||||
function assert(condition) {
|
||||
function assert(condition: unknown) {
|
||||
if (!condition) {
|
||||
throw new Error("Assertion failed.");
|
||||
}
|
||||
}
|
||||
|
||||
class SkipTest extends Error {
|
||||
constructor(message) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "SkipTest";
|
||||
}
|
||||
}
|
||||
|
||||
const tests = [
|
||||
["testLegacySign1", async function(driver, exec) {
|
||||
["testLegacySign1", async function(driver: string, exec: (cmd: HaloCommandObject) => Promise<HaloResponseObject>) {
|
||||
if (driver !== 'webnfc') {
|
||||
let resPkeys = await exec({
|
||||
const resPkeys = await exec({
|
||||
"name": "get_pkeys"
|
||||
});
|
||||
|
||||
let pk1 = ec.keyFromPublic(resPkeys.publicKeys[1], 'hex');
|
||||
let digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
const pk1 = ec.keyFromPublic(resPkeys.publicKeys[1], 'hex');
|
||||
const digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
|
||||
let res = await exec({
|
||||
const res = await exec({
|
||||
"name": "sign",
|
||||
"keyNo": 1,
|
||||
"digest": digest,
|
||||
@@ -41,32 +42,32 @@ const tests = [
|
||||
|
||||
assert(pk1.verify(digest, res.signature.der));
|
||||
} else {
|
||||
let digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
const digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
|
||||
let res = await exec({
|
||||
const res = await exec({
|
||||
"name": "sign",
|
||||
"keyNo": 1,
|
||||
"digest": digest,
|
||||
"legacySignCommand": true
|
||||
});
|
||||
|
||||
let pk1 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
const pk1 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
assert(pk1.verify(digest, res.signature.der));
|
||||
}
|
||||
}],
|
||||
["testSign1", async function(driver, exec) {
|
||||
let digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
["testSign1", async function(driver: string, exec: (cmd: HaloCommandObject) => Promise<HaloResponseObject>) {
|
||||
const digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
|
||||
let res = await exec({
|
||||
const res = await exec({
|
||||
"name": "sign",
|
||||
"keyNo": 1,
|
||||
"digest": digest
|
||||
});
|
||||
|
||||
let pk1 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
const pk1 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
assert(pk1.verify(digest, res.signature.der));
|
||||
}],
|
||||
["testKeyGen3", async function(driver, exec) {
|
||||
["testKeyGen3", async function(driver: string, exec: (cmd: HaloCommandObject) => Promise<HaloResponseObject>) {
|
||||
let resGenKey;
|
||||
|
||||
try {
|
||||
@@ -88,40 +89,40 @@ const tests = [
|
||||
|
||||
assert(resGenKey.needsConfirm);
|
||||
|
||||
let resGenKeyConfirm = await exec({
|
||||
const resGenKeyConfirm = await exec({
|
||||
"name": "gen_key_confirm",
|
||||
"publicKey": resGenKey.publicKey
|
||||
});
|
||||
|
||||
assert(resGenKeyConfirm.status === "ok");
|
||||
|
||||
let resGenKeyFinalize = await exec({
|
||||
const resGenKeyFinalize = await exec({
|
||||
"name": "gen_key_finalize"
|
||||
});
|
||||
|
||||
assert(resGenKeyConfirm.status === "ok");
|
||||
}],
|
||||
["testSign3", async function(driver, exec) {
|
||||
let digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
["testSign3", async function(driver: string, exec: (cmd: HaloCommandObject) => Promise<HaloResponseObject>) {
|
||||
const digest = "b64ab259577c3a28fda62c8e64744c8dd42a82155fbca7de02a1d85d8383d4e1";
|
||||
|
||||
let res = await exec({
|
||||
const res = await exec({
|
||||
"name": "sign",
|
||||
"keyNo": 3,
|
||||
"digest": digest
|
||||
});
|
||||
|
||||
let pk3 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
const pk3 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
assert(pk3.verify(digest, res.signature.der));
|
||||
}],
|
||||
["testSign1Typed", async function(driver, exec) {
|
||||
let domain = {
|
||||
["testSign1Typed", async function(driver: string, exec: (cmd: HaloCommandObject) => Promise<HaloResponseObject>) {
|
||||
const domain = {
|
||||
name: 'Ether Mail',
|
||||
version: '1',
|
||||
chainId: 1,
|
||||
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
|
||||
};
|
||||
|
||||
let types = {
|
||||
const types = {
|
||||
Person: [
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'wallet', type: 'address' }
|
||||
@@ -133,7 +134,7 @@ const tests = [
|
||||
]
|
||||
};
|
||||
|
||||
let value = {
|
||||
const value = {
|
||||
from: {
|
||||
name: 'Cow',
|
||||
wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'
|
||||
@@ -145,13 +146,13 @@ const tests = [
|
||||
contents: 'Hello, Bob!'
|
||||
};
|
||||
|
||||
let res = await exec({
|
||||
const res = await exec({
|
||||
"name": "sign",
|
||||
"keyNo": 1,
|
||||
"typedData": {domain, types, value}
|
||||
});
|
||||
|
||||
let pk1 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
const pk1 = ec.keyFromPublic(res.publicKey, 'hex');
|
||||
assert(res.input.digest === "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2");
|
||||
assert(res.input.primaryType === "Mail");
|
||||
assert(res.input.domainHash === "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f");
|
||||
@@ -159,20 +160,21 @@ const tests = [
|
||||
}]
|
||||
];
|
||||
|
||||
async function __runTestSuite(__unsafe, driver, exec) {
|
||||
async function __runTestSuite(__unsafe: {__this_is_unsafe: true}, driver: string, exec: (cmd: HaloCommandObject) => Promise<HaloResponseObject>) {
|
||||
if (!__unsafe.__this_is_unsafe) {
|
||||
throw new HaloLogicError("This method is used for internal testing, shouldn't be invoked directly.");
|
||||
}
|
||||
|
||||
let passed = [];
|
||||
let skipped = [];
|
||||
let failed = [];
|
||||
const passed = [];
|
||||
const skipped = [];
|
||||
const failed = [];
|
||||
|
||||
for (let testObj of tests) {
|
||||
for (const testObj of tests) {
|
||||
console.log(testObj[0]);
|
||||
|
||||
try {
|
||||
await testObj[1](driver, exec);
|
||||
const testFunc = testObj[1] as (driver: string, exec: (cmd: HaloCommandObject) => Promise<HaloResponseObject>) => Promise<void>;
|
||||
await testFunc(driver, exec);
|
||||
console.log(testObj[0], 'PASSED');
|
||||
passed.push(testObj[0]);
|
||||
} catch (e) {
|
||||
@@ -6,31 +6,37 @@
|
||||
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
import elliptic from 'elliptic';
|
||||
import {ethers} from 'ethers';
|
||||
import {ethers, Signature} from 'ethers';
|
||||
import {HaloLogicError} from "./exceptions.js";
|
||||
import crypto from "crypto";
|
||||
import {BN} from 'bn.js';
|
||||
import {PublicKeyList} from "../types.js";
|
||||
|
||||
const ec = new elliptic.ec('secp256k1');
|
||||
|
||||
const SECP256k1_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
||||
const BJJ_ORDER = 0x060c89ce5c263405370a08b6d0302b0bab3eedb83920ee0a677297dc392126f1n;
|
||||
|
||||
function hex2arr(hexString) {
|
||||
interface SignatureObj {
|
||||
r: string
|
||||
s: string
|
||||
}
|
||||
|
||||
function hex2arr(hexString: string) {
|
||||
return new Uint8Array(
|
||||
hexString.match(/.{1,2}/g).map(
|
||||
hexString.match(/.{1,2}/g)!.map(
|
||||
byte => parseInt(byte, 16)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function arr2hex(buffer) {
|
||||
function arr2hex(buffer: number[] | Uint8Array) {
|
||||
return [...new Uint8Array(buffer)]
|
||||
.map(x => x.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
function parsePublicKeys(buffer) {
|
||||
function parsePublicKeys(buffer: Buffer | string): PublicKeyList {
|
||||
let buf;
|
||||
|
||||
if (typeof buffer === "string") {
|
||||
@@ -39,17 +45,17 @@ function parsePublicKeys(buffer) {
|
||||
buf = Buffer.from(buffer);
|
||||
}
|
||||
|
||||
let out = {};
|
||||
const out: PublicKeyList = {};
|
||||
let keyNo = 1;
|
||||
|
||||
while (true) {
|
||||
let keyLength = buf[0];
|
||||
const keyLength = buf[0];
|
||||
|
||||
if (typeof keyLength === "undefined" || keyLength === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
let key = buf.slice(1, 1 + keyLength);
|
||||
const key = buf.slice(1, 1 + keyLength);
|
||||
out[keyNo] = key.toString('hex');
|
||||
buf = buf.slice(1 + keyLength);
|
||||
keyNo++;
|
||||
@@ -58,25 +64,25 @@ function parsePublicKeys(buffer) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseSig(res, curveOrder) {
|
||||
function parseSig(res: Buffer, curveOrder: bigint) {
|
||||
if (res[0] !== 0x30 || res[2] !== 0x02) {
|
||||
throw new HaloLogicError("Unable to parse signature, unexpected header (1).");
|
||||
}
|
||||
|
||||
let rLen = res[3];
|
||||
const rLen = res[3];
|
||||
|
||||
if (res[rLen + 4] !== 0x02) {
|
||||
throw new HaloLogicError("Unable to parse signature, unexpected header (2).");
|
||||
}
|
||||
|
||||
let sLen = res[rLen + 5];
|
||||
const sLen = res[rLen + 5];
|
||||
|
||||
if (res.length !== rLen + 4 + 2 + sLen) {
|
||||
throw new HaloLogicError("Unable to parse signature, unexpected length.");
|
||||
}
|
||||
|
||||
let r = res.slice(4, rLen + 4);
|
||||
let s = res.slice(rLen + 4 + 2, rLen + 4 + 2 + sLen);
|
||||
const r = res.slice(4, rLen + 4);
|
||||
const s = res.slice(rLen + 4 + 2, rLen + 4 + 2 + sLen);
|
||||
let rn = BigInt('0x' + r.toString('hex'));
|
||||
let sn = BigInt('0x' + s.toString('hex'));
|
||||
|
||||
@@ -95,12 +101,12 @@ function parseSig(res, curveOrder) {
|
||||
};
|
||||
}
|
||||
|
||||
function sigToDer(sig) {
|
||||
let r = BigInt('0x' + sig.r);
|
||||
let s = BigInt('0x' + sig.s);
|
||||
function sigToDer(sig: SignatureObj) {
|
||||
const r = BigInt('0x' + sig.r);
|
||||
const s = BigInt('0x' + sig.s);
|
||||
|
||||
let padR = r.toString(16).length % 2 ? '0' : '';
|
||||
let padS = s.toString(16).length % 2 ? '0' : '';
|
||||
const padR = r.toString(16).length % 2 ? '0' : '';
|
||||
const padS = s.toString(16).length % 2 ? '0' : '';
|
||||
|
||||
let encR = Buffer.from(padR + r.toString(16), 'hex');
|
||||
let encS = Buffer.from(padS + s.toString(16), 'hex');
|
||||
@@ -125,9 +131,9 @@ function sigToDer(sig) {
|
||||
]);
|
||||
}
|
||||
|
||||
function convertSignature(digest, signature, publicKey, curveOrder) {
|
||||
signature = Buffer.from(signature, "hex");
|
||||
let fixedSig = parseSig(signature, curveOrder);
|
||||
function convertSignature(digest: string, signature: string, publicKey: string, curveOrder: bigint) {
|
||||
const sigBuf = Buffer.from(signature, "hex");
|
||||
const fixedSig = parseSig(sigBuf, curveOrder);
|
||||
|
||||
let recoveryParam = null;
|
||||
|
||||
@@ -142,12 +148,12 @@ function convertSignature(digest, signature, publicKey, curveOrder) {
|
||||
throw new HaloLogicError("Failed to get recovery param.");
|
||||
}
|
||||
|
||||
let finalSig = '0x' + fixedSig.r
|
||||
const finalSig = '0x' + fixedSig.r
|
||||
+ fixedSig.s
|
||||
+ Buffer.from([27 + recoveryParam]).toString('hex');
|
||||
|
||||
let pkeyAddress = ethers.computeAddress('0x' + publicKey);
|
||||
let recoveredAddress = ethers.recoverAddress('0x' + digest, finalSig);
|
||||
const pkeyAddress = ethers.computeAddress('0x' + publicKey);
|
||||
const recoveredAddress = ethers.recoverAddress('0x' + digest, finalSig);
|
||||
|
||||
if (pkeyAddress !== recoveredAddress) {
|
||||
throw new HaloLogicError("Failed to correctly recover public key from the signature.");
|
||||
@@ -158,16 +164,16 @@ function convertSignature(digest, signature, publicKey, curveOrder) {
|
||||
...fixedSig,
|
||||
v: recoveryParam + 0x1b
|
||||
},
|
||||
"der": sigToDer(parseSig(signature, curveOrder)).toString('hex'),
|
||||
"ether": finalSig.toString('hex')
|
||||
"der": sigToDer(parseSig(sigBuf, curveOrder)).toString('hex'),
|
||||
"ether": finalSig
|
||||
};
|
||||
}
|
||||
|
||||
function recoverPublicKey(digest, signature, curveOrder) {
|
||||
let out = [];
|
||||
function recoverPublicKey(digest: string, signature: string, curveOrder: bigint) {
|
||||
const out = [];
|
||||
|
||||
signature = Buffer.from(signature, "hex");
|
||||
let fixedSig = parseSig(signature, curveOrder);
|
||||
const sigBuf = Buffer.from(signature, "hex");
|
||||
const fixedSig = parseSig(sigBuf, curveOrder);
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
out.push(ec.recoverPubKey(new BN(digest, 16), fixedSig, i).encode('hex'));
|
||||
@@ -176,11 +182,15 @@ function recoverPublicKey(digest, signature, curveOrder) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function mode(arr) {
|
||||
function mode<Type>(arr: Type[]): Type {
|
||||
if (arr.length <= 0) {
|
||||
throw new Error("Zero-length array.");
|
||||
}
|
||||
|
||||
return arr.sort((a, b) =>
|
||||
arr.filter(v => v === a).length
|
||||
- arr.filter(v => v === b).length
|
||||
).pop();
|
||||
).pop()!;
|
||||
}
|
||||
|
||||
function randomBuffer() {
|
||||
@@ -191,7 +201,7 @@ function isWebDebugEnabled() {
|
||||
return typeof window !== "undefined" && window.localStorage && window.localStorage.getItem("DEBUG_LIBHALO_WEB") === "1";
|
||||
}
|
||||
|
||||
function webDebug(...args) {
|
||||
function webDebug(...args: unknown[]) {
|
||||
if (isWebDebugEnabled()) {
|
||||
console.log(...args);
|
||||
}
|
||||
229
core/src.ts/types.ts
Normal file
229
core/src.ts/types.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import {Buffer} from 'buffer/index.js';
|
||||
|
||||
|
||||
// These types should only be used internally for the command dispatchers.
|
||||
// There are specific arg/return types defined for each HaLo command.
|
||||
|
||||
// eslint-disable-next-line
|
||||
export type HaloCommandObject = any;
|
||||
// eslint-disable-next-line
|
||||
export type HaloResponseObject = any;
|
||||
|
||||
export interface Card {
|
||||
type: string
|
||||
atr: Buffer
|
||||
}
|
||||
|
||||
export interface ReaderEventListener {
|
||||
(eventName: 'card', listener: (card: Card) => void): void;
|
||||
(eventName: 'card.off', listener: (card: Card) => void): void;
|
||||
(eventName: 'error', listener: (err: Error) => void): void;
|
||||
(eventName: 'end', listener: () => void): void;
|
||||
}
|
||||
|
||||
export interface Reader {
|
||||
reader: {
|
||||
name: string
|
||||
}
|
||||
autoProcessing: boolean
|
||||
transmit: (data: Buffer, responseMaxLength: number) => Promise<Buffer>;
|
||||
on: ReaderEventListener
|
||||
}
|
||||
|
||||
export interface TransceiveFunc {
|
||||
(data: Buffer): Promise<Buffer>;
|
||||
}
|
||||
|
||||
export interface StatusCallbackDetails {
|
||||
execMethod: string
|
||||
execStep: string
|
||||
cancelScan: () => void
|
||||
}
|
||||
|
||||
export interface PublicKeyList {
|
||||
[keyNo: number]: string
|
||||
}
|
||||
|
||||
export interface ExecReturnStruct {
|
||||
result: string
|
||||
extra: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ExecHaloCmdOptions {
|
||||
method: "credential" | "pcsc" | "webnfc" | "nfc-manager"
|
||||
exec: (command: Buffer, options?: ExecOptions) => Promise<ExecReturnStruct>
|
||||
}
|
||||
|
||||
export interface ExecOptions {
|
||||
statusCallback?: (status: string, statusDetails: StatusCallbackDetails) => void;
|
||||
}
|
||||
|
||||
export type HaloWebMethod = "credential" | "webnfc";
|
||||
|
||||
export interface ExecHaloCmdWebOptions extends ExecOptions {
|
||||
method?: HaloWebMethod
|
||||
noDebounce?: boolean
|
||||
}
|
||||
|
||||
export interface EmptyOptions {
|
||||
|
||||
}
|
||||
|
||||
export interface RNNFCManagerIsoDepHandler {
|
||||
transceive: (data: number[]) => Promise<number[]>;
|
||||
}
|
||||
|
||||
export interface RNNFCManager {
|
||||
isoDepHandler: RNNFCManagerIsoDepHandler
|
||||
}
|
||||
|
||||
export interface ExecOptions {
|
||||
noCheck?: boolean
|
||||
pcscExecLayer?: "u2f"
|
||||
}
|
||||
|
||||
export interface GatewayWelcomeMsg {
|
||||
serverVersion: {
|
||||
tagName: string
|
||||
commitId: string
|
||||
version: number[]
|
||||
}
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface BridgeOptions {
|
||||
createWebSocket?: (url: string) => WebSocket
|
||||
}
|
||||
|
||||
export interface BridgeEvent {
|
||||
event: "handle_added" | "handle_removed" | "handle_not_compatible" | "reader_added" | "reader_removed" | "exec_success" | "exec_exception"
|
||||
uid: string | null
|
||||
}
|
||||
|
||||
export interface BridgeHandleEvent extends BridgeEvent {
|
||||
event: "handle_added" | "handle_removed"
|
||||
uid: null
|
||||
data: {
|
||||
handle: string
|
||||
reader_name: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface BridgeHandleAdded extends BridgeHandleEvent {
|
||||
event: "handle_added"
|
||||
}
|
||||
|
||||
export interface BridgeHandleRemoved extends BridgeHandleEvent {
|
||||
event: "handle_removed"
|
||||
}
|
||||
|
||||
export interface BridgeHandleNotCompatibleEvent extends BridgeEvent {
|
||||
event: "handle_not_compatible"
|
||||
uid: null
|
||||
data: {
|
||||
reader_name: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface FindBridgeOptions {
|
||||
wsPort?: number
|
||||
wssPort?: number
|
||||
createWebSocket?: (url: string) => WebSocket
|
||||
diagnose?: boolean
|
||||
}
|
||||
|
||||
export interface FindBridgeOptionsNoDiagnose extends FindBridgeOptions {
|
||||
diagnose?: false
|
||||
}
|
||||
|
||||
export interface FindBridgeOptionsDiagnose extends FindBridgeOptions {
|
||||
diagnose: true
|
||||
}
|
||||
|
||||
export interface FindBridgeResult {
|
||||
urls: string[]
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
export interface KeyFlags {
|
||||
isPasswordProtected: boolean
|
||||
hasMandatoryPassword: boolean
|
||||
rawSignCommandNotUsed: boolean
|
||||
isImported: boolean
|
||||
isExported: boolean
|
||||
}
|
||||
|
||||
export interface KeyState extends KeyFlags {
|
||||
failedAuthCounter: number
|
||||
}
|
||||
|
||||
export interface HaloCmdCFGNDEF {
|
||||
flagUseText: boolean
|
||||
flagHidePk1: boolean
|
||||
flagHidePk2: boolean
|
||||
flagHidePk3: boolean
|
||||
flagShowPk1Attest: boolean
|
||||
flagShowPk2Attest: boolean
|
||||
flagHideRNDSIG: boolean
|
||||
flagHideCMDRES: boolean
|
||||
|
||||
flagShowPk3Attest: boolean
|
||||
flagShowLatch1Sig: boolean
|
||||
flagShowLatch2Sig: boolean
|
||||
flagLegacyStatic: boolean
|
||||
flagShowPkN: boolean
|
||||
flagShowPkNAttest: boolean
|
||||
flagRNDSIGUseBJJ62: boolean
|
||||
|
||||
pkN: KeySlotNo
|
||||
}
|
||||
|
||||
export interface HaloResCFGNDEF {
|
||||
status: "ok"
|
||||
cfgBytes: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdGenKey {
|
||||
keyNo: KeySlotNo
|
||||
entropy?: HexString
|
||||
}
|
||||
|
||||
export interface HaloResGenKeyV1 {
|
||||
needsConfirmPK: true
|
||||
publicKey: HexString
|
||||
}
|
||||
|
||||
export interface HaloResGenKeyV2 {
|
||||
needsConfirmPK: false
|
||||
rootPublicKey: HexString
|
||||
rootAttestSig: HexString
|
||||
}
|
||||
|
||||
export type HaloResGenKey = HaloResGenKeyV1 | HaloResGenKeyV2;
|
||||
|
||||
export interface HaloCmdGenKeyConfirm {
|
||||
keyNo: KeySlotNo
|
||||
publicKey: HexString
|
||||
}
|
||||
|
||||
export interface HaloResGenKeyConfirm {
|
||||
rootPublicKey: HexString
|
||||
rootAttestSig: HexString
|
||||
}
|
||||
|
||||
export interface HaloCmdGenKeyFinalize {
|
||||
keyNo: KeySlotNo
|
||||
password?: ASCIIString
|
||||
}
|
||||
|
||||
export interface HaloResGenKeyFinalize {
|
||||
publicKey: HexString
|
||||
attestSig: HexString
|
||||
}
|
||||
|
||||
export type KeySlotNo = number;
|
||||
export type ASCIIString = string;
|
||||
export type HexString = string;
|
||||
|
||||
export * from './types_webnfc.js';
|
||||
83
core/src.ts/types_webnfc.ts
Normal file
83
core/src.ts/types_webnfc.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
// Type definitions for Web NFC
|
||||
// Project: https://github.com/w3c/web-nfc
|
||||
// Definitions by: Takefumi Yoshii <https://github.com/takefumi-yoshii>
|
||||
// TypeScript Version: 3.9
|
||||
|
||||
// This type definitions referenced to WebIDL.
|
||||
// https://w3c.github.io/web-nfc/#actual-idl-index
|
||||
|
||||
export interface Window {
|
||||
NDEFMessage: NDEFMessage
|
||||
}
|
||||
export declare class NDEFMessage {
|
||||
constructor(messageInit: NDEFMessageInit)
|
||||
records: ReadonlyArray<NDEFRecord>
|
||||
}
|
||||
export declare interface NDEFMessageInit {
|
||||
records: NDEFRecordInit[]
|
||||
}
|
||||
|
||||
export declare type NDEFRecordDataSource = string | BufferSource | NDEFMessageInit
|
||||
|
||||
export interface Window {
|
||||
NDEFRecord: NDEFRecord
|
||||
}
|
||||
export declare class NDEFRecord {
|
||||
constructor(recordInit: NDEFRecordInit)
|
||||
readonly recordType: string
|
||||
readonly mediaType?: string
|
||||
readonly id?: string
|
||||
readonly data?: DataView
|
||||
readonly encoding?: string
|
||||
readonly lang?: string
|
||||
toRecords?: () => NDEFRecord[]
|
||||
}
|
||||
export declare interface NDEFRecordInit {
|
||||
recordType: string
|
||||
mediaType?: string
|
||||
id?: string
|
||||
encoding?: string
|
||||
lang?: string
|
||||
data?: NDEFRecordDataSource
|
||||
}
|
||||
|
||||
export declare type NDEFMessageSource = string | BufferSource | NDEFMessageInit
|
||||
|
||||
export interface Window {
|
||||
NDEFReader: NDEFReader
|
||||
}
|
||||
export declare class NDEFReader extends EventTarget {
|
||||
constructor()
|
||||
onreading: (this: this, event: NDEFReadingEvent) => any
|
||||
onreadingerror: (this: this, error: Event) => any
|
||||
scan: (options?: NDEFScanOptions) => Promise<void>
|
||||
write: (
|
||||
message: NDEFMessageSource,
|
||||
options?: NDEFWriteOptions
|
||||
) => Promise<void>
|
||||
makeReadOnly: (options?: NDEFMakeReadOnlyOptions) => Promise<void>
|
||||
}
|
||||
|
||||
export interface Window {
|
||||
NDEFReadingEvent: NDEFReadingEvent
|
||||
}
|
||||
export declare class NDEFReadingEvent extends Event {
|
||||
constructor(type: string, readingEventInitDict: NDEFReadingEventInit)
|
||||
serialNumber: string
|
||||
message: NDEFMessage
|
||||
}
|
||||
export interface NDEFReadingEventInit extends EventInit {
|
||||
serialNumber?: string
|
||||
message: NDEFMessageInit
|
||||
}
|
||||
|
||||
export interface NDEFWriteOptions {
|
||||
overwrite?: boolean
|
||||
signal?: AbortSignal
|
||||
}
|
||||
export interface NDEFMakeReadOnlyOptions {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
export interface NDEFScanOptions {
|
||||
signal: AbortSignal
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import {StatusCallbackDetails} from "../types.js";
|
||||
|
||||
const PROMPT_STYLES = `
|
||||
@keyframes __libhalo_popup_waitingRingGrow {
|
||||
0% {
|
||||
@@ -155,7 +157,7 @@ const PROMPT_HTML = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
function emulatedPromptStatusCallback(status, statusObj) {
|
||||
function emulatedPromptStatusCallback(status: string, statusObj: StatusCallbackDetails) {
|
||||
if (!document.getElementById('__libhalo_popup_stylesheet')) {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('id', '__libhalo_popup_stylesheet');
|
||||
@@ -185,9 +187,9 @@ function emulatedPromptStatusCallback(status, statusObj) {
|
||||
default: statusText = "<" + status + ">"; break;
|
||||
}
|
||||
|
||||
pdiv.innerText = statusText;
|
||||
cancelBtn.onclick = statusObj.cancelScan;
|
||||
rdiv.style.display = status !== 'finished' ? 'block' : 'none';
|
||||
pdiv!.innerText = statusText;
|
||||
cancelBtn!.onclick = statusObj.cancelScan;
|
||||
rdiv!.style.display = status !== 'finished' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -1,18 +1,19 @@
|
||||
import WebSocketAsPromised from 'websocket-as-promised';
|
||||
import {NFCBadTransportError} from "../halo/exceptions.js";
|
||||
import {FindBridgeOptions, FindBridgeOptionsDiagnose, FindBridgeOptionsNoDiagnose, FindBridgeResult} from "../types.js";
|
||||
|
||||
function haloCreateWs(url) {
|
||||
function haloCreateWs(url: string) {
|
||||
return new WebSocketAsPromised(url, {
|
||||
packMessage: data => JSON.stringify(data),
|
||||
unpackMessage: data => JSON.parse(data),
|
||||
unpackMessage: data => JSON.parse(data as string),
|
||||
attachRequestId: (data, requestId) => Object.assign({uid: requestId}, data),
|
||||
extractRequestId: data => data && data.uid
|
||||
});
|
||||
}
|
||||
|
||||
function runHealthCheck(url, openTimeout, createWebSocket) {
|
||||
function runHealthCheck(url: string, openTimeout: number, createWebSocket: (url: string) => WebSocket) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let closeTimeout = null;
|
||||
let closeTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
const pingUrl = url.includes('?') ? (url + '&ping=1') : (url + '?ping=1');
|
||||
const wsp = new WebSocketAsPromised(pingUrl, {
|
||||
@@ -45,12 +46,12 @@ function runHealthCheck(url, openTimeout, createWebSocket) {
|
||||
});
|
||||
}
|
||||
|
||||
function createChecks(wsPort, wssPort, createWebSocket) {
|
||||
function createChecks(wsPort: number, wssPort: number, createWebSocket: (url: string) => WebSocket) {
|
||||
// detect Firefox
|
||||
const isFirefox = typeof window !== "undefined" && window.hasOwnProperty("InternalError");
|
||||
const isFirefox = typeof window !== "undefined" && Object.prototype.hasOwnProperty.call(window, "InternalError");
|
||||
const openTimeout = isFirefox ? 10000 : 5000;
|
||||
|
||||
let checks = [
|
||||
const checks = [
|
||||
runHealthCheck('ws://127.0.0.1:' + wsPort + '/ws', openTimeout, createWebSocket)
|
||||
];
|
||||
|
||||
@@ -65,7 +66,10 @@ function createChecks(wsPort, wssPort, createWebSocket) {
|
||||
return checks;
|
||||
}
|
||||
|
||||
async function haloFindBridge(options) {
|
||||
async function haloFindBridge(options: FindBridgeOptionsNoDiagnose): Promise<string>;
|
||||
async function haloFindBridge(options: FindBridgeOptionsDiagnose): Promise<FindBridgeResult>;
|
||||
|
||||
async function haloFindBridge(options: FindBridgeOptions) {
|
||||
options = Object.assign({}, options) || {};
|
||||
|
||||
if (!options.wsPort) {
|
||||
@@ -80,14 +84,14 @@ async function haloFindBridge(options) {
|
||||
const wssPort = options.wssPort;
|
||||
const createWebSocket = options.createWebSocket
|
||||
? options.createWebSocket
|
||||
: (url) => new WebSocket(url);
|
||||
: (url: string) => new WebSocket(url);
|
||||
|
||||
if (options.diagnose) {
|
||||
let res = await Promise.allSettled(createChecks(wsPort, wssPort, createWebSocket));
|
||||
let urls = [];
|
||||
let errors = [];
|
||||
const res = await Promise.allSettled(createChecks(wsPort, wssPort, createWebSocket));
|
||||
const urls = [];
|
||||
const errors = [];
|
||||
|
||||
for (let o of res) {
|
||||
for (const o of res) {
|
||||
if (o.status === "fulfilled") {
|
||||
urls.push(o.value);
|
||||
} else {
|
||||
@@ -8,6 +8,8 @@ import * as all_exports from "./web_apis.js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
Object.keys(all_exports).forEach((key) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
window[key] = all_exports[key];
|
||||
});
|
||||
}
|
||||
8
core/tsconfig.commonjs.json
Normal file
8
core/tsconfig.commonjs.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node10",
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib.commonjs"
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importHelpers": true,
|
||||
"lib": [
|
||||
"es2020",
|
||||
"es5",
|
||||
"es2022",
|
||||
"dom"
|
||||
],
|
||||
"moduleResolution": "node16",
|
||||
@@ -16,7 +15,7 @@
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedLocals": false,
|
||||
"preserveSymlinks": true,
|
||||
"preserveWatchOutput": true,
|
||||
"pretty": false,
|
||||
@@ -29,7 +28,7 @@
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/**/*.tsx",
|
||||
"./src.ts/**/*.ts",
|
||||
"./src.ts/**/*.js"
|
||||
],
|
||||
"exclude": []
|
||||
|
||||
@@ -10,7 +10,7 @@ const __dirname = dirname(__filename);
|
||||
|
||||
export default {
|
||||
entry: {
|
||||
app: './src.ts/web/weblib.js',
|
||||
app: './src.ts/web/weblib.ts',
|
||||
},
|
||||
output: {
|
||||
filename: 'libhalo.js',
|
||||
@@ -21,14 +21,17 @@ export default {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
test: /\.ts$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
extensions: ['.ts', '.js'],
|
||||
extensionAlias: {
|
||||
'.js': ['.js', '.ts'],
|
||||
},
|
||||
fallback: {
|
||||
vm: false,
|
||||
buffer: resolve(__dirname, './node_modules/buffer/index.js'),
|
||||
@@ -41,7 +44,9 @@ export default {
|
||||
apply: (compiler) => {
|
||||
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
|
||||
compilation.getAssets().forEach((asset) => {
|
||||
fs.copyFileSync('./dist/' + asset.name, '../cli/assets/static/' + asset.name);
|
||||
if (asset.name === 'libhalo.js') {
|
||||
fs.copyFileSync('./dist/' + asset.name, '../cli/assets/static/' + asset.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
724
core/yarn.lock
724
core/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user