331 lines
8.5 KiB
JavaScript
331 lines
8.5 KiB
JavaScript
import { randomBytes } from 'crypto';
|
|
import { Int64BE, Uint64BE } from 'int64-buffer';
|
|
import blake2b from 'blake2b';
|
|
import { TextEncoder } from 'text-encoding';
|
|
import { getCustomEncryption } from '../utils/constants';
|
|
import {
|
|
be_ui8_array_to_i16,
|
|
be_ui8_array_to_i32,
|
|
be_ui8_array_to_ui16,
|
|
be_ui8_array_to_ui32,
|
|
i16_to_be_ui8_array,
|
|
i32_to_be_ui8_array,
|
|
i8_to_ui8_array,
|
|
ui16_to_be_ui8_array,
|
|
ui32_to_be_ui8_array,
|
|
ui8_array_to_i8,
|
|
ui8_array_to_ui8,
|
|
ui8_to_ui8_array,
|
|
} from '../utils/byte_order';
|
|
import { XChaCha20Poly1305 } from '@stablelib/xchacha20poly1305';
|
|
|
|
export default class packet {
|
|
constructor(token) {
|
|
this.token = token;
|
|
}
|
|
|
|
buffer = new Uint8Array(0);
|
|
decode_offset = 0;
|
|
token;
|
|
|
|
append_buffer = (buffer) => {
|
|
if (!(buffer instanceof Uint8Array)) {
|
|
throw new Error('Buffer must be of type Uint8Array');
|
|
}
|
|
|
|
this.buffer = this.buffer
|
|
? new Uint8Array([...this.buffer, ...buffer])
|
|
: buffer;
|
|
};
|
|
|
|
clear = () => {
|
|
this.buffer = null;
|
|
this.decode_offset = 0;
|
|
};
|
|
|
|
decode_buffer = (length) => {
|
|
if (!this.buffer) {
|
|
throw new Error('Invalid buffer');
|
|
}
|
|
|
|
const ret = this.buffer.slice(
|
|
this.decode_offset,
|
|
this.decode_offset + length
|
|
);
|
|
this.decode_offset += length;
|
|
return Buffer.from(ret);
|
|
};
|
|
|
|
decode_stat = () => {
|
|
const mode = this.decode_ui16();
|
|
const nlink = this.decode_ui16();
|
|
const uid = this.decode_ui32();
|
|
const gid = this.decode_ui32();
|
|
const atime = this.decode_ui64();
|
|
const mtime = this.decode_ui64();
|
|
const ctime = this.decode_ui64();
|
|
const birth_time = this.decode_ui64();
|
|
const size = this.decode_ui64();
|
|
const blocks = this.decode_ui64();
|
|
const blksize = this.decode_ui32();
|
|
const flags = this.decode_ui32();
|
|
const directory = !!this.decode_ui8();
|
|
return {
|
|
mode,
|
|
nlink,
|
|
uid,
|
|
gid,
|
|
atime,
|
|
mtime,
|
|
ctime,
|
|
birth_time,
|
|
size,
|
|
blocks,
|
|
blksize,
|
|
flags,
|
|
directory,
|
|
};
|
|
};
|
|
|
|
decode_utf8 = () => {
|
|
if (!this.buffer) {
|
|
throw new Error('Invalid buffer');
|
|
}
|
|
|
|
const startIndex = this.decode_offset;
|
|
const endIndex = this.buffer.indexOf(0, startIndex);
|
|
if (endIndex >= 0) {
|
|
let ret = '';
|
|
for (let i = startIndex; i < endIndex; i++) {
|
|
ret += String.fromCharCode(this.buffer[i]);
|
|
}
|
|
this.decode_offset = endIndex + 1;
|
|
return ret;
|
|
}
|
|
|
|
throw new Error('String not found in buffer');
|
|
};
|
|
|
|
decode_i8 = () => {
|
|
return ui8_array_to_i8(this.buffer, this.decode_offset++);
|
|
};
|
|
|
|
decode_ui8 = () => {
|
|
return ui8_array_to_ui8(this.buffer, this.decode_offset++);
|
|
};
|
|
|
|
decode_i16 = () => {
|
|
const ret = be_ui8_array_to_i16(this.buffer, this.decode_offset);
|
|
this.decode_offset += 2;
|
|
return ret;
|
|
};
|
|
|
|
decode_ui16 = () => {
|
|
const ret = be_ui8_array_to_ui16(this.buffer, this.decode_offset);
|
|
this.decode_offset += 2;
|
|
return ret;
|
|
};
|
|
|
|
decode_i32 = () => {
|
|
const ret = be_ui8_array_to_i32(this.buffer, this.decode_offset);
|
|
this.decode_offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
decode_ui32 = () => {
|
|
const ret = be_ui8_array_to_ui32(this.buffer, this.decode_offset);
|
|
this.decode_offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
decode_i64 = () => {
|
|
const ret = new Int64BE(
|
|
this.buffer.slice(this.decode_offset, this.decode_offset + 8)
|
|
);
|
|
this.decode_offset += 8;
|
|
return ret.toString(10);
|
|
};
|
|
|
|
decode_ui64 = () => {
|
|
const ret = new Uint64BE(
|
|
this.buffer.slice(this.decode_offset, this.decode_offset + 8)
|
|
);
|
|
this.decode_offset += 8;
|
|
return ret.toString(10);
|
|
};
|
|
|
|
decrypt = async () => {
|
|
try {
|
|
let hash = blake2b(32);
|
|
hash = hash.update(new TextEncoder().encode(this.token));
|
|
|
|
const key = Uint8Array.from(hash.digest());
|
|
const nonce = this.buffer.slice(0, 24);
|
|
const mac = this.buffer.slice(24, 16 + 24);
|
|
|
|
const customEncryption = getCustomEncryption();
|
|
if (customEncryption) {
|
|
this.buffer = Buffer.from(
|
|
await customEncryption.decrypt(
|
|
Buffer.from(key).toString('base64'),
|
|
Buffer.from(nonce).toString('base64'),
|
|
Buffer.from(mac).toString('base64'),
|
|
Buffer.from(this.buffer.slice(40)).toString('base64')
|
|
),
|
|
'base64'
|
|
);
|
|
} else {
|
|
const aad = ui32_to_be_ui8_array(this.buffer.length);
|
|
this.buffer = new Uint8Array([
|
|
...this.buffer.slice(nonce.length + mac.length),
|
|
...this.buffer.slice(nonce.length, nonce.length + mac.length),
|
|
]);
|
|
const result = new XChaCha20Poly1305(key).open(nonce, this.buffer, aad);
|
|
if (!result) {
|
|
throw new Error('decryption failed');
|
|
}
|
|
this.buffer = Buffer.from(result);
|
|
}
|
|
|
|
this.buffer = new Uint8Array(this.buffer);
|
|
return this.buffer;
|
|
} catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
};
|
|
|
|
encode_buffer = (buffer) => {
|
|
this.append_buffer(new Uint8Array(buffer));
|
|
};
|
|
|
|
encode_i8 = (num) => {
|
|
this.append_buffer(i8_to_ui8_array(num));
|
|
};
|
|
|
|
encode_top_i8 = (num) => {
|
|
this.push_buffer(i8_to_ui8_array(num));
|
|
};
|
|
|
|
encode_u8 = (num) => {
|
|
this.append_buffer(ui8_to_ui8_array(num));
|
|
};
|
|
|
|
encode_top_u8 = (num) => {
|
|
this.push_buffer(ui8_to_ui8_array(num));
|
|
};
|
|
|
|
encode_i16 = (num) => {
|
|
this.append_buffer(i16_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_top_i16 = (num) => {
|
|
this.push_buffer(i16_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_ui16 = (num) => {
|
|
this.append_buffer(ui16_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_top_ui16 = (num) => {
|
|
this.push_buffer(ui16_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_i32 = (num) => {
|
|
this.append_buffer(i32_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_top_i32 = (num) => {
|
|
this.push_buffer(i32_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_ui32 = (num) => {
|
|
this.append_buffer(ui32_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_top_ui32 = (num) => {
|
|
this.push_buffer(ui32_to_be_ui8_array(num));
|
|
};
|
|
|
|
encode_i64 = (num) => {
|
|
this.append_buffer(new Uint8Array(new Int64BE(num).toArray()));
|
|
};
|
|
|
|
encode_top_i64 = (num) => {
|
|
this.push_buffer(new Uint8Array(new Int64BE(num).toArray()));
|
|
};
|
|
|
|
encode_ui64 = (num) => {
|
|
this.append_buffer(new Uint8Array(new Uint64BE(num).toArray()));
|
|
};
|
|
|
|
encode_top_ui64 = (num) => {
|
|
this.push_buffer(new Uint8Array(new Uint64BE(num).toArray()));
|
|
};
|
|
|
|
encode_utf8 = (str) => {
|
|
if (!(typeof str === 'string' || str instanceof String)) {
|
|
throw new Error('Value must be of type string');
|
|
}
|
|
|
|
const buffer = new Uint8Array([...new TextEncoder().encode(str), 0]);
|
|
this.append_buffer(buffer);
|
|
};
|
|
|
|
encode_top_utf8 = (str) => {
|
|
if (!(typeof str === 'string' || str instanceof String)) {
|
|
throw new Error('Value must be of type string');
|
|
}
|
|
|
|
const buffer = new Uint8Array([...new TextEncoder().encode(str), 0]);
|
|
this.push_buffer(buffer);
|
|
};
|
|
|
|
encrypt = async (nonce) => {
|
|
try {
|
|
let hash = blake2b(32);
|
|
hash = hash.update(new TextEncoder().encode(this.token));
|
|
|
|
const key = Uint8Array.from(hash.digest());
|
|
if (!nonce) {
|
|
nonce = Uint8Array.from(randomBytes(24));
|
|
}
|
|
|
|
const customEncryption = getCustomEncryption();
|
|
if (customEncryption) {
|
|
this.buffer = new Uint8Array(
|
|
Buffer.from(
|
|
await customEncryption.encrypt(
|
|
Buffer.from(key).toString('base64'),
|
|
Buffer.from(nonce).toString('base64'),
|
|
Buffer.from(this.buffer).toString('base64')
|
|
),
|
|
'base64'
|
|
)
|
|
);
|
|
} else {
|
|
const aad = ui32_to_be_ui8_array(this.buffer.length + 40);
|
|
this.buffer = new XChaCha20Poly1305(key).seal(nonce, this.buffer, aad);
|
|
this.buffer = new Uint8Array([
|
|
...this.buffer.slice(this.buffer.length - 16),
|
|
...this.buffer.slice(0, this.buffer.length - 16),
|
|
]);
|
|
this.push_buffer(nonce);
|
|
}
|
|
|
|
return this.buffer;
|
|
} catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
};
|
|
|
|
push_buffer = (buffer) => {
|
|
if (!(buffer instanceof Uint8Array)) {
|
|
throw new Error('Buffer must be of type Uint8Array');
|
|
}
|
|
|
|
this.buffer = this.buffer
|
|
? new Uint8Array([...buffer, ...this.buffer])
|
|
: buffer;
|
|
};
|
|
}
|