UNPKG

@octalmage/node-appletv

Version:

A Node.js library for communicating with an Apple TV

81 lines (80 loc) 2.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const sodium_1 = require("sodium"); const crypto = require("crypto"); const number_1 = require("./number"); function computePoly1305(cipherText, AAD, nonce, key) { if (AAD == null) { AAD = Buffer.alloc(0); } const msg = Buffer.concat([ AAD, getPadding(AAD, 16), cipherText, getPadding(cipherText, 16), number_1.default.UInt53toBufferLE(AAD.length), number_1.default.UInt53toBufferLE(cipherText.length) ]); const polyKey = sodium_1.api.crypto_stream_chacha20(32, nonce, key); const computed_hmac = sodium_1.api.crypto_onetimeauth(msg, polyKey); polyKey.fill(0); return computed_hmac; } // i'd really prefer for this to be a direct call to // Sodium.crypto_aead_chacha20poly1305_decrypt() // but unfortunately the way it constructs the message to // calculate the HMAC is not compatible with homekit // (long story short, it uses [ AAD, AAD.length, CipherText, CipherText.length ] // whereas homekit expects [ AAD, CipherText, AAD.length, CipherText.length ] function verifyAndDecrypt(cipherText, mac, AAD, nonce, key) { const matches = sodium_1.api.crypto_verify_16(mac, computePoly1305(cipherText, AAD, nonce, key)); if (matches === 0) { return sodium_1.api .crypto_stream_chacha20_xor_ic(cipherText, nonce, 1, key); } return null; } // See above about calling directly into libsodium. function encryptAndSeal(plainText, AAD, nonce, key) { const cipherText = sodium_1.api .crypto_stream_chacha20_xor_ic(plainText, nonce, 1, key); const hmac = computePoly1305(cipherText, AAD, nonce, key); return [cipherText, hmac]; } function getPadding(buffer, blockSize) { return buffer.length % blockSize === 0 ? Buffer.alloc(0) : Buffer.alloc(blockSize - (buffer.length % blockSize)); } function HKDF(hashAlg, salt, ikm, info, size) { // create the hash alg to see if it exists and get its length var hash = crypto.createHash(hashAlg); var hashLength = hash.digest().length; // now we compute the PRK var hmac = crypto.createHmac(hashAlg, salt); hmac.update(ikm); var prk = hmac.digest(); var prev = Buffer.alloc(0); var output; var buffers = []; var num_blocks = Math.ceil(size / hashLength); info = Buffer.from(info); for (var i = 0; i < num_blocks; i++) { var hmac = crypto.createHmac(hashAlg, prk); var input = Buffer.concat([ prev, info, Buffer.from(String.fromCharCode(i + 1)) ]); hmac.update(input); prev = hmac.digest(); buffers.push(prev); } output = Buffer.concat(buffers, size); return output.slice(0, size); } exports.default = { encryptAndSeal, verifyAndDecrypt, HKDF };