UNPKG

hap-nodejs

Version:

HAP-NodeJS is a Node.js implementation of HomeKit Accessory Server.

144 lines 5.69 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateCurve25519KeyPair = generateCurve25519KeyPair; exports.generateCurve25519SharedSecKey = generateCurve25519SharedSecKey; exports.HKDF = HKDF; exports.writeUInt64LE = writeUInt64LE; exports.chacha20_poly1305_decryptAndVerify = chacha20_poly1305_decryptAndVerify; exports.chacha20_poly1305_encryptAndSeal = chacha20_poly1305_encryptAndSeal; exports.layerEncrypt = layerEncrypt; exports.layerDecrypt = layerDecrypt; const tslib_1 = require("tslib"); const assert_1 = tslib_1.__importDefault(require("assert")); const crypto_1 = tslib_1.__importDefault(require("crypto")); const futoin_hkdf_1 = tslib_1.__importDefault(require("futoin-hkdf")); const tweetnacl_1 = tslib_1.__importDefault(require("tweetnacl")); if (!crypto_1.default.getCiphers().includes("chacha20-poly1305")) { assert_1.default.fail("The cipher 'chacha20-poly1305' is not supported with your current running nodejs version v" + process.version + ". " + "At least a nodejs version of v10.17.0 (excluding v11.0 and v11.1) is required!"); } /** * @group Cryptography */ function generateCurve25519KeyPair() { return tweetnacl_1.default.box.keyPair(); } /** * @group Cryptography */ function generateCurve25519SharedSecKey(priKey, pubKey) { return tweetnacl_1.default.scalarMult(priKey, pubKey); } /** * @group Cryptography */ function HKDF(hashAlg, salt, ikm, info, size) { return (0, futoin_hkdf_1.default)(ikm, size, { hash: hashAlg, salt: salt, info: info }); } const MAX_UINT32 = 0x00000000FFFFFFFF; const MAX_INT53 = 0x001FFFFFFFFFFFFF; function uintHighLow(number) { (0, assert_1.default)(number > -1 && number <= MAX_INT53, "number out of range"); (0, assert_1.default)(Math.floor(number) === number, "number must be an integer"); let high = 0; const signbit = number & 0xFFFFFFFF; const low = signbit < 0 ? (number & 0x7FFFFFFF) + 0x80000000 : signbit; if (number > MAX_UINT32) { high = (number - low) / (MAX_UINT32 + 1); } return [high, low]; } /** * @group Utils */ function writeUInt64LE(number, buffer, offset = 0) { const hl = uintHighLow(number); buffer.writeUInt32LE(hl[1], offset); buffer.writeUInt32LE(hl[0], offset + 4); } //Security Layer Enc/Dec /** * @group Cryptography */ function chacha20_poly1305_decryptAndVerify(key, nonce, aad, ciphertext, authTag) { if (nonce.length < 12) { // openssl 3.x.x requires 98 bits nonce length nonce = Buffer.concat([ Buffer.alloc(12 - nonce.length, 0), nonce, ]); } const decipher = crypto_1.default.createDecipheriv("chacha20-poly1305", key, nonce, { authTagLength: 16 }); if (aad) { decipher.setAAD(aad, { plaintextLength: ciphertext.length }); } decipher.setAuthTag(authTag); const plaintext = decipher.update(ciphertext); decipher.final(); // final call verifies integrity using the auth tag. Throws error if something was manipulated! return plaintext; } /** * @group Cryptography */ function chacha20_poly1305_encryptAndSeal(key, nonce, aad, plaintext) { if (nonce.length < 12) { // openssl 3.x.x requires 98 bits nonce length nonce = Buffer.concat([ Buffer.alloc(12 - nonce.length, 0), nonce, ]); } const cipher = crypto_1.default.createCipheriv("chacha20-poly1305", key, nonce, { authTagLength: 16 }); if (aad) { cipher.setAAD(aad, { plaintextLength: plaintext.length }); } const ciphertext = cipher.update(plaintext); cipher.final(); // final call creates the auth tag const authTag = cipher.getAuthTag(); return { ciphertext: ciphertext, authTag: authTag, }; } /** * @group Cryptography */ function layerEncrypt(data, encryption) { let result = Buffer.alloc(0); const total = data.length; for (let offset = 0; offset < total;) { const length = Math.min(total - offset, 0x400); const leLength = Buffer.alloc(2); leLength.writeUInt16LE(length, 0); const nonce = Buffer.alloc(8); writeUInt64LE(encryption.accessoryToControllerCount++, nonce, 0); const encrypted = chacha20_poly1305_encryptAndSeal(encryption.accessoryToControllerKey, nonce, leLength, data.slice(offset, offset + length)); offset += length; result = Buffer.concat([result, leLength, encrypted.ciphertext, encrypted.authTag]); } return result; } /** * @group Cryptography */ function layerDecrypt(packet, encryption) { if (encryption.incompleteFrame) { packet = Buffer.concat([encryption.incompleteFrame, packet]); encryption.incompleteFrame = undefined; } let result = Buffer.alloc(0); const total = packet.length; for (let offset = 0; offset < total;) { const realDataLength = packet.slice(offset, offset + 2).readUInt16LE(0); const availableDataLength = total - offset - 2 - 16; if (realDataLength > availableDataLength) { // Fragmented packet encryption.incompleteFrame = packet.slice(offset); break; } const nonce = Buffer.alloc(8); writeUInt64LE(encryption.controllerToAccessoryCount++, nonce, 0); const plaintext = chacha20_poly1305_decryptAndVerify(encryption.controllerToAccessoryKey, nonce, packet.slice(offset, offset + 2), packet.slice(offset + 2, offset + 2 + realDataLength), packet.slice(offset + 2 + realDataLength, offset + 2 + realDataLength + 16)); result = Buffer.concat([result, plaintext]); offset += (18 + realDataLength); } return result; } //# sourceMappingURL=hapCrypto.js.map