mirror of
https://github.com/arx-research/libhalo.git
synced 2026-01-10 13:48:08 -05:00
Add bridge server functionality (#114)
This commit is contained in:
committed by
GitHub
parent
92bc7d922f
commit
d00633f9d4
19
cli/args.js
19
cli/args.js
@@ -27,6 +27,25 @@ if (process.env.__UNSAFE_ENABLE_TESTS === "1") {
|
||||
});
|
||||
}
|
||||
|
||||
let serverParser = subparsers.add_parser("server", {help: "Run local WebSocket server."});
|
||||
serverParser.add_argument("-l", "--listen-host", {
|
||||
help: "IP where the server should bind",
|
||||
default: "127.0.0.1",
|
||||
dest: "listenHost"
|
||||
});
|
||||
serverParser.add_argument("-p", "--listen-port", {
|
||||
help: "Port where the server should bind",
|
||||
type: "int",
|
||||
default: 49437,
|
||||
dest: "listenPort"
|
||||
});
|
||||
serverParser.add_argument("-a", "--allow-origins", {
|
||||
help: "List of origins that are allowed to connect (semicolon-separated)",
|
||||
type: "str",
|
||||
default: null,
|
||||
dest: "allowOrigins"
|
||||
});
|
||||
|
||||
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."});
|
||||
|
||||
24
cli/cli.js
24
cli/cli.js
@@ -11,6 +11,7 @@ const {parseArgs} = require('./args.js');
|
||||
const {execHaloCmdPCSC} = require('../index.js');
|
||||
const {__runTestSuite} = require("../halo/tests");
|
||||
const util = require("util");
|
||||
const {wsEventCardDisconnected, wsCreateServer, wsEventCardConnected} = require("./ws_server");
|
||||
|
||||
const nfc = new NFC();
|
||||
let stopPCSCTimeout = null;
|
||||
@@ -38,13 +39,18 @@ nfc.on('reader', reader => {
|
||||
reader.autoProcessing = false;
|
||||
|
||||
reader.on('card', card => {
|
||||
clearTimeout(stopPCSCTimeout);
|
||||
stopPCSCTimeout = setTimeout(stopPCSC, 4000, "timeout", args.output);
|
||||
if (args.name === "server") {
|
||||
wsEventCardConnected(reader);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.name === "pcsc_detect") {
|
||||
console.log("Tag inserted:", reader.reader.name, '(Type: ' + card.type + ', ATR: ' + card.atr.toString('hex').toUpperCase() + ')');
|
||||
}
|
||||
|
||||
clearTimeout(stopPCSCTimeout);
|
||||
stopPCSCTimeout = setTimeout(stopPCSC, 4000, "timeout", args.output);
|
||||
|
||||
(async () => {
|
||||
let res = await checkCard(reader);
|
||||
|
||||
@@ -88,6 +94,14 @@ nfc.on('reader', reader => {
|
||||
})();
|
||||
});
|
||||
|
||||
reader.on('card.off', card => {
|
||||
wsEventCardDisconnected(reader);
|
||||
});
|
||||
|
||||
reader.on('end', () => {
|
||||
wsEventCardDisconnected(reader);
|
||||
});
|
||||
|
||||
reader.on('error', err => {
|
||||
console.log(`${reader.reader.name} an error occurred`, err);
|
||||
});
|
||||
@@ -125,4 +139,8 @@ function stopPCSC(code, output) {
|
||||
}
|
||||
}
|
||||
|
||||
stopPCSCTimeout = setTimeout(stopPCSC, 4000, "timeout", args.output);
|
||||
if (args.name === "server") {
|
||||
wsCreateServer(args);
|
||||
} else {
|
||||
stopPCSCTimeout = setTimeout(stopPCSC, 4000, "timeout", args.output);
|
||||
}
|
||||
|
||||
23
cli/package-lock.json
generated
23
cli/package-lock.json
generated
@@ -9,7 +9,8 @@
|
||||
"version": "1.1.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nfc-pcsc": "^0.8.1"
|
||||
"nfc-pcsc": "^0.8.1",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"bin": {
|
||||
"halocli": "cli.js"
|
||||
@@ -1652,6 +1653,26 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
@@ -4,26 +4,33 @@
|
||||
"description": "HaLo Command Line Interface Tool for PC/SC",
|
||||
"contributors": [
|
||||
{
|
||||
"name" : "Michal Leszczynski",
|
||||
"email" : "ml@arx.sh",
|
||||
"url" : "https://github.com/icedevml"
|
||||
"name": "Michal Leszczynski",
|
||||
"email": "ml@arx.sh",
|
||||
"url": "https://github.com/icedevml"
|
||||
},
|
||||
{
|
||||
"name" : "Cameron Robertson",
|
||||
"email" : "cameron@arx.sh",
|
||||
"url" : "https://github.com/ccamrobertson"
|
||||
"name": "Cameron Robertson",
|
||||
"email": "cameron@arx.sh",
|
||||
"url": "https://github.com/ccamrobertson"
|
||||
}
|
||||
],
|
||||
"keywords": ["blockchain", "ethereum", "bitcoin", "nfc"],
|
||||
"keywords": [
|
||||
"blockchain",
|
||||
"ethereum",
|
||||
"bitcoin",
|
||||
"nfc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/arx-research/libhalo#readme",
|
||||
"bugs": {
|
||||
"url" : "https://github.com/arx-research/libhalo/issues/new/choose"
|
||||
"url": "https://github.com/arx-research/libhalo/issues/new/choose"
|
||||
},
|
||||
"main": "cli.js",
|
||||
"bin": "cli.js",
|
||||
"pkg": {
|
||||
"targets": ["node16"],
|
||||
"targets": [
|
||||
"node16"
|
||||
],
|
||||
"outputPath": "dist",
|
||||
"assets": [
|
||||
"node_modules/@pokusew/pcsclite/build/Release/pcsclite.node"
|
||||
@@ -34,7 +41,8 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"nfc-pcsc": "^0.8.1"
|
||||
"nfc-pcsc": "^0.8.1",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkg": "^5.8.0"
|
||||
|
||||
145
cli/ws_server.js
Normal file
145
cli/ws_server.js
Normal file
@@ -0,0 +1,145 @@
|
||||
const { WebSocketServer } = require('ws');
|
||||
const crypto = require('crypto').webcrypto;
|
||||
const {execHaloCmdPCSC} = require('../index.js');
|
||||
|
||||
let wss = null;
|
||||
|
||||
let currentWsClient = null;
|
||||
let currentState = null;
|
||||
|
||||
function generateHandle() {
|
||||
return Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64');
|
||||
}
|
||||
|
||||
function generateAuthPin() {
|
||||
let val = Buffer.from(crypto.getRandomValues(new Uint8Array(3))).toString('hex');
|
||||
return val.slice(0, 3) + '-' + val.slice(3);
|
||||
}
|
||||
|
||||
function sendToCurrentWs(ws, data) {
|
||||
if (currentWsClient !== null && (ws === null || currentWsClient === ws)) {
|
||||
console.log('send', data);
|
||||
currentWsClient.send(JSON.stringify(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function wsEventCardConnected(reader) {
|
||||
if (currentState) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_removed",
|
||||
"uid": null,
|
||||
"data": {
|
||||
"handle": currentState.handle
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let handle = generateHandle();
|
||||
currentState = {"handle": handle, "reader": reader};
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_added",
|
||||
"uid": null,
|
||||
"data": {
|
||||
"handle": handle
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function wsEventCardDisconnected(reader) {
|
||||
if (currentState !== null && currentState.reader === reader) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_removed",
|
||||
"uid": null,
|
||||
"data": {
|
||||
"handle": currentState.handle
|
||||
}
|
||||
});
|
||||
currentState = null;
|
||||
}
|
||||
}
|
||||
|
||||
function wsCreateServer(args) {
|
||||
wss = new WebSocketServer({host: args.listenHost, port: args.listenPort});
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
let originHostname = new URL(req.headers.origin).hostname;
|
||||
|
||||
if (args.allowOrigins) {
|
||||
let allowedOrigins = args.allowOrigins.split(';');
|
||||
|
||||
if (!allowedOrigins.includes(req.headers.origin)) {
|
||||
ws.close(4002, "Connecting origin is not on the configured allow list.");
|
||||
return;
|
||||
}
|
||||
} else if (originHostname !== "localhost" || originHostname !== "127.0.0.1") {
|
||||
ws.close(4003, "Connecting origin is not localhost. No other allowed origins are configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentWsClient) {
|
||||
currentWsClient.close(4001, "New client has connected. Server has dropped the current connection.");
|
||||
}
|
||||
|
||||
currentWsClient = ws;
|
||||
|
||||
ws.on('error', console.error);
|
||||
|
||||
ws.on('message', async function message(data) {
|
||||
if (currentWsClient !== ws) {
|
||||
return;
|
||||
}
|
||||
|
||||
let packet = JSON.parse(data);
|
||||
console.log('recv', packet);
|
||||
|
||||
if (packet.type === "exec_halo") {
|
||||
try {
|
||||
if (!currentState || packet.handle !== currentState.handle) {
|
||||
throw new Error("Invalid handle.");
|
||||
}
|
||||
|
||||
let res = await execHaloCmdPCSC(packet.command, currentState.reader);
|
||||
sendToCurrentWs(ws, {
|
||||
"event": "exec_success",
|
||||
"uid": packet.uid,
|
||||
"data": {
|
||||
"res": res
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
sendToCurrentWs(ws, {
|
||||
"event": "exec_exception",
|
||||
"uid": packet.uid,
|
||||
"data": {
|
||||
"exception": {
|
||||
"message": String(e),
|
||||
"stack": e.stack
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sendToCurrentWs(ws, {
|
||||
"event": "ws_connected",
|
||||
"uid": null,
|
||||
"data": {}
|
||||
});
|
||||
|
||||
if (currentState) {
|
||||
sendToCurrentWs(null, {
|
||||
"event": "handle_added",
|
||||
"uid": null,
|
||||
"data": {
|
||||
"handle": currentState.handle
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {wsCreateServer, wsEventCardConnected, wsEventCardDisconnected};
|
||||
2
web/examples/ws/.gitignore
vendored
Normal file
2
web/examples/ws/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
2009
web/examples/ws/package-lock.json
generated
Normal file
2009
web/examples/ws/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
web/examples/ws/package.json
Normal file
18
web/examples/ws/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "libhalo-wsdemo",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "wsdemo.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"webpack": "^5.76.2",
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"websocket-as-promised": "^2.0.1"
|
||||
}
|
||||
}
|
||||
10
web/examples/ws/webpack.config.js
Normal file
10
web/examples/ws/webpack.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './wsdemo.js',
|
||||
},
|
||||
output: {
|
||||
filename: 'wsdemo.js'
|
||||
},
|
||||
mode: 'production',
|
||||
target: 'web',
|
||||
};
|
||||
65
web/examples/ws/wsdemo.html
Normal file
65
web/examples/ws/wsdemo.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<script src="dist/wsdemo.js"></script>
|
||||
|
||||
<script>
|
||||
function log(data) {
|
||||
console.log(data);
|
||||
document.getElementById('log').innerText += '\n' + data;
|
||||
}
|
||||
|
||||
let wsp = createWs('ws://localhost:49437');
|
||||
|
||||
async function processTag(handle) {
|
||||
let res = await wsp.sendRequest({
|
||||
"type": "exec_halo",
|
||||
"handle": handle,
|
||||
"command": {
|
||||
"name": "get_pkeys"
|
||||
}
|
||||
});
|
||||
|
||||
if (res.event === "exec_exception") {
|
||||
log("!!! ERROR !!! Failed to execute HaLo command.");
|
||||
}
|
||||
|
||||
log(JSON.stringify(res));
|
||||
|
||||
res = await wsp.sendRequest({
|
||||
"type": "exec_halo",
|
||||
"handle": handle,
|
||||
"command": {
|
||||
"name": "sign",
|
||||
"message": "010203",
|
||||
"keyNo": 1
|
||||
}
|
||||
});
|
||||
|
||||
if (res.event === "exec_exception") {
|
||||
log("!!! ERROR !!! Failed to execute HaLo command.");
|
||||
}
|
||||
|
||||
log(JSON.stringify(res));
|
||||
}
|
||||
|
||||
wsp.onUnpackedMessage.addListener(async ev => {
|
||||
if (ev.event !== "exec_success" && ev.event !== "exec_exception") {
|
||||
log(JSON.stringify(ev));
|
||||
}
|
||||
|
||||
if (ev.event === "handle_added") {
|
||||
await processTag(ev.data.handle);
|
||||
}
|
||||
});
|
||||
|
||||
wsp.onClose.addListener(event => {
|
||||
if (event.code === 4001) {
|
||||
log('Connection closed, new client has connected.');
|
||||
} else {
|
||||
log('Connection closed: ' + event.code);
|
||||
}
|
||||
});
|
||||
|
||||
wsp.open();
|
||||
</script>
|
||||
|
||||
<pre id="log">Connecting to the server...</pre>
|
||||
|
||||
18
web/examples/ws/wsdemo.js
Normal file
18
web/examples/ws/wsdemo.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const WebSocketAsPromised = require('websocket-as-promised');
|
||||
|
||||
function createWs(url) {
|
||||
return new WebSocketAsPromised(url, {
|
||||
packMessage: data => JSON.stringify(data),
|
||||
unpackMessage: data => JSON.parse(data),
|
||||
attachRequestId: (data, requestId) => Object.assign({uid: requestId}, data),
|
||||
extractRequestId: data => data && data.uid
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {createWs};
|
||||
|
||||
if (window) {
|
||||
Object.keys(module.exports).forEach((key) => {
|
||||
window[key] = module.exports[key];
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user