node-appletv-x
Version:
A Node.js library for communicating with an Apple TV
488 lines (408 loc) • 13.3 kB
JavaScript
;
/* chacha20 - 256 bits */
// Written in 2014 by Devi Mandiri. Public domain.
//
// Implementation derived from chacha-ref.c version 20080118
// See for details: http://cr.yp.to/chacha/chacha-20080128.pdf
module.exports = {
Chacha20Ctx: Chacha20Ctx,
chacha20_keysetup: chacha20_keysetup,
chacha20_ivsetup: chacha20_ivsetup,
chacha20_keystream: chacha20_keystream,
chacha20_update: chacha20_update,
chacha20_final: chacha20_final,
Poly1305Ctx: Poly1305Ctx,
poly1305_init: poly1305_init,
poly1305_update: poly1305_update,
poly1305_finish: poly1305_finish,
poly1305_auth: poly1305_auth,
poly1305_verify: poly1305_verify
};
var Chacha20KeySize = 32;
var Chacha20NonceSize = 8;
function Chacha20Ctx() {
this.input = new Array(16);
this.leftover = 0;
this.buffer = Buffer.alloc(64);
};
function load32(x, i) {
return x[i] | (x[i+1]<<8) | (x[i+2]<<16) | (x[i+3]<<24);
}
function store32(x, i, u) {
x[i] = u & 0xff; u >>>= 8;
x[i+1] = u & 0xff; u >>>= 8;
x[i+2] = u & 0xff; u >>>= 8;
x[i+3] = u & 0xff;
}
function plus(v, w) {
return (v + w) >>> 0;
}
function rotl32(v, c) {
return ((v << c) >>> 0) | (v >>> (32 - c));
}
function quarterRound(x, a, b, c, d) {
x[a] = plus(x[a], x[b]); x[d] = rotl32(x[d] ^ x[a], 16);
x[c] = plus(x[c], x[d]); x[b] = rotl32(x[b] ^ x[c], 12);
x[a] = plus(x[a], x[b]); x[d] = rotl32(x[d] ^ x[a], 8);
x[c] = plus(x[c], x[d]); x[b] = rotl32(x[b] ^ x[c], 7);
}
function chacha20_keysetup(ctx, key) {
ctx.input[0] = 1634760805;
ctx.input[1] = 857760878;
ctx.input[2] = 2036477234;
ctx.input[3] = 1797285236;
for (var i = 0; i < 8; i++) {
ctx.input[i+4] = load32(key, i*4);
}
}
function chacha20_ivsetup(ctx, iv) {
ctx.input[12] = 0;
ctx.input[13] = 0;
ctx.input[14] = load32(iv, 0);
ctx.input[15] = load32(iv, 4);
}
function chacha20_encrypt(ctx, dst, src, len) {
var x = new Array(16);
var buf = new Array(64);
var i = 0, dpos = 0, spos = 0;
while (len > 0 ) {
for (i = 16; i--;) x[i] = ctx.input[i];
for (i = 20; i > 0; i -= 2) {
quarterRound(x, 0, 4, 8,12);
quarterRound(x, 1, 5, 9,13);
quarterRound(x, 2, 6,10,14);
quarterRound(x, 3, 7,11,15);
quarterRound(x, 0, 5,10,15);
quarterRound(x, 1, 6,11,12);
quarterRound(x, 2, 7, 8,13);
quarterRound(x, 3, 4, 9,14);
}
for (i = 16; i--;) x[i] += ctx.input[i];
for (i = 16; i--;) store32(buf, 4*i, x[i]);
ctx.input[12] = plus(ctx.input[12], 1);
if (!ctx.input[12]) {
ctx.input[13] = plus(ctx.input[13], 1);
}
if (len <= 64) {
for (i = len; i--;) {
dst[i+dpos] = src[i+spos] ^ buf[i];
}
return;
}
for (i = 64; i--;) {
dst[i+dpos] = src[i+spos] ^ buf[i];
}
len -= 64;
spos += 64;
dpos += 64;
}
}
function chacha20_decrypt(ctx, dst, src, len) {
chacha20_encrypt(ctx, dst, src, len);
}
function chacha20_update(ctx, dst, src, inlen) {
var bytes = 0;
var out_start = 0;
var out_inc = 0;
if ((ctx.leftover + inlen) >= 64) {
if (ctx.leftover != 0) {
bytes = 64 - ctx.leftover;
if (src.length > 0) {
src.copy(ctx.buffer,ctx.leftover,0,bytes);
src = src.slice(bytes,src.length);
}
chacha20_encrypt(ctx,dst,ctx.buffer,64);
inlen -= bytes;
dst = dst.slice(64,dst.length);
out_inc += 64;
ctx.leftover = 0;
}
bytes = (inlen & (~63));
if (bytes > 0) {
chacha20_encrypt(ctx, dst, src, bytes);
inlen -= bytes;
src = src.slice(bytes,src.length);
dst = dst.slice(bytes,dst.length);
out_inc += bytes;
}
}
if (inlen > 0) {
if (src.length > 0) {
src.copy(ctx.buffer,ctx.leftover,0,src.length);
} else {
var zeros = Buffer.alloc(inlen);
zeros.copy(ctx.buffer,ctx.leftover,0,inlen);
}
ctx.leftover += inlen;
}
return out_inc - out_start;
}
function chacha20_final(ctx, dst) {
if (ctx.leftover != 0) {
chacha20_encrypt(ctx, dst, ctx.buffer, 64);
}
return ctx.leftover;
}
function chacha20_keystream(ctx, dst, len) {
for (var i = 0; i < len; ++i) dst[i] = 0;
chacha20_encrypt(ctx, dst, dst, len);
}
/* poly1305 */
// Written in 2014 by Devi Mandiri. Public domain.
//
// Implementation derived from poly1305-donna-16.h
// See for details: https://github.com/floodyberry/poly1305-donna
var Poly1305KeySize = 32;
var Poly1305TagSize = 16;
function Poly1305Ctx() {
this.buffer = new Array(Poly1305TagSize);
this.leftover = 0;
this.r = new Array(10);
this.h = new Array(10);
this.pad = new Array(8);
this.finished = 0;
};
function U8TO16(p, pos) {
return ((p[pos] & 0xff) & 0xffff) | (((p[pos+1] & 0xff) & 0xffff) << 8);
}
function U16TO8(p, pos, v) {
p[pos] = (v ) & 0xff;
p[pos+1] = (v >>> 8) & 0xff;
}
function poly1305_init(ctx, key) {
var t = [], i = 0;
for (i = 8; i--;) t[i] = U8TO16(key, i*2);
ctx.r[0] = t[0] & 0x1fff;
ctx.r[1] = ((t[0] >>> 13) | (t[1] << 3)) & 0x1fff;
ctx.r[2] = ((t[1] >>> 10) | (t[2] << 6)) & 0x1f03;
ctx.r[3] = ((t[2] >>> 7) | (t[3] << 9)) & 0x1fff;
ctx.r[4] = ((t[3] >>> 4) | (t[4] << 12)) & 0x00ff;
ctx.r[5] = (t[4] >>> 1) & 0x1ffe;
ctx.r[6] = ((t[4] >>> 14) | (t[5] << 2)) & 0x1fff;
ctx.r[7] = ((t[5] >>> 11) | (t[6] << 5)) & 0x1f81;
ctx.r[8] = ((t[6] >>> 8) | (t[7] << 8)) & 0x1fff;
ctx.r[9] = (t[7] >>> 5) & 0x007f;
for (i = 8; i--;) {
ctx.h[i] = 0;
ctx.pad[i] = U8TO16(key, 16+(2*i));
}
ctx.h[8] = 0;
ctx.h[9] = 0;
ctx.leftover = 0;
ctx.finished = 0;
}
function poly1305_blocks(ctx, m, mpos, bytes) {
var hibit = ctx.finished ? 0 : (1 << 11);
var t = [], d = [], c = 0, i = 0, j = 0;
while (bytes >= Poly1305TagSize) {
for (i = 8; i--;) t[i] = U8TO16(m, i*2+mpos);
ctx.h[0] += t[0] & 0x1fff;
ctx.h[1] += ((t[0] >>> 13) | (t[1] << 3)) & 0x1fff;
ctx.h[2] += ((t[1] >>> 10) | (t[2] << 6)) & 0x1fff;
ctx.h[3] += ((t[2] >>> 7) | (t[3] << 9)) & 0x1fff;
ctx.h[4] += ((t[3] >>> 4) | (t[4] << 12)) & 0x1fff;
ctx.h[5] += (t[4] >>> 1) & 0x1fff;
ctx.h[6] += ((t[4] >>> 14) | (t[5] << 2)) & 0x1fff;
ctx.h[7] += ((t[5] >>> 11) | (t[6] << 5)) & 0x1fff;
ctx.h[8] += ((t[6] >>> 8) | (t[7] << 8)) & 0x1fff;
ctx.h[9] += (t[7] >>> 5) | hibit;
for (i = 0, c = 0; i < 10; i++) {
d[i] = c;
for (j = 0; j < 10; j++) {
d[i] += (ctx.h[j] & 0xffffffff) * ((j <= i) ? ctx.r[i-j] : (5 * ctx.r[i+10-j]));
if (j === 4) {
c = (d[i] >>> 13);
d[i] &= 0x1fff;
}
}
c += (d[i] >>> 13);
d[i] &= 0x1fff;
}
c = ((c << 2) + c);
c += d[0];
d[0] = ((c & 0xffff) & 0x1fff);
c = (c >>> 13);
d[1] += c;
for (i = 10; i--;) ctx.h[i] = d[i] & 0xffff;
mpos += Poly1305TagSize;
bytes -= Poly1305TagSize;
}
}
function poly1305_update(ctx, m, bytes) {
var want = 0, i = 0, mpos = 0;
if (ctx.leftover) {
want = (Poly1305TagSize - ctx.leftover);
if (want > bytes)
want = bytes;
for (i = want; i--;) {
ctx.buffer[ctx.leftover+i] = m[i+mpos];
}
bytes -= want;
mpos += want;
ctx.leftover += want;
if (ctx.leftover < Poly1305TagSize)
return;
poly1305_blocks(ctx, ctx.buffer, 0, Poly1305TagSize);
ctx.leftover = 0;
}
if (bytes >= Poly1305TagSize) {
want = (bytes & ~(Poly1305TagSize - 1));
poly1305_blocks(ctx, m, mpos, want);
mpos += want;
bytes -= want;
}
if (bytes) {
for (i = bytes; i--;) {
ctx.buffer[ctx.leftover+i] = m[i+mpos];
}
ctx.leftover += bytes;
}
}
function poly1305_finish(ctx, mac) {
var g = [], c = 0, mask = 0, f = 0, i = 0;
if (ctx.leftover) {
i = ctx.leftover;
ctx.buffer[i++] = 1;
for (; i < Poly1305TagSize; i++) {
ctx.buffer[i] = 0;
}
ctx.finished = 1;
poly1305_blocks(ctx, ctx.buffer, 0, Poly1305TagSize);
}
c = ctx.h[1] >>> 13;
ctx.h[1] &= 0x1fff;
for (i = 2; i < 10; i++) {
ctx.h[i] += c;
c = ctx.h[i] >>> 13;
ctx.h[i] &= 0x1fff;
}
ctx.h[0] += (c * 5);
c = ctx.h[0] >>> 13;
ctx.h[0] &= 0x1fff;
ctx.h[1] += c;
c = ctx.h[1] >>> 13;
ctx.h[1] &= 0x1fff;
ctx.h[2] += c;
g[0] = ctx.h[0] + 5;
c = g[0] >>> 13;
g[0] &= 0x1fff;
for (i = 1; i < 10; i++) {
g[i] = ctx.h[i] + c;
c = g[i] >>> 13;
g[i] &= 0x1fff;
}
g[9] -= (1 << 13);
g[9] &= 0xffff;
mask = (g[9] >>> 15) - 1;
for (i = 10; i--;) g[i] &= mask;
mask = ~mask;
for (i = 10; i--;) {
ctx.h[i] = (ctx.h[i] & mask) | g[i];
}
ctx.h[0] = ((ctx.h[0] ) | (ctx.h[1] << 13)) & 0xffff;
ctx.h[1] = ((ctx.h[1] >> 3) | (ctx.h[2] << 10)) & 0xffff;
ctx.h[2] = ((ctx.h[2] >> 6) | (ctx.h[3] << 7)) & 0xffff;
ctx.h[3] = ((ctx.h[3] >> 9) | (ctx.h[4] << 4)) & 0xffff;
ctx.h[4] = ((ctx.h[4] >> 12) | (ctx.h[5] << 1) | (ctx.h[6] << 14)) & 0xffff;
ctx.h[5] = ((ctx.h[6] >> 2) | (ctx.h[7] << 11)) & 0xffff;
ctx.h[6] = ((ctx.h[7] >> 5) | (ctx.h[8] << 8)) & 0xffff;
ctx.h[7] = ((ctx.h[8] >> 8) | (ctx.h[9] << 5)) & 0xffff;
f = (ctx.h[0] & 0xffffffff) + ctx.pad[0];
ctx.h[0] = f & 0xffff;
for (i = 1; i < 8; i++) {
f = (ctx.h[i] & 0xffffffff) + ctx.pad[i] + (f >>> 16);
ctx.h[i] = f & 0xffff;
}
for (i = 8; i--;) {
U16TO8(mac, i*2, ctx.h[i]);
ctx.pad[i] = 0;
}
for (i = 10; i--;) {
ctx.h[i] = 0;
ctx.r[i] = 0;
}
}
function poly1305_auth(mac, m, bytes, key) {
var ctx = new Poly1305Ctx();
poly1305_init(ctx, key);
poly1305_update(ctx, m, bytes);
poly1305_finish(ctx, mac);
}
function poly1305_verify(mac1, mac2) {
var dif = 0;
for (var i = 0; i < 16; i++) {
dif |= (mac1[i] ^ mac2[i]);
}
dif = (dif - 1) >>> 31;
return (dif & 1);
}
/* chacha20poly1305 AEAD */
// Written in 2014 by Devi Mandiri. Public domain.
// Caveat:
// http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#page-9
// specified P_MAX and A_MAX are 2^64 and C_MAX is 2^64+16.
// While according to http://www.ecma-international.org/ecma-262/5.1/#sec-15.4
// I think max input length = 2^32-1 = 4294967295 = ~3.9Gb due to the ToUint32 abstract operation.
// Whatever ;)
function AeadCtx(key) {
this.key = key;
};
function aead_init(c20ctx, key, nonce) {
chacha20_keysetup(c20ctx, key);
chacha20_ivsetup(c20ctx, nonce);
var subkey = [];
chacha20_keystream(c20ctx, subkey, 64);
return subkey.slice(0, 32);
}
function store64(dst, pos, num) {
var hi = 0, lo = num >>> 0;
if ((+(Math.abs(num))) >= 1) {
if (num > 0) {
hi = ((Math.min((+(Math.floor(num/4294967296))), 4294967295))|0) >>> 0;
} else {
hi = (~~((+(Math.ceil((num - +(((~~(num)))>>>0))/4294967296))))) >>> 0;
}
}
dst[pos] = lo & 0xff; lo >>>= 8;
dst[pos+1] = lo & 0xff; lo >>>= 8;
dst[pos+2] = lo & 0xff; lo >>>= 8;
dst[pos+3] = lo & 0xff;
dst[pos+4] = hi & 0xff; hi >>>= 8;
dst[pos+5] = hi & 0xff; hi >>>= 8;
dst[pos+6] = hi & 0xff; hi >>>= 8;
dst[pos+7] = hi & 0xff;
}
function aead_mac(key, ciphertext, data) {
var clen = ciphertext.length;
var dlen = data.length;
var m = new Array(clen + dlen + 16);
var i = dlen;
for (; i--;) m[i] = data[i];
store64(m, dlen, dlen);
for (i = clen; i--;) m[dlen+8+i] = ciphertext[i];
store64(m, clen+dlen+8, clen);
var mac = [];
poly1305_auth(mac, m, m.length, key);
return mac;
}
function aead_encrypt(ctx, nonce, input, ad) {
var c = new Chacha20Ctx();
var key = aead_init(c, ctx.key, nonce);
var ciphertext = [];
chacha20_encrypt(c, ciphertext, input, input.length);
var mac = aead_mac(key, ciphertext, ad);
var out = [];
out = out.concat(ciphertext, mac);
return out;
}
function aead_decrypt(ctx, nonce, ciphertext, ad) {
var c = new Chacha20Ctx();
var key = aead_init(c, ctx.key, nonce);
var clen = ciphertext.length - Poly1305TagSize;
var digest = ciphertext.slice(clen);
var mac = aead_mac(key, ciphertext.slice(0, clen), ad);
if (poly1305_verify(digest, mac) !== 1) return false;
var out = [];
chacha20_decrypt(c, out, ciphertext, clen);
return out;
}