UNPKG

@octalmage/node-appletv

Version:

A Node.js library for communicating with an Apple TV

103 lines (85 loc) 2.95 kB
import { api as Sodium } from 'sodium'; import * as crypto from 'crypto'; import number from './number'; function computePoly1305(cipherText: Buffer, AAD: Buffer, nonce: Buffer, key: Buffer): Buffer { if (AAD == null) { AAD = Buffer.alloc(0); } const msg = Buffer.concat([ AAD, getPadding(AAD, 16), cipherText, getPadding(cipherText, 16), number.UInt53toBufferLE(AAD.length), number.UInt53toBufferLE(cipherText.length) ]) const polyKey = Sodium.crypto_stream_chacha20(32, nonce, key); const computed_hmac = Sodium.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: Buffer, mac: Buffer, AAD: Buffer, nonce: Buffer, key: Buffer): Buffer { const matches = Sodium.crypto_verify_16( mac, computePoly1305(cipherText, AAD, nonce, key) ); if (matches === 0) { return Sodium .crypto_stream_chacha20_xor_ic(cipherText, nonce, 1, key); } return null; } // See above about calling directly into libsodium. function encryptAndSeal(plainText: Buffer, AAD: Buffer, nonce: Buffer, key: Buffer): Buffer[] { const cipherText = Sodium .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: string, salt: Buffer, ikm: Buffer, info: Buffer, size: number): Buffer { // 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); } export default { encryptAndSeal, verifyAndDecrypt, HKDF }