UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

319 lines (267 loc) 9.67 kB
/* eslint-disable @typescript-eslint/no-var-requires */ import { randomBytes } from "crypto"; import { SecurityManager } from "./Manager"; // prettier-ignore const networkKey = Buffer.from([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]); const ownNodeId = 1; const options = { networkKey, ownNodeId, nonceTimeout: 500 }; describe("lib/security/Manager", () => { beforeAll(() => { jest.useFakeTimers(); }); afterAll(() => { jest.clearAllTimers(); jest.useRealTimers(); }); describe("constructor()", () => { it("should set the network key, auth key and encryption key", () => { const man = new SecurityManager(options); expect(man.networkKey).toEqual(networkKey); expect(Buffer.isBuffer(man.authKey)).toBeTrue(); expect(man.authKey.length).toBe(16); expect(Buffer.isBuffer(man.encryptionKey)).toBeTrue(); expect(man.encryptionKey.length).toBe(16); }); it("should throw if the network key doesn't have length 16", () => { expect( () => new SecurityManager({ networkKey: Buffer.from([]), ownNodeId: 1, nonceTimeout: 500, }), ).toThrow("16 bytes"); }); }); describe("generateNonce", () => { it("should return a random Buffer of the given length", () => { const man = new SecurityManager(options); // I know, this is not really checking if the value is random const nonce1 = man.generateNonce(2, 8); const nonce2 = man.generateNonce(2, 8); const nonce3 = man.generateNonce(2, 8); expect(Buffer.isBuffer(nonce1)).toBeTrue(); expect(Buffer.isBuffer(nonce2)).toBeTrue(); expect(Buffer.isBuffer(nonce3)).toBeTrue(); expect(nonce1.length).toBe(8); expect(nonce2.length).toBe(8); expect(nonce3.length).toBe(8); }); it("should ensure that no collisions happen", () => { jest.resetModules(); jest.isolateModules(() => { const buf1a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]); const buf1b = Buffer.from([1, 2, 3, 4, 5, 6, 7, 9]); // has the same nonce id const buf2 = Buffer.from([2, 2, 3, 4, 5, 6, 7, 8]); jest.mock("crypto"); const crypto: typeof import("crypto") = require("crypto"); const original: typeof import("crypto") = jest.requireActual("crypto"); (crypto.randomBytes as jest.Mock) .mockReturnValueOnce(buf1a) .mockReturnValueOnce(buf1b) .mockReturnValueOnce(buf2); crypto.createCipheriv = original.createCipheriv; crypto.createDecipheriv = original.createDecipheriv; const SM: typeof SecurityManager = require("./Manager").SecurityManager; const man = new SM(options); const nonce1 = man.generateNonce(2, 8); const nonce2 = man.generateNonce(2, 8); expect(nonce1).toEqual(buf1a); expect(nonce2).toEqual(buf2); expect(man.getNonce(1)).toEqual(buf1a); expect(man.getNonce(2)).toEqual(buf2); jest.resetModules(); }); }); it("should store nonces for the current node id", () => { const man = new SecurityManager(options); const nonce1 = man.generateNonce(2, 8); const nonce2 = man.generateNonce(2, 8); const nonce3 = man.generateNonce(2, 8); expect( man.getNonce({ issuer: 2, nonceId: man.getNonceId(nonce1) }), ).toBeUndefined(); expect( man.getNonce({ issuer: 2, nonceId: man.getNonceId(nonce2) }), ).toBeUndefined(); expect( man.getNonce({ issuer: 2, nonceId: man.getNonceId(nonce3) }), ).toBeUndefined(); }); it("the nonces should expire after the given timeout", () => { const man = new SecurityManager(options); const nonce = man.generateNonce(2, 8); const nonceId = nonce[0]; expect(man.getNonce(nonceId)).toEqual(nonce); jest.advanceTimersByTime(options.nonceTimeout + 50); expect(man.getNonce(nonceId)).toBeUndefined(); }); it(`the nonce should be marked as "reserved"`, () => { const man = new SecurityManager(options); man.generateNonce(2, 8); expect(man.getFreeNonce(ownNodeId)).toBeUndefined(); }); }); describe("getNonceId", () => { it("should return the first byte of the nonce", () => { const man = new SecurityManager(options); const nonce1 = man.generateNonce(2, 8); const nonce2 = man.generateNonce(2, 8); const nonce3 = man.generateNonce(2, 8); expect(man.getNonceId(nonce1)).toBe(nonce1[0]); expect(man.getNonceId(nonce2)).toBe(nonce2[0]); expect(man.getNonceId(nonce3)).toBe(nonce3[0]); }); }); describe("getNonce", () => { it("should return a previously generated nonce with the same id", () => { const man = new SecurityManager(options); const nonce1 = man.generateNonce(2, 8); const nonce2 = man.generateNonce(2, 8); const nonce3 = man.generateNonce(2, 8); const nonceId1 = man.getNonceId(nonce1); const nonceId2 = man.getNonceId(nonce2); const nonceId3 = man.getNonceId(nonce3); expect(nonce1).toEqual(man.getNonce(nonceId1)); expect(nonce2).toEqual(man.getNonce(nonceId2)); expect(nonce3).toEqual(man.getNonce(nonceId3)); }); }); describe("setNonce", () => { it("should store a given nonce to be retrieved later", () => { const man = new SecurityManager(options); expect(man.getNonce(1)).toBeUndefined(); const nonce: Buffer = randomBytes(8); nonce[0] = 1; man.setNonce(1, { nonce, receiver: 2 }); expect(man.getNonce(1)).toEqual(nonce); }); it("the nonces should timeout after the given timeout", () => { const man = new SecurityManager(options); const nonce: Buffer = randomBytes(8); const nonceId = nonce[0]; man.setNonce(nonceId, { nonce, receiver: 2 }); expect(man.getNonce(nonceId)).toEqual(nonce); jest.advanceTimersByTime(options.nonceTimeout + 50); expect(man.getNonce(nonceId)).toBeUndefined(); }); it("should mark the nonce as free", () => { const man = new SecurityManager(options); const nonce: Buffer = randomBytes(8); nonce[0] = 1; man.setNonce( { issuer: 2, nonceId: 1, }, { nonce, receiver: options.ownNodeId }, ); // Wrong node expect(man.getFreeNonce(1)).toBeUndefined(); expect(man.getFreeNonce(2)).toEqual(nonce); }); it("when a free nonce expires, it should no longer be free", () => { const man = new SecurityManager(options); const nonce: Buffer = randomBytes(8); man.setNonce( { issuer: 2, nonceId: 1, }, { nonce, receiver: options.ownNodeId }, ); jest.advanceTimersByTime(options.nonceTimeout + 50); expect(man.getFreeNonce(2)).toBeUndefined(); }); }); describe("hasNonce", () => { it("should return whether a nonce id is in the database", () => { const man = new SecurityManager(options); // Manually set expect(man.hasNonce(1)).toBeFalse(); const nonce1: Buffer = randomBytes(8); nonce1[0] = 1; man.setNonce(1, { nonce: nonce1, receiver: 2 }); expect(man.hasNonce(1)).toBeTrue(); // And generated const nonce2 = man.generateNonce(2, 8); const nonceId2 = man.getNonceId(nonce2); expect(man.hasNonce(nonceId2)).toBeTrue(); }); }); describe("deleteNonce", () => { it("should remove a nonce from the database", () => { const man = new SecurityManager(options); const nonce = man.generateNonce(2, 8); const nonceId = man.getNonceId(nonce); man.deleteNonce(nonceId); expect(man.getNonce(nonceId)).toBeUndefined(); expect(man.hasNonce(nonceId)).toBeFalse(); }); it("and all other nonces that were created for the same receiver", () => { const man = new SecurityManager(options); const nonce1 = man.generateNonce(2, 8); const nonceId1 = man.getNonceId(nonce1); const nonce2 = man.generateNonce(2, 8); const nonceId2 = man.getNonceId(nonce2); man.deleteNonce(nonceId1); expect(man.getNonce(nonceId1)).toBeUndefined(); expect(man.hasNonce(nonceId1)).toBeFalse(); expect(man.getNonce(nonceId2)).toBeUndefined(); expect(man.hasNonce(nonceId2)).toBeFalse(); }); }); describe("deleteAllNoncesForReceiver", () => { it("should only delete the nonces for the given receiver", () => { const man = new SecurityManager(options); const nonce1 = man.generateNonce(2, 8); const nonceId1 = man.getNonceId(nonce1); const nonce2 = man.generateNonce(2, 8); const nonceId2 = man.getNonceId(nonce2); // different receiver const nonce3 = man.generateNonce(3, 8); const nonceId3 = man.getNonceId(nonce3); man.deleteAllNoncesForReceiver(2); expect(man.getNonce(nonceId1)).toBeUndefined(); expect(man.hasNonce(nonceId1)).toBeFalse(); expect(man.getNonce(nonceId2)).toBeUndefined(); expect(man.hasNonce(nonceId2)).toBeFalse(); expect(man.getNonce(nonceId3)).not.toBeUndefined(); expect(man.hasNonce(nonceId3)).not.toBeFalse(); }); }); describe("getFreeNonce", () => { it("should reserve the nonce", () => { const man = new SecurityManager(options); const nonce: Buffer = randomBytes(8); nonce[0] = 1; man.setNonce( { issuer: 2, nonceId: 1, }, { nonce, receiver: options.ownNodeId }, ); expect(man.getFreeNonce(2)).toEqual(nonce); expect(man.getFreeNonce(2)).toBeUndefined(); }); }); it("nonces should be stored separately for each node", () => { const man = new SecurityManager(options); const nonce1 = man.generateNonce(3, 8); const nonceId1 = man.getNonceId(nonce1); // Create a nonce with the same nonceId but with another issuer const nonce2: Buffer = randomBytes(8); nonce2[0] = nonceId1; const id2 = { issuer: 4, nonceId: nonceId1 }; expect(man.hasNonce(id2)).toBeFalse(); expect(man.getNonce(id2)).toBeUndefined(); man.setNonce(id2, { nonce: nonce2, receiver: 1 }); expect(man.hasNonce(id2)).toBeTrue(); expect(man.getNonce(id2)).toEqual(nonce2); }); });