199 lines
7.3 KiB
JavaScript
199 lines
7.3 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _crypto = require('crypto');
|
|
|
|
var _crypto2 = _interopRequireDefault(_crypto);
|
|
|
|
var _defs = require('./defs');
|
|
|
|
var _utils = require('../utils');
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
const TLS_STAGE_HELLO = 1;
|
|
const TLS_STAGE_CHANGE_CIPHER_SPEC = 2;
|
|
const TLS_STAGE_APPLICATION_DATA = 3;
|
|
|
|
const MIN_AD_PAYLOAD_LEN = 0x0800;
|
|
const MAX_AD_PAYLOAD_LEN = 0x3FFF;
|
|
|
|
function stb(str) {
|
|
return Buffer.from(str, 'hex');
|
|
}
|
|
|
|
function getUTC() {
|
|
return (0, _utils.numberToBuffer)((0, _utils.getCurrentTimestampInt)(), 4);
|
|
}
|
|
|
|
function ApplicationData(buffer) {
|
|
const len = (0, _utils.numberToBuffer)(buffer.length);
|
|
return Buffer.concat([stb('170303'), len, buffer]);
|
|
}
|
|
|
|
class ObfsTls12TicketPreset extends _defs.IPreset {
|
|
constructor(...args) {
|
|
var _temp;
|
|
|
|
return _temp = super(...args), this._sni = [], this._stage = TLS_STAGE_HELLO, this._staging = Buffer.alloc(0), this._adBuf = null, _temp;
|
|
}
|
|
|
|
static onCheckParams({ sni }) {
|
|
if (typeof sni === 'undefined') {
|
|
throw Error('\'sni\' must be set');
|
|
}
|
|
if (!Array.isArray(sni)) {
|
|
sni = [sni];
|
|
}
|
|
if (sni.some(s => typeof s !== 'string' || s.length < 1)) {
|
|
throw Error('\'sni\' must be a non-empty string or an array without empty strings');
|
|
}
|
|
}
|
|
|
|
onInit({ sni }) {
|
|
this._sni = Array.isArray(sni) ? sni : [sni];
|
|
this._adBuf = new _utils.AdvancedBuffer({ getPacketLength: this.onReceiving.bind(this) });
|
|
this._adBuf.on('data', this.onChunkReceived.bind(this));
|
|
}
|
|
|
|
onDestroy() {
|
|
this._adBuf.clear();
|
|
this._adBuf = null;
|
|
this._staging = null;
|
|
this._sni = null;
|
|
}
|
|
|
|
getRandomSNI() {
|
|
const index = _crypto2.default.randomBytes(1)[0] % this._sni.length;
|
|
return Buffer.from(this._sni[index]);
|
|
}
|
|
|
|
clientOut({ buffer, next }) {
|
|
if (this._stage === TLS_STAGE_HELLO) {
|
|
this._stage = TLS_STAGE_CHANGE_CIPHER_SPEC;
|
|
this._staging = buffer;
|
|
|
|
|
|
const sni = this.getRandomSNI();
|
|
|
|
const random = [...getUTC(), ..._crypto2.default.randomBytes(28)];
|
|
|
|
const session = [...stb('20'), ..._crypto2.default.randomBytes(0x20)];
|
|
|
|
const cipher_suites = [...stb('001a'), ...stb('c02b'), ...stb('c02f'), ...stb('c02c'), ...stb('c030'), ...stb('cc14'), ...stb('cc13'), ...stb('c013'), ...stb('c014'), ...stb('009c'), ...stb('009d'), ...stb('002f'), ...stb('0035'), ...stb('000a')];
|
|
|
|
const ext_server_name = [...stb('0000'), ...(0, _utils.numberToBuffer)(2 + 1 + 2 + sni.length), ...(0, _utils.numberToBuffer)(1 + 2 + sni.length), ...stb('00'), ...(0, _utils.numberToBuffer)(sni.length), ...sni];
|
|
|
|
const ticketLen = (0, _utils.getRandomInt)(200, 400);
|
|
const session_ticket = [...stb('0023'), ...(0, _utils.numberToBuffer)(ticketLen), ..._crypto2.default.randomBytes(ticketLen)];
|
|
|
|
const exts = [...stb('ff01000100'), ...ext_server_name, ...stb('00170000'), ...session_ticket, ...stb('000d00140012040308040401050308050501080606010201'), ...stb('000500050100000000'), ...stb('00120000'), ...stb('75500000'), ...stb('000b00020100'), ...stb('000a0006000400170018')];
|
|
|
|
const body = [...stb('0303'), ...random, ...session, ...cipher_suites, ...stb('01'), ...stb('00'), ...(0, _utils.numberToBuffer)(exts.length), ...exts];
|
|
const header = [...stb('16'), ...stb('0301'), ...(0, _utils.numberToBuffer)(1 + 3 + body.length), ...stb('01'), ...(0, _utils.numberToBuffer)(body.length, 3)];
|
|
return next(Buffer.from([...header, ...body]));
|
|
}
|
|
|
|
if (this._stage === TLS_STAGE_CHANGE_CIPHER_SPEC) {
|
|
this._staging = Buffer.concat([this._staging, buffer]);
|
|
}
|
|
|
|
if (this._stage === TLS_STAGE_APPLICATION_DATA) {
|
|
const chunks = (0, _utils.getRandomChunks)(buffer, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN).map(chunk => ApplicationData(chunk));
|
|
return Buffer.concat(chunks);
|
|
}
|
|
}
|
|
|
|
serverIn({ buffer, next, fail }) {
|
|
if (this._stage === TLS_STAGE_HELLO) {
|
|
this._stage = TLS_STAGE_CHANGE_CIPHER_SPEC;
|
|
|
|
if (buffer.length < 200) {
|
|
fail(`TLS handshake header is too short, length=${buffer.length} dump=${buffer.slice(0, 100).toString('hex')}`);
|
|
return;
|
|
}
|
|
|
|
if (!buffer.slice(0, 3).equals(stb('160301'))) {
|
|
fail(`invalid TLS handshake header=${buffer.slice(0, 3).toString('hex')}, want=160301, dump=${buffer.slice(0, 100).toString('hex')}`);
|
|
return;
|
|
}
|
|
|
|
const tlsLen = buffer.slice(3, 5).readUInt16BE(0);
|
|
|
|
if (tlsLen !== buffer.length - 5) {
|
|
fail(`unexpected TLS handshake body length=${buffer.length - 5}, want=${tlsLen}, dump=${buffer.slice(0, 100).toString('hex')}`);
|
|
return;
|
|
}
|
|
|
|
const random = [...getUTC(), ..._crypto2.default.randomBytes(28)];
|
|
|
|
const session = [...stb('20'), ..._crypto2.default.randomBytes(0x20)];
|
|
|
|
const exts = [...stb('ff01000100'), ...stb('00050000'), ...stb('00170000')];
|
|
|
|
const body = [...stb('0303'), ...random, ...session, ...stb('c02f'), ...stb('00'), ...(0, _utils.numberToBuffer)(exts.length), ...exts];
|
|
|
|
const header = [...stb('16'), ...stb('0303'), ...(0, _utils.numberToBuffer)(1 + 3 + body.length), ...stb('02'), ...(0, _utils.numberToBuffer)(body.length, 3)];
|
|
|
|
const server_hello = [...header, ...body];
|
|
|
|
const ticket = _crypto2.default.randomBytes((0, _utils.getRandomInt)(200, 255));
|
|
const session_ticket = [...stb('000004b0'), ...(0, _utils.numberToBuffer)(ticket.length), ...ticket];
|
|
const new_session_ticket_body = [...stb('04'), ...(0, _utils.numberToBuffer)(session_ticket.length, 3), ...session_ticket];
|
|
const new_session_ticket = [...stb('160303'), ...(0, _utils.numberToBuffer)(new_session_ticket_body.length), ...new_session_ticket_body];
|
|
|
|
const change_cipher_spec = [...stb('140303000101')];
|
|
|
|
const finishedLen = (0, _utils.getRandomInt)(32, 40);
|
|
const finished = [...stb('16'), ...stb('0303'), ...(0, _utils.numberToBuffer)(finishedLen), ..._crypto2.default.randomBytes(finishedLen)];
|
|
|
|
return next(Buffer.from([...server_hello, ...new_session_ticket, ...change_cipher_spec, ...finished]), true);
|
|
}
|
|
|
|
let _buffer = buffer;
|
|
|
|
if (this._stage === TLS_STAGE_CHANGE_CIPHER_SPEC) {
|
|
this._stage = TLS_STAGE_APPLICATION_DATA;
|
|
|
|
_buffer = buffer.slice(43);
|
|
}
|
|
|
|
this._adBuf.put(_buffer, { next, fail });
|
|
}
|
|
|
|
serverOut({ buffer }) {
|
|
const chunks = (0, _utils.getRandomChunks)(buffer, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN).map(chunk => ApplicationData(chunk));
|
|
return Buffer.concat(chunks);
|
|
}
|
|
|
|
clientIn({ buffer, next, fail }) {
|
|
if (this._stage === TLS_STAGE_CHANGE_CIPHER_SPEC) {
|
|
this._stage = TLS_STAGE_APPLICATION_DATA;
|
|
|
|
const change_cipher_spec = [...stb('140303000101')];
|
|
|
|
const finished = [...stb('16'), ...stb('0303'), ...stb('0020'), ..._crypto2.default.randomBytes(0x20)];
|
|
|
|
const chunks = (0, _utils.getRandomChunks)(this._staging, MIN_AD_PAYLOAD_LEN, MAX_AD_PAYLOAD_LEN).map(chunk => ApplicationData(chunk));
|
|
this._staging = null;
|
|
return next(Buffer.from([...change_cipher_spec, ...finished, ...Buffer.concat(chunks)]), true);
|
|
}
|
|
this._adBuf.put(buffer, { next, fail });
|
|
}
|
|
|
|
onReceiving(buffer) {
|
|
if (buffer.length < 5) {
|
|
return;
|
|
}
|
|
return 5 + buffer.readUInt16BE(3);
|
|
}
|
|
|
|
onChunkReceived(chunk, { next }) {
|
|
next(chunk.slice(5));
|
|
}
|
|
|
|
}
|
|
exports.default = ObfsTls12TicketPreset; |