Switch to XChaCha20-Poly1305
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 1.3.3-r1
|
## 1.4.0-r1
|
||||||
|
- Switched packet encryption to XChaCha20-Poly1305
|
||||||
|
- Allow external XChaCha20-Poly1305 encryption/decryption
|
||||||
- Support writing base64 string data
|
- Support writing base64 string data
|
||||||
- Allow external ChaCha20 encryption/decryption
|
|
||||||
- Renamed 'delete/delete_file' to 'remove/remove_file'
|
- Renamed 'delete/delete_file' to 'remove/remove_file'
|
||||||
|
|
||||||
## 1.3.1-r3
|
## 1.3.1-r3
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@blockstorage/repertory-js",
|
"name": "@blockstorage/repertory-js",
|
||||||
"version": "1.3.3-r1",
|
"version": "1.4.0-r1",
|
||||||
"description": "A Node.js module for interfacing with Repertory's remote mount API",
|
"description": "A Node.js module for interfacing with Repertory's remote mount API",
|
||||||
"author": "scott.e.graves@protonmail.com",
|
"author": "scott.e.graves@protonmail.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
"prepublish": "rollup -c --silent && ./fixup"
|
"prepublish": "rollup -c --silent && ./fixup"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@stablelib/xchacha20poly1305": "^1.0.1",
|
||||||
"int64-buffer": "^1.0.0",
|
"int64-buffer": "^1.0.0",
|
||||||
"socket-pool": "^1.2.3",
|
"socket-pool": "^1.2.3",
|
||||||
"text-encoding": "^0.7.0",
|
"text-encoding": "^0.7.0",
|
||||||
|
|||||||
@@ -7,3 +7,13 @@ test('can construct a packet', () => {
|
|||||||
expect(p.buffer).toBeNull();
|
expect(p.buffer).toBeNull();
|
||||||
expect(p.decode_offset).toEqual(0);
|
expect(p.decode_offset).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can encrypt and decrypt a packet', async () => {
|
||||||
|
console.log('testing');
|
||||||
|
const p = new packet('my password');
|
||||||
|
p.encode_utf8('moose');
|
||||||
|
await p.encrypt();
|
||||||
|
await p.decrypt();
|
||||||
|
const str = p.decode_utf8();
|
||||||
|
expect(str).toEqual('moose');
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Int64BE, Uint64BE } from 'int64-buffer';
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { TextEncoder } from 'text-encoding';
|
import { TextEncoder } from 'text-encoding';
|
||||||
import { getCustomEncryption } from '../utils/constants';
|
import { getCustomEncryption } from '../utils/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
be_ui8_array_to_i16,
|
be_ui8_array_to_i16,
|
||||||
be_ui8_array_to_i32,
|
be_ui8_array_to_i32,
|
||||||
@@ -18,14 +17,13 @@ import {
|
|||||||
ui8_array_to_ui8,
|
ui8_array_to_ui8,
|
||||||
ui8_to_ui8_array,
|
ui8_to_ui8_array,
|
||||||
} from '../utils/byte_order';
|
} from '../utils/byte_order';
|
||||||
import JSChaCha20 from '../utils/jschacha20';
|
import {XChaCha20Poly1305} from '@stablelib/xchacha20poly1305';
|
||||||
|
|
||||||
export default class packet {
|
export default class packet {
|
||||||
constructor(token) {
|
constructor(token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
static HEADER_STRING = 'repertory';
|
|
||||||
static HEADER = new TextEncoder().encode(this.HEADER_STRING);
|
static HEADER = new TextEncoder().encode(this.HEADER_STRING);
|
||||||
|
|
||||||
buffer = null;
|
buffer = null;
|
||||||
@@ -164,7 +162,8 @@ export default class packet {
|
|||||||
hash = hash.update(new TextEncoder().encode(this.token));
|
hash = hash.update(new TextEncoder().encode(this.token));
|
||||||
|
|
||||||
const key = Uint8Array.from(hash.digest());
|
const key = Uint8Array.from(hash.digest());
|
||||||
const nonce = this.buffer.slice(0, 12);
|
const nonce = this.buffer.slice(0, 24);
|
||||||
|
const mac = this.buffer.slice(24, 16);
|
||||||
|
|
||||||
const customEncryption = getCustomEncryption();
|
const customEncryption = getCustomEncryption();
|
||||||
if (customEncryption) {
|
if (customEncryption) {
|
||||||
@@ -172,23 +171,18 @@ export default class packet {
|
|||||||
await customEncryption.decrypt(
|
await customEncryption.decrypt(
|
||||||
Buffer.from(key).toString('base64'),
|
Buffer.from(key).toString('base64'),
|
||||||
Buffer.from(nonce).toString('base64'),
|
Buffer.from(nonce).toString('base64'),
|
||||||
Buffer.from(this.buffer.slice(12)).toString('base64')
|
Buffer.from(mac).toString('base64'),
|
||||||
|
Buffer.from(this.buffer.slice(40)).toString('base64')
|
||||||
),
|
),
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const aad = ui32_to_be_ui8_array(this.buffer.length);
|
||||||
this.buffer = Buffer.from(
|
this.buffer = Buffer.from(
|
||||||
new JSChaCha20(key, nonce, 0).decrypt(this.buffer.slice(12))
|
new XChaCha20Poly1305(key).open(nonce, this.buffer.slice(24), aad),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.decode_offset = packet.HEADER.length;
|
|
||||||
|
|
||||||
const header = this.buffer.slice(0, 9);
|
|
||||||
if (header.toString() !== packet.HEADER_STRING) {
|
|
||||||
return Promise.reject(new Error('Header does not match'));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.buffer = new Uint8Array(this.buffer);
|
this.buffer = new Uint8Array(this.buffer);
|
||||||
return this.buffer;
|
return this.buffer;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -284,13 +278,12 @@ export default class packet {
|
|||||||
|
|
||||||
encrypt = async (nonce) => {
|
encrypt = async (nonce) => {
|
||||||
try {
|
try {
|
||||||
this.push_buffer(packet.HEADER);
|
|
||||||
let hash = crypto.createHash('sha256');
|
let hash = crypto.createHash('sha256');
|
||||||
hash = hash.update(new TextEncoder().encode(this.token));
|
hash = hash.update(new TextEncoder().encode(this.token));
|
||||||
|
|
||||||
const key = Uint8Array.from(hash.digest());
|
const key = Uint8Array.from(hash.digest());
|
||||||
if (!nonce) {
|
if (!nonce) {
|
||||||
nonce = Uint8Array.from(randomBytes(12));
|
nonce = Uint8Array.from(randomBytes(24));
|
||||||
}
|
}
|
||||||
|
|
||||||
const customEncryption = getCustomEncryption();
|
const customEncryption = getCustomEncryption();
|
||||||
@@ -306,9 +299,10 @@ export default class packet {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.buffer = new JSChaCha20(key, nonce, 0).encrypt(this.buffer);
|
const aad = ui32_to_be_ui8_array(this.buffer.length + 40)
|
||||||
}
|
this.buffer = new XChaCha20Poly1305(key).seal(nonce, this.buffer, aad);
|
||||||
this.push_buffer(nonce);
|
this.push_buffer(nonce);
|
||||||
|
}
|
||||||
|
|
||||||
return this.buffer;
|
return this.buffer;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1,322 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2017, Bubelich Mykola
|
|
||||||
* https://www.bubelich.com
|
|
||||||
*
|
|
||||||
* (。◕‿‿◕。)
|
|
||||||
*
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met, 0x
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of the copyright holder nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
* POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* ChaCha20 is a stream cipher designed by D. J. Bernstein.
|
|
||||||
* It is a refinement of the Salsa20 algorithm, and it uses a 256-bit key.
|
|
||||||
*
|
|
||||||
* ChaCha20 successively calls the ChaCha20 block function, with the same key and nonce, and with successively increasing block counter parameters.
|
|
||||||
* ChaCha20 then serializes the resulting state by writing the numbers in little-endian order, creating a keystream block.
|
|
||||||
*
|
|
||||||
* Concatenating the keystream blocks from the successive blocks forms a keystream.
|
|
||||||
* The ChaCha20 function then performs an XOR of this keystream with the plaintext.
|
|
||||||
* Alternatively, each keystream block can be XORed with a plaintext block before proceeding to create_or_open the next block, saving some memory.
|
|
||||||
* There is no requirement for the plaintext to be an integral multiple of 512 bits. If there is extra keystream from the last block, it is discarded.
|
|
||||||
*
|
|
||||||
* The inputs to ChaCha20 are
|
|
||||||
* - 256-bit key
|
|
||||||
* - 32-bit initial counter
|
|
||||||
* - 96-bit nonce. In some protocols, this is known as the Initialization Vector
|
|
||||||
* - Arbitrary-length plaintext
|
|
||||||
*
|
|
||||||
* Implementation derived from chacha-ref.c version 20080118
|
|
||||||
* See for details, 0x http, 0x//cr.yp.to/chacha/chacha-20080128.pdf
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} key
|
|
||||||
* @param {Uint8Array} nonce
|
|
||||||
* @param {number} counter
|
|
||||||
* @throws {Error}
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
var JSChaCha20 = function (key, nonce, counter) {
|
|
||||||
if (typeof counter === 'undefined') {
|
|
||||||
counter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(key instanceof Uint8Array) || key.length !== 32) {
|
|
||||||
throw new Error('Key should be 32 byte array!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(nonce instanceof Uint8Array) || nonce.length !== 12) {
|
|
||||||
throw new Error('Nonce should be 12 byte array!');
|
|
||||||
}
|
|
||||||
|
|
||||||
this._rounds = 20;
|
|
||||||
// Constants
|
|
||||||
this._sigma = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
|
|
||||||
|
|
||||||
// param construction
|
|
||||||
this._param = [
|
|
||||||
this._sigma[0],
|
|
||||||
this._sigma[1],
|
|
||||||
this._sigma[2],
|
|
||||||
this._sigma[3],
|
|
||||||
// key
|
|
||||||
this._get32(key, 0),
|
|
||||||
this._get32(key, 4),
|
|
||||||
this._get32(key, 8),
|
|
||||||
this._get32(key, 12),
|
|
||||||
this._get32(key, 16),
|
|
||||||
this._get32(key, 20),
|
|
||||||
this._get32(key, 24),
|
|
||||||
this._get32(key, 28),
|
|
||||||
// counter
|
|
||||||
counter,
|
|
||||||
// nonce
|
|
||||||
this._get32(nonce, 0),
|
|
||||||
this._get32(nonce, 4),
|
|
||||||
this._get32(nonce, 8),
|
|
||||||
];
|
|
||||||
|
|
||||||
// init 64 byte keystream block //
|
|
||||||
this._keystream = [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
// internal byte counter //
|
|
||||||
this._byteCounter = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
JSChaCha20.prototype._chacha = function () {
|
|
||||||
var mix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
||||||
var i = 0;
|
|
||||||
var b = 0;
|
|
||||||
|
|
||||||
// copy param array to mix //
|
|
||||||
for (i = 0; i < 16; i++) {
|
|
||||||
mix[i] = this._param[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// mix rounds //
|
|
||||||
for (i = 0; i < this._rounds; i += 2) {
|
|
||||||
this._quarterround(mix, 0, 4, 8, 12);
|
|
||||||
this._quarterround(mix, 1, 5, 9, 13);
|
|
||||||
this._quarterround(mix, 2, 6, 10, 14);
|
|
||||||
this._quarterround(mix, 3, 7, 11, 15);
|
|
||||||
|
|
||||||
this._quarterround(mix, 0, 5, 10, 15);
|
|
||||||
this._quarterround(mix, 1, 6, 11, 12);
|
|
||||||
this._quarterround(mix, 2, 7, 8, 13);
|
|
||||||
this._quarterround(mix, 3, 4, 9, 14);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < 16; i++) {
|
|
||||||
// add
|
|
||||||
mix[i] += this._param[i];
|
|
||||||
|
|
||||||
// store keystream
|
|
||||||
this._keystream[b++] = mix[i] & 0xff;
|
|
||||||
this._keystream[b++] = (mix[i] >>> 8) & 0xff;
|
|
||||||
this._keystream[b++] = (mix[i] >>> 16) & 0xff;
|
|
||||||
this._keystream[b++] = (mix[i] >>> 24) & 0xff;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The basic operation of the ChaCha algorithm is the quarter round.
|
|
||||||
* It operates on four 32-bit unsigned integers, denoted a, b, c, and d.
|
|
||||||
*
|
|
||||||
* @param {Array} output
|
|
||||||
* @param {number} a
|
|
||||||
* @param {number} b
|
|
||||||
* @param {number} c
|
|
||||||
* @param {number} d
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
JSChaCha20.prototype._quarterround = function (output, a, b, c, d) {
|
|
||||||
output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 16);
|
|
||||||
output[b] = this._rotl(output[b] ^ (output[c] += output[d]), 12);
|
|
||||||
output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 8);
|
|
||||||
output[b] = this._rotl(output[b] ^ (output[c] += output[d]), 7);
|
|
||||||
|
|
||||||
// JavaScript hack to make UINT32 :) //
|
|
||||||
output[a] >>>= 0;
|
|
||||||
output[b] >>>= 0;
|
|
||||||
output[c] >>>= 0;
|
|
||||||
output[d] >>>= 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Little-endian to uint 32 bytes
|
|
||||||
*
|
|
||||||
* @param {Uint8Array|[number]} data
|
|
||||||
* @param {number} index
|
|
||||||
* @return {number}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
JSChaCha20.prototype._get32 = function (data, index) {
|
|
||||||
return (
|
|
||||||
data[index++] ^
|
|
||||||
(data[index++] << 8) ^
|
|
||||||
(data[index++] << 16) ^
|
|
||||||
(data[index] << 24)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cyclic left rotation
|
|
||||||
*
|
|
||||||
* @param {number} data
|
|
||||||
* @param {number} shift
|
|
||||||
* @return {number}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
JSChaCha20.prototype._rotl = function (data, shift) {
|
|
||||||
return (data << shift) | (data >>> (32 - shift));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt data with key and nonce
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} data
|
|
||||||
* @return {Uint8Array}
|
|
||||||
*/
|
|
||||||
JSChaCha20.prototype.encrypt = function (data) {
|
|
||||||
return this._update(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt data with key and nonce
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} data
|
|
||||||
* @return {Uint8Array}
|
|
||||||
*/
|
|
||||||
JSChaCha20.prototype.decrypt = function (data) {
|
|
||||||
return this._update(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt or Decrypt data with key and nonce
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} data
|
|
||||||
* @return {Uint8Array}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
JSChaCha20.prototype._update = function (data) {
|
|
||||||
if (!(data instanceof Uint8Array) || data.length === 0) {
|
|
||||||
throw new Error('Data should be type of bytes (Uint8Array) and not empty!');
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = new Uint8Array(data.length);
|
|
||||||
|
|
||||||
// core function, build block and xor with input data //
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
if (this._byteCounter === 0 || this._byteCounter === 64) {
|
|
||||||
// generate new block //
|
|
||||||
|
|
||||||
this._chacha();
|
|
||||||
// counter increment //
|
|
||||||
this._param[12]++;
|
|
||||||
|
|
||||||
// reset internal counter //
|
|
||||||
this._byteCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
output[i] = data[i] ^ this._keystream[this._byteCounter++];
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
};
|
|
||||||
|
|
||||||
// EXPORT //
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports = JSChaCha20;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user