blinksocks/lib/presets/obfs-tls1.2-ticket.js
2018-02-22 21:10:40 +08:00

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;