mirror of
https://github.com/arx-research/libhalo.git
synced 2026-01-10 13:48:08 -05:00
Bridge: Tune the TLS related settings and code (#153)
This commit is contained in:
committed by
GitHub
parent
4e2b7d04e9
commit
97f4718ca2
@@ -12,12 +12,12 @@
|
||||
This is the example web application of HaLo Bridge Server.
|
||||
Please connect your PC/SC reader and tap the compatible HaLo tag.
|
||||
</p>
|
||||
<pre id="log">Connecting to the server...</pre>
|
||||
<pre id="log" style="word-break: break-all; white-space: pre-wrap;">Connecting to the server...</pre>
|
||||
</div>
|
||||
<script src="/assets/static/libhalo.js"></script>
|
||||
<script>
|
||||
const forceUseTLS = {% if forceUseTLS %}true{% else %}false{% endif %};
|
||||
const displayTLSWarn = {% if displayTLSWarn %}true{% else %}false{% endif %};
|
||||
let wsp = null;
|
||||
|
||||
const wsPort = {{ wsPort }};
|
||||
const wssPort = {{ wssPort }};
|
||||
|
||||
@@ -26,16 +26,6 @@
|
||||
document.getElementById('log').innerText += '\n' + data;
|
||||
}
|
||||
|
||||
let wsAddress;
|
||||
|
||||
if (window.location.protocol === 'https:' || forceUseTLS) {
|
||||
wsAddress = 'wss://halo-bridge.local:' + wssPort;
|
||||
} else {
|
||||
wsAddress = 'ws://127.0.0.1:' + wsPort;
|
||||
}
|
||||
|
||||
let wsp = createWs(wsAddress);
|
||||
|
||||
async function execHaloCommand(command) {
|
||||
log('Executing command: ' + JSON.stringify(command));
|
||||
let res = await wsp.sendRequest(command);
|
||||
@@ -69,48 +59,59 @@
|
||||
});
|
||||
}
|
||||
|
||||
wsp.onUnpackedMessage.addListener(async ev => {
|
||||
if (ev.event !== "exec_success" && ev.event !== "exec_exception") {
|
||||
switch (ev.event) {
|
||||
case 'ws_connected':
|
||||
log('Connected to the server.');
|
||||
break;
|
||||
case 'reader_added':
|
||||
log('Reader connected: ' + ev.data.reader_name);
|
||||
break;
|
||||
case 'reader_removed':
|
||||
log('Reader disconnected: ' + ev.data.reader_name);
|
||||
break;
|
||||
case 'handle_added':
|
||||
log('Detected tag: ' + ev.data.reader_name + ' (handle: ' + ev.data.handle + ')');
|
||||
break;
|
||||
case 'handle_removed':
|
||||
log('Removed tag: ' + ev.data.reader_name + ' (handle: ' + ev.data.handle + ')');
|
||||
break;
|
||||
async function run() {
|
||||
let wsAddress = await haloFindBridge({
|
||||
wsPort: wsPort,
|
||||
wssPort: wssPort
|
||||
});
|
||||
|
||||
wsp = haloCreateWs(wsAddress);
|
||||
|
||||
wsp.onUnpackedMessage.addListener(async ev => {
|
||||
if (ev.event !== "exec_success" && ev.event !== "exec_exception") {
|
||||
switch (ev.event) {
|
||||
case 'ws_connected':
|
||||
log('Connected to the server.');
|
||||
break;
|
||||
case 'reader_added':
|
||||
log('Reader connected: ' + ev.data.reader_name);
|
||||
break;
|
||||
case 'reader_removed':
|
||||
log('Reader disconnected: ' + ev.data.reader_name);
|
||||
break;
|
||||
case 'handle_added':
|
||||
log('Detected tag: ' + ev.data.reader_name + ' (handle: ' + ev.data.handle + ')');
|
||||
break;
|
||||
case 'handle_removed':
|
||||
log('Removed tag: ' + ev.data.reader_name + ' (handle: ' + ev.data.handle + ')');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.event === "handle_added") {
|
||||
await processTag(ev.data.handle);
|
||||
}
|
||||
});
|
||||
if (ev.event === "handle_added") {
|
||||
await processTag(ev.data.handle);
|
||||
}
|
||||
});
|
||||
|
||||
wsp.onClose.addListener(event => {
|
||||
if (event.code === 1006 && displayTLSWarn) {
|
||||
log('IMPORTANT: You might be missing a local certificate, which is required for halo-bridge on Safari. ' +
|
||||
'If the halo-bridge doesn\'t work at all, please try to reinstall the entire toolset using the ' +
|
||||
'release PKG file. When the installer will ask you about certificate generation, ' +
|
||||
'please choose "Generate certificate" option.');
|
||||
}
|
||||
wsp.onClose.addListener(event => {
|
||||
if (event.code === 1006) {
|
||||
log('IMPORTANT: You might be missing a local certificate, which is required for halo-bridge on Safari. ' +
|
||||
'If the halo-bridge doesn\'t work at all, please try to reinstall the entire toolset using the ' +
|
||||
'release PKG file. When the installer will ask you about certificate generation, ' +
|
||||
'please choose "Generate certificate" option.');
|
||||
}
|
||||
|
||||
if (event.code === 4001) {
|
||||
log('Connection closed, new client has connected.');
|
||||
} else {
|
||||
log('Connection closed: ' + event.code);
|
||||
}
|
||||
});
|
||||
if (event.code === 4001) {
|
||||
log('Connection closed, new client has connected.');
|
||||
} else {
|
||||
log('Connection closed: ' + event.code);
|
||||
}
|
||||
});
|
||||
|
||||
wsp.open();
|
||||
wsp.open();
|
||||
}
|
||||
|
||||
run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -149,16 +149,11 @@ function runHalo(entryMode, args) {
|
||||
|
||||
if (entryMode === "server") {
|
||||
console.log('Launching Web Socket Server...');
|
||||
let serverInfo = wsCreateServer(
|
||||
args, () => Object.keys(nfc.readers).map(r => nfc.readers[r].name));
|
||||
wsCreateServer(args, () => Object.keys(nfc.readers).map(r => nfc.readers[r].name));
|
||||
console.log('Web Socket Server is listening...');
|
||||
|
||||
if (!args.nonInteractive) {
|
||||
if (serverInfo.hasTLS) {
|
||||
open('https://halo-bridge.local:' + args.listenPortTLS);
|
||||
} else {
|
||||
open('http://127.0.0.1:' + args.listenPort);
|
||||
}
|
||||
open('http://127.0.0.1:' + args.listenPort);
|
||||
}
|
||||
} else {
|
||||
stopPCSCTimeout = setTimeout(stopPCSC, 4000, "timeout", args.output);
|
||||
|
||||
@@ -14,23 +14,24 @@ mkdir -p /usr/local/etc/halo-bridge
|
||||
# remove stale files
|
||||
rm -f /usr/local/etc/halo-bridge/private_key.pem
|
||||
rm -f /usr/local/etc/halo-bridge/server.csr
|
||||
rm -f /usr/local/etc/halo-bridge/server.csr
|
||||
rm -f /usr/local/etc/halo-bridge/server.crt
|
||||
|
||||
# ask user whether he wants to generate a certificate for halo-bridge.local
|
||||
if osascript -e 'display dialog "In order for halo-bridge to work correctly, we will need to generate a self-signed certificate for the '\''halo-bridge.local'\'' domain and mark it as trusted in the system.\n\nYou can skip that step if you don'\''t need to use halo-bridge tool." buttons {"Skip", "Generate certificate"} default button "Generate certificate" cancel button "Skip" with icon caution with title "HaLo Tools Installer"'
|
||||
then
|
||||
# generate new local certificate
|
||||
openssl genrsa -out /usr/local/etc/halo-bridge/private_key.pem 2048
|
||||
openssl req -new -sha256 -key /usr/local/etc/halo-bridge/private_key.pem -out /usr/local/etc/halo-bridge/server.csr -subj '/CN=halo-bridge.local/'
|
||||
openssl req -x509 -sha256 -days 3650 -extensions SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName='DNS:halo-bridge.local'")) -key /usr/local/etc/halo-bridge/private_key.pem -in /usr/local/etc/halo-bridge/server.csr -out /usr/local/etc/halo-bridge/server.pem
|
||||
openssl req -new -sha256 -key /usr/local/etc/halo-bridge/private_key.pem -out /usr/local/etc/halo-bridge/server.csr -subj '/CN=halo-tools (Local Certificate)/'
|
||||
openssl req -x509 -sha256 -days 3650 -extensions HALO -config <(printf "[HALO]\nsubjectAltName='DNS:halo-bridge.local,DNS:localhost,IP:127.0.0.1,IP:::1'\nbasicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=critical,serverAuth") -key /usr/local/etc/halo-bridge/private_key.pem -in /usr/local/etc/halo-bridge/server.csr -out /usr/local/etc/halo-bridge/server.crt
|
||||
|
||||
# add certificate to the trust list
|
||||
security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /usr/local/etc/halo-bridge/server.pem
|
||||
security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /usr/local/etc/halo-bridge/server.crt
|
||||
fi
|
||||
|
||||
# add halo-bridge.local domain to /etc/hosts if it doesn't exist yet
|
||||
if ! grep -q "halo-bridge.local" /etc/hosts
|
||||
then
|
||||
echo "" >> /etc/hosts
|
||||
echo "127.0.0.1 halo-bridge.local" >> /etc/hosts
|
||||
fi
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const https = require("https");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const util = require("util");
|
||||
|
||||
let wss = null;
|
||||
|
||||
@@ -51,7 +52,7 @@ async function validateCSRFToken(token) {
|
||||
}
|
||||
|
||||
function sendToCurrentWs(ws, data) {
|
||||
console.log('send', data);
|
||||
console.log('send', util.inspect(data, {showHidden: false, depth: null, colors: true}));
|
||||
|
||||
if (currentWsClient !== null && (ws === null || currentWsClient === ws)) {
|
||||
currentWsClient.send(JSON.stringify(data));
|
||||
@@ -125,10 +126,10 @@ function readTLSData() {
|
||||
|
||||
if (process.platform === "win32") {
|
||||
privateKeyPath = path.join(os.homedir(), ".halo-bridge\\private_key.pem");
|
||||
certificatePath = path.join(os.homedir(), ".halo-bridge\\server.pem");
|
||||
certificatePath = path.join(os.homedir(), ".halo-bridge\\server.crt");
|
||||
} else {
|
||||
privateKeyPath = '/usr/local/etc/halo-bridge/private_key.pem';
|
||||
certificatePath = '/usr/local/etc/halo-bridge/server.pem';
|
||||
certificatePath = '/usr/local/etc/halo-bridge/server.crt';
|
||||
}
|
||||
|
||||
if (fs.existsSync(privateKeyPath) && fs.existsSync(certificatePath)) {
|
||||
@@ -148,7 +149,6 @@ function wsCreateServer(args, getReaderNames) {
|
||||
const app = express();
|
||||
const server = app.listen(args.listenPort, args.listenHost);
|
||||
|
||||
let forceUseTLS = (process.platform === "darwin");
|
||||
let displayTLSWarn = (process.platform === "darwin");
|
||||
|
||||
if (tlsData) {
|
||||
@@ -172,13 +172,18 @@ function wsCreateServer(args, getReaderNames) {
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.render('ws_client.html', {
|
||||
forceUseTLS: forceUseTLS,
|
||||
displayTLSWarn: displayTLSWarn,
|
||||
wsPort: args.listenPort,
|
||||
wssPort: args.listenPortTLS
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.type('text/plain')
|
||||
.set('Access-Control-Allow-Origin', req.headers.origin)
|
||||
.set('Access-Control-Allow-Methods', 'GET')
|
||||
.send('OK');
|
||||
});
|
||||
|
||||
app.get('/consent', async (req, res) => {
|
||||
let url = new URL(req.query.website);
|
||||
let csrfToken = await makeCSRFToken();
|
||||
@@ -229,6 +234,13 @@ function wsCreateServer(args, getReaderNames) {
|
||||
}
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
let parts = req.url.split('?');
|
||||
|
||||
if (parts.length === 2 && parts[1] === "ping=1") {
|
||||
ws.close(4090, "Pong.");
|
||||
return;
|
||||
}
|
||||
|
||||
let permitted = false;
|
||||
let originHostname = new URL(req.headers.origin).hostname;
|
||||
|
||||
@@ -267,7 +279,7 @@ function wsCreateServer(args, getReaderNames) {
|
||||
}
|
||||
|
||||
let packet = JSON.parse(data);
|
||||
console.log('recv', packet);
|
||||
console.log('recv', util.inspect(packet, {showHidden: false, depth: null, colors: true}));
|
||||
|
||||
if (packet.type === "exec_halo") {
|
||||
try {
|
||||
|
||||
4
docs/halo-bridge.md
Normal file
4
docs/halo-bridge.md
Normal file
@@ -0,0 +1,4 @@
|
||||
(TODO description of halo-bridge)
|
||||
|
||||
(TODO how to connect with halo-bridge from external web app - check HTTPS then fall back to HTTP endpoint,
|
||||
would give nearly 100% coverage of all systems and browsers)
|
||||
@@ -1,20 +1,10 @@
|
||||
const {JWEUtil} = require("../jwe_util");
|
||||
const WebSocketAsPromised = require("websocket-as-promised");
|
||||
const queryString = require("query-string");
|
||||
|
||||
let currentCmd = null;
|
||||
let jweUtil = new JWEUtil();
|
||||
let wsp;
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
async function haloGateExecutorCreateWs(logCallback, newCommandCallback) {
|
||||
let protocol;
|
||||
let searchParts = window.location.hash.split('/');
|
||||
|
||||
88
web/web_utils.js
Normal file
88
web/web_utils.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const WebSocketAsPromised = require('websocket-as-promised');
|
||||
|
||||
function haloCreateWs(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
|
||||
});
|
||||
}
|
||||
|
||||
function runHealthCheck(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let closeTimeout = null;
|
||||
|
||||
const pingUrl = url.includes('?') ? (url + '&ping=1') : (url + '?ping=1');
|
||||
const wsp = new WebSocketAsPromised(pingUrl, {timeout: 5000});
|
||||
|
||||
wsp.onClose.addListener(event => {
|
||||
if (event.code === 4090) {
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout);
|
||||
}
|
||||
|
||||
resolve(url);
|
||||
} else {
|
||||
throw new Error("Unexpected WebSocket close code: " + event.code);
|
||||
}
|
||||
});
|
||||
|
||||
wsp.open()
|
||||
.then(() => {
|
||||
closeTimeout = setTimeout(() => {
|
||||
wsp.close();
|
||||
reject(new Error('WebSocket didn\'t close as expected.'));
|
||||
}, 2000);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function createChecks(wsPort, wssPort) {
|
||||
return [
|
||||
runHealthCheck('wss://halo-bridge.local:' + wssPort + '/ws'),
|
||||
runHealthCheck('ws://127.0.0.1:' + wsPort + '/ws')
|
||||
];
|
||||
}
|
||||
|
||||
async function haloFindBridge(options) {
|
||||
options = Object.assign({}, options) || {};
|
||||
|
||||
if (!options.wsPort) {
|
||||
options.wsPort = 32868;
|
||||
}
|
||||
|
||||
if (!options.wssPort) {
|
||||
options.wssPort = 32869;
|
||||
}
|
||||
|
||||
if (options.diagnose) {
|
||||
let res = await Promise.allSettled(createChecks(wsPort, wssPort));
|
||||
let urls = [];
|
||||
let errors = [];
|
||||
|
||||
for (let o of res) {
|
||||
if (o.status === "fulfilled") {
|
||||
urls.push(o.value);
|
||||
} else {
|
||||
errors.push(o.reason);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
urls,
|
||||
errors
|
||||
};
|
||||
} else {
|
||||
try {
|
||||
return await Promise.any(createChecks(wsPort, wssPort));
|
||||
} catch (e) {
|
||||
throw new Error("Unable to locate halo bridge.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {haloCreateWs, haloFindBridge};
|
||||
@@ -19,9 +19,9 @@ const {
|
||||
arr2hex, hex2arr, parsePublicKeys, convertSignature, recoverPublicKey
|
||||
} = require("../halo/utils");
|
||||
const {__runTestSuite} = require("../halo/tests");
|
||||
const WebSocketAsPromised = require("websocket-as-promised");
|
||||
const {HaloGateway} = require("../halo/gateway/requestor");
|
||||
const {haloGateExecutorCreateWs, haloGateExecutorUserConfirm} = require("../halo/gateway/executor");
|
||||
const {haloFindBridge, haloCreateWs} = require("./web_utils");
|
||||
|
||||
module.exports = {
|
||||
// utilities
|
||||
@@ -33,6 +33,10 @@ module.exports = {
|
||||
|
||||
// for web usage
|
||||
execHaloCmdWeb,
|
||||
haloFindBridge,
|
||||
|
||||
// for bridge demo
|
||||
haloCreateWs,
|
||||
|
||||
// for web usage with gateway
|
||||
HaloGateway,
|
||||
|
||||
Reference in New Issue
Block a user