1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.generateCurve25519KeyPair = generateCurve25519KeyPair;
|
4 | exports.generateCurve25519SharedSecKey = generateCurve25519SharedSecKey;
|
5 | exports.HKDF = HKDF;
|
6 | exports.writeUInt64LE = writeUInt64LE;
|
7 | exports.chacha20_poly1305_decryptAndVerify = chacha20_poly1305_decryptAndVerify;
|
8 | exports.chacha20_poly1305_encryptAndSeal = chacha20_poly1305_encryptAndSeal;
|
9 | exports.layerEncrypt = layerEncrypt;
|
10 | exports.layerDecrypt = layerDecrypt;
|
11 | const tslib_1 = require("tslib");
|
12 | const assert_1 = tslib_1.__importDefault(require("assert"));
|
13 | const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
14 | const futoin_hkdf_1 = tslib_1.__importDefault(require("futoin-hkdf"));
|
15 | const tweetnacl_1 = tslib_1.__importDefault(require("tweetnacl"));
|
16 | if (!crypto_1.default.getCiphers().includes("chacha20-poly1305")) {
|
17 | assert_1.default.fail("The cipher 'chacha20-poly1305' is not supported with your current running nodejs version v" + process.version + ". " +
|
18 | "At least a nodejs version of v10.17.0 (excluding v11.0 and v11.1) is required!");
|
19 | }
|
20 |
|
21 |
|
22 |
|
23 | function generateCurve25519KeyPair() {
|
24 | return tweetnacl_1.default.box.keyPair();
|
25 | }
|
26 |
|
27 |
|
28 |
|
29 | function generateCurve25519SharedSecKey(priKey, pubKey) {
|
30 | return tweetnacl_1.default.scalarMult(priKey, pubKey);
|
31 | }
|
32 |
|
33 |
|
34 |
|
35 | function HKDF(hashAlg, salt, ikm, info, size) {
|
36 | return (0, futoin_hkdf_1.default)(ikm, size, { hash: hashAlg, salt: salt, info: info });
|
37 | }
|
38 | const MAX_UINT32 = 0x00000000FFFFFFFF;
|
39 | const MAX_INT53 = 0x001FFFFFFFFFFFFF;
|
40 | function uintHighLow(number) {
|
41 | (0, assert_1.default)(number > -1 && number <= MAX_INT53, "number out of range");
|
42 | (0, assert_1.default)(Math.floor(number) === number, "number must be an integer");
|
43 | let high = 0;
|
44 | const signbit = number & 0xFFFFFFFF;
|
45 | const low = signbit < 0 ? (number & 0x7FFFFFFF) + 0x80000000 : signbit;
|
46 | if (number > MAX_UINT32) {
|
47 | high = (number - low) / (MAX_UINT32 + 1);
|
48 | }
|
49 | return [high, low];
|
50 | }
|
51 |
|
52 |
|
53 |
|
54 | function writeUInt64LE(number, buffer, offset = 0) {
|
55 | const hl = uintHighLow(number);
|
56 | buffer.writeUInt32LE(hl[1], offset);
|
57 | buffer.writeUInt32LE(hl[0], offset + 4);
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | function chacha20_poly1305_decryptAndVerify(key, nonce, aad, ciphertext, authTag) {
|
64 | if (nonce.length < 12) {
|
65 | nonce = Buffer.concat([
|
66 | Buffer.alloc(12 - nonce.length, 0),
|
67 | nonce,
|
68 | ]);
|
69 | }
|
70 | const decipher = crypto_1.default.createDecipheriv("chacha20-poly1305", key, nonce, { authTagLength: 16 });
|
71 | if (aad) {
|
72 | decipher.setAAD(aad, { plaintextLength: ciphertext.length });
|
73 | }
|
74 | decipher.setAuthTag(authTag);
|
75 | const plaintext = decipher.update(ciphertext);
|
76 | decipher.final();
|
77 | return plaintext;
|
78 | }
|
79 |
|
80 |
|
81 |
|
82 | function chacha20_poly1305_encryptAndSeal(key, nonce, aad, plaintext) {
|
83 | if (nonce.length < 12) {
|
84 | nonce = Buffer.concat([
|
85 | Buffer.alloc(12 - nonce.length, 0),
|
86 | nonce,
|
87 | ]);
|
88 | }
|
89 | const cipher = crypto_1.default.createCipheriv("chacha20-poly1305", key, nonce, { authTagLength: 16 });
|
90 | if (aad) {
|
91 | cipher.setAAD(aad, { plaintextLength: plaintext.length });
|
92 | }
|
93 | const ciphertext = cipher.update(plaintext);
|
94 | cipher.final();
|
95 | const authTag = cipher.getAuthTag();
|
96 | return {
|
97 | ciphertext: ciphertext,
|
98 | authTag: authTag,
|
99 | };
|
100 | }
|
101 |
|
102 |
|
103 |
|
104 | function layerEncrypt(data, encryption) {
|
105 | let result = Buffer.alloc(0);
|
106 | const total = data.length;
|
107 | for (let offset = 0; offset < total;) {
|
108 | const length = Math.min(total - offset, 0x400);
|
109 | const leLength = Buffer.alloc(2);
|
110 | leLength.writeUInt16LE(length, 0);
|
111 | const nonce = Buffer.alloc(8);
|
112 | writeUInt64LE(encryption.accessoryToControllerCount++, nonce, 0);
|
113 | const encrypted = chacha20_poly1305_encryptAndSeal(encryption.accessoryToControllerKey, nonce, leLength, data.slice(offset, offset + length));
|
114 | offset += length;
|
115 | result = Buffer.concat([result, leLength, encrypted.ciphertext, encrypted.authTag]);
|
116 | }
|
117 | return result;
|
118 | }
|
119 |
|
120 |
|
121 |
|
122 | function layerDecrypt(packet, encryption) {
|
123 | if (encryption.incompleteFrame) {
|
124 | packet = Buffer.concat([encryption.incompleteFrame, packet]);
|
125 | encryption.incompleteFrame = undefined;
|
126 | }
|
127 | let result = Buffer.alloc(0);
|
128 | const total = packet.length;
|
129 | for (let offset = 0; offset < total;) {
|
130 | const realDataLength = packet.slice(offset, offset + 2).readUInt16LE(0);
|
131 | const availableDataLength = total - offset - 2 - 16;
|
132 | if (realDataLength > availableDataLength) {
|
133 | encryption.incompleteFrame = packet.slice(offset);
|
134 | break;
|
135 | }
|
136 | const nonce = Buffer.alloc(8);
|
137 | writeUInt64LE(encryption.controllerToAccessoryCount++, nonce, 0);
|
138 | 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));
|
139 | result = Buffer.concat([result, plaintext]);
|
140 | offset += (18 + realDataLength);
|
141 | }
|
142 | return result;
|
143 | }
|
144 |
|
\ | No newline at end of file |