162 lines
5.3 KiB
JavaScript
162 lines
5.3 KiB
JavaScript
|
|
"use strict";
|
||
|
|
|
||
|
|
const test = require("node:test");
|
||
|
|
const assert = require("node:assert/strict");
|
||
|
|
const crypto = require("node:crypto");
|
||
|
|
const {
|
||
|
|
GuanghuRouter,
|
||
|
|
canonicalApproval,
|
||
|
|
canonicalConnect,
|
||
|
|
workorderDigest,
|
||
|
|
} = require("./guanghu-router");
|
||
|
|
|
||
|
|
function registeredDevice() {
|
||
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
||
|
|
const publicJwk = publicKey.export({ format: "jwk" });
|
||
|
|
return {
|
||
|
|
device: {
|
||
|
|
device_id: "HL-BS-MAC-001",
|
||
|
|
owner_id: "owner",
|
||
|
|
label: "冰朔的 HoloLake",
|
||
|
|
public_key: publicJwk.x,
|
||
|
|
enabled: true,
|
||
|
|
},
|
||
|
|
privateKey,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function signedConnect(router, device, privateKey, now) {
|
||
|
|
const challenge = router.challenge(device.device_id, now);
|
||
|
|
assert.equal(challenge.ok, true);
|
||
|
|
const signature = crypto.sign(
|
||
|
|
null,
|
||
|
|
Buffer.from(canonicalConnect({
|
||
|
|
deviceId: device.device_id,
|
||
|
|
challengeId: challenge.challengeId,
|
||
|
|
nonce: challenge.nonce,
|
||
|
|
clientTimestamp: now,
|
||
|
|
})),
|
||
|
|
privateKey,
|
||
|
|
).toString("base64url");
|
||
|
|
return router.authorizeConnection({
|
||
|
|
deviceId: device.device_id,
|
||
|
|
challengeId: challenge.challengeId,
|
||
|
|
clientTimestamp: now,
|
||
|
|
signature,
|
||
|
|
}, now);
|
||
|
|
}
|
||
|
|
|
||
|
|
test("only a registered device with a valid signature receives a one-time route token", () => {
|
||
|
|
const { device, privateKey } = registeredDevice();
|
||
|
|
const router = new GuanghuRouter({ devices: [device] });
|
||
|
|
assert.deepEqual(router.challenge("unknown-device", 1_000), {
|
||
|
|
ok: false,
|
||
|
|
reason: "device_not_registered",
|
||
|
|
});
|
||
|
|
|
||
|
|
const challenge = router.challenge(device.device_id, 1_000);
|
||
|
|
const rejected = router.authorizeConnection({
|
||
|
|
deviceId: device.device_id,
|
||
|
|
challengeId: challenge.challengeId,
|
||
|
|
clientTimestamp: 1_000,
|
||
|
|
signature: "invalid",
|
||
|
|
}, 1_000);
|
||
|
|
assert.equal(rejected.ok, false);
|
||
|
|
assert.equal(rejected.reason, "device_signature_invalid");
|
||
|
|
|
||
|
|
const authorized = signedConnect(router, device, privateKey, 1_001);
|
||
|
|
assert.equal(authorized.ok, true);
|
||
|
|
assert.match(authorized.routeToken, /^[A-Za-z0-9_-]{40,}$/);
|
||
|
|
assert.equal(router.isApproverOnline("owner"), false);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("the open transport is the online fact and closing it makes the device offline", () => {
|
||
|
|
const { device, privateKey } = registeredDevice();
|
||
|
|
const router = new GuanghuRouter({ devices: [device] });
|
||
|
|
const authorized = signedConnect(router, device, privateKey, 2_000);
|
||
|
|
const events = [];
|
||
|
|
const opened = router.open(authorized.routeToken, event => events.push(event), 2_001);
|
||
|
|
|
||
|
|
assert.equal(opened.ok, true);
|
||
|
|
assert.equal(opened.state, "online");
|
||
|
|
assert.equal(router.isApproverOnline("owner"), true);
|
||
|
|
assert.equal(events[0].type, "router.connected");
|
||
|
|
assert.equal(events[0].receipt.state, "online");
|
||
|
|
assert.equal(router.open(authorized.routeToken, () => {}, 2_002).reason, "route_token_not_found");
|
||
|
|
|
||
|
|
opened.close(2_003);
|
||
|
|
assert.equal(router.isApproverOnline("owner"), false);
|
||
|
|
assert.equal(events.at(-1).type, "router.closed");
|
||
|
|
assert.equal(events.at(-1).receipt.state, "offline");
|
||
|
|
});
|
||
|
|
|
||
|
|
test("authorization cards travel through the already-open route", () => {
|
||
|
|
const { device, privateKey } = registeredDevice();
|
||
|
|
const router = new GuanghuRouter({ devices: [device] });
|
||
|
|
const authorized = signedConnect(router, device, privateKey, 3_000);
|
||
|
|
const events = [];
|
||
|
|
router.open(authorized.routeToken, event => events.push(event), 3_001);
|
||
|
|
const order = {
|
||
|
|
id: "203e12af-f821-4b62-b80f-b3d73df05161",
|
||
|
|
persona: { pid: "ICE-GL-ZY001", name: "铸渊" },
|
||
|
|
target: "JD-FD-PRIMARY",
|
||
|
|
scope: "server-login",
|
||
|
|
action: "read-navigation-map",
|
||
|
|
allowed_actions: ["read-navigation-map", "inspect-services"],
|
||
|
|
description: "进入第五域",
|
||
|
|
resource: "",
|
||
|
|
createdAt: 3_000,
|
||
|
|
expiresAt: 4_000,
|
||
|
|
state: "pending",
|
||
|
|
};
|
||
|
|
assert.equal(router.deliver("owner", order), 1);
|
||
|
|
const card = events.at(-1);
|
||
|
|
assert.equal(card.type, "authorization.requested");
|
||
|
|
assert.equal(card.workorder.id, order.id);
|
||
|
|
assert.equal(card.digest, workorderDigest(order));
|
||
|
|
});
|
||
|
|
|
||
|
|
test("the bound device signs the exact authorization card digest", () => {
|
||
|
|
const { device, privateKey } = registeredDevice();
|
||
|
|
const router = new GuanghuRouter({ devices: [device] });
|
||
|
|
const authorized = signedConnect(router, device, privateKey, 4_000);
|
||
|
|
router.open(authorized.routeToken, () => {}, 4_001);
|
||
|
|
const order = {
|
||
|
|
id: "203e12af-f821-4b62-b80f-b3d73df05161",
|
||
|
|
persona: { pid: "ICE-GL-ZY001", name: "铸渊" },
|
||
|
|
target: "JD-FD-PRIMARY",
|
||
|
|
scope: "server-login",
|
||
|
|
action: "read-navigation-map",
|
||
|
|
allowed_actions: ["read-navigation-map", "inspect-services"],
|
||
|
|
description: "进入第五域",
|
||
|
|
resource: "",
|
||
|
|
createdAt: 4_000,
|
||
|
|
expiresAt: 5_000,
|
||
|
|
state: "pending",
|
||
|
|
};
|
||
|
|
const digest = workorderDigest(order);
|
||
|
|
const signature = crypto.sign(
|
||
|
|
null,
|
||
|
|
Buffer.from(canonicalApproval({
|
||
|
|
deviceId: device.device_id,
|
||
|
|
workorderId: order.id,
|
||
|
|
digest,
|
||
|
|
})),
|
||
|
|
privateKey,
|
||
|
|
).toString("base64url");
|
||
|
|
|
||
|
|
const verified = router.verifyApproval(
|
||
|
|
device.device_id,
|
||
|
|
order,
|
||
|
|
signature,
|
||
|
|
);
|
||
|
|
assert.equal(verified.ok, true);
|
||
|
|
assert.equal(verified.authorizerId, "owner");
|
||
|
|
assert.equal(verified.deviceId, device.device_id);
|
||
|
|
|
||
|
|
const changed = { ...order, action: "inspect-services" };
|
||
|
|
assert.equal(
|
||
|
|
router.verifyApproval(device.device_id, changed, signature).reason,
|
||
|
|
"device_signature_invalid",
|
||
|
|
);
|
||
|
|
});
|