85 lines
2.3 KiB
TypeScript
85 lines
2.3 KiB
TypeScript
import {
|
|
buildCommand,
|
|
buildError,
|
|
Command,
|
|
parseCommand,
|
|
} from "./commandbuilder.ts";
|
|
import { Device } from "./device.ts";
|
|
|
|
function toHex(data: Uint8Array): string {
|
|
return Array.from(data).map((b) => b.toString(16)).join("");
|
|
}
|
|
async function generateHMAC(key: string, message: string) {
|
|
const encoder = new TextEncoder();
|
|
const keyData = encoder.encode(key);
|
|
const messageData = encoder.encode(message);
|
|
|
|
const cryptoKey = await crypto.subtle.importKey(
|
|
"raw",
|
|
keyData,
|
|
{ name: "HMAC", hash: { name: "SHA-256" } },
|
|
false,
|
|
["sign"],
|
|
);
|
|
|
|
const signature = await crypto.subtle.sign(
|
|
"HMAC",
|
|
cryptoKey,
|
|
messageData,
|
|
);
|
|
|
|
return toHex(new Uint8Array(signature));
|
|
}
|
|
|
|
export class Authentication {
|
|
_socket: WebSocket;
|
|
_encoder: TextEncoder;
|
|
nonce: string;
|
|
device: Device | undefined;
|
|
signature: string | undefined;
|
|
|
|
constructor(socket: WebSocket) {
|
|
this._socket = socket;
|
|
this._encoder = new TextEncoder();
|
|
this.nonce = toHex(crypto.getRandomValues(new Uint8Array(32)));
|
|
}
|
|
|
|
async authenticate(message: Command) { // ugh, js doesn't have async constructors, hence the seeprate init function.
|
|
if (
|
|
message.d == undefined || !("id" in message.d) ||
|
|
typeof (message.d.id) !== "string" || !(message.d.id in Device.devices)
|
|
) {
|
|
this._socket.send(buildError(2, "Invalid packet. Missing ID"));
|
|
this._socket.close();
|
|
return;
|
|
}
|
|
|
|
this._socket.addEventListener("message", (msg) => this._validate(msg));
|
|
this.device = Device.devices[message.d.id];
|
|
this.signature = await generateHMAC(this.device.password, this.nonce);
|
|
|
|
this._socket.send(
|
|
buildCommand("auth_nonce", { nonce: this.nonce }),
|
|
);
|
|
}
|
|
|
|
_validate(msg: MessageEvent<any>) {
|
|
if (!this.device || !this.signature) return;
|
|
|
|
const parsed = parseCommand(msg.data);
|
|
if (
|
|
parsed === null || parsed.c !== "auth_validate" ||
|
|
parsed.d === undefined || !("signature" in parsed.d)
|
|
) return;
|
|
|
|
if (this.signature !== parsed.d.signature) {
|
|
this._socket.send(buildError(3, "Invalid signature."));
|
|
this._socket.close;
|
|
return;
|
|
}
|
|
|
|
this._socket.send(buildCommand("auth_ok"));
|
|
this.device?.connect(this._socket);
|
|
this._socket.removeEventListener("message", this._validate);
|
|
}
|
|
}
|