/* eslint-disable no-bitwise */ import * as events from 'events'; import * as tls from 'tls'; import { encodeTunnelPW, openTLSSockets, startTLSServer } from '../tls/crypt'; import { AdditionalAuthHandler, ResponseAuthHandler } from '../types/Handler'; import { PAPChallenge } from './challenges/pap'; import { IEAPType } from '../types/EAPType'; interface IEAPResponseHandlers { response: (respData?: Buffer, msgType?: number) => void; checkAuth: ResponseAuthHandler; } export class EAPTTLS implements IEAPType { papChallenge: PAPChallenge; constructor(private sendEAPResponse) { this.papChallenge = new PAPChallenge(); } decode(msg: Buffer) { const flags = msg.slice(5, 6).readUInt8(0); // .toString('hex'); // if (flags) // @todo check if "L" flag is set in flags const decodedFlags = { lengthIncluded: flags & 0b010000000, moreFragments: flags & 0b001000000, start: flags & 0b000100000, reserved: flags & 0b000011000, version: flags & 0b010000111 }; let msglength; if (decodedFlags.lengthIncluded) { msglength = msg.slice(6, 10).readInt32BE(0); // .readDoubleLE(0); // .toString('hex'); } const data = msg.slice(decodedFlags.lengthIncluded ? 10 : 6, msg.length); return { decodedFlags, msglength, data }; } handleMessage(msg: Buffer, state: string, handlers, identifier: number) { const { decodedFlags, msglength, data } = this.decode(msg); // check if no data package is there and we have something in the queue, if so.. empty the queue first if (!data || data.length === 0) { // @todo: queue processing console.warn( `>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS, ACK / NACK (no data, just a confirmation, ID: ${identifier})` ); return; } console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS', { // flags: `00000000${flags.toString(2)}`.substr(-8), decodedFlags, identifier, /* 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | L | M | S | R | R | V | +---+---+---+---+---+---+---+---+ L = Length included M = More fragments S = Start R = Reserved V = Version (000 for EAP-TTLSv0) */ msglength, data, dataStr: data.toString() }); let currentConnection = openTLSSockets.get(state) as | { events: events.EventEmitter; tls: tls.TLSSocket; currentHandlers: IEAPResponseHandlers } | undefined; if (!currentConnection) { const connection = startTLSServer(); currentConnection = { events: connection.events, tls: connection.tls, currentHandlers: handlers }; openTLSSockets.set(state, currentConnection); // register event listeners currentConnection.events.on('incoming', (incomingData: Buffer) => { const type = incomingData.slice(3, 4).readUInt8(0); // const code = data.slice(4, 5).readUInt8(0); switch (type) { case 1: // PAP / CHAP try { const { username, password } = this.papChallenge.decode(incomingData); currentConnection!.currentHandlers.checkAuth(username, password); } catch (err) { // pwd not found.. console.error('pwd not found', err); // NAK currentConnection!.currentHandlers.response(undefined, 3); /* this.sendEAPResponse( currentConnection!.currentHandlers.response, identifier, undefined, 3 ); */ currentConnection!.events.emit('end'); throw new Error(`pwd not found`); } break; default: console.log('data', incomingData); console.log('data str', incomingData.toString()); // currentConnection!.events.emit('end'); console.log('UNSUPPORTED AUTH TYPE, requesting PAP'); // throw new Error(`unsupported auth type${type}`); currentConnection!.currentHandlers.response(Buffer.from([1]), 3); /* this.sendEAPResponse( currentConnection!.currentHandlers.response, identifier, Buffer.from([1]), 3 ); */ } }); currentConnection.events.on('response', (responseData: Buffer) => { // console.log('sending encrypted data back to client', responseData); // send back... currentConnection!.currentHandlers.response(responseData); // this.sendEAPResponse(currentConnection!.currentHandlers.response, identifier, responseData); // this.sendMessage(TYPE.PRELOGIN, data, false); }); currentConnection.events.on('end', () => { // cleanup socket console.log('ENDING SOCKET'); openTLSSockets.del(state); }); } /* else { console.log('using existing socket'); } */ // update handlers currentConnection.currentHandlers = { response: (respData?: Buffer, msgType?: number) => this.sendEAPResponse(handlers.response, identifier, respData, msgType), checkAuth: (username: string, password: string) => { const additionalAuthHandler: AdditionalAuthHandler = (success, params) => { const buffer = Buffer.from([ success ? 3 : 4, // 3.. success, 4... failure identifier, 0, // length (1/2) 4 // length (2/2) ]); params.attributes.push(['EAP-Message', buffer]); if (params.packet.attributes && params.packet.attributes['User-Name']) { // reappend username to response params.attributes.push(['User-Name', params.packet.attributes['User-Name']]); } /* if (sess->eap_if->eapKeyDataLen > 64) { len = 32; } else { len = sess->eap_if->eapKeyDataLen / 2; } */ const keyingMaterial = (currentConnection?.tls as any).exportKeyingMaterial( 128, 'ttls keying material' ); // console.log('keyingMaterial', keyingMaterial); // eapKeyData + len params.attributes.push([ 'Vendor-Specific', 311, [ [ 16, encodeTunnelPW( keyingMaterial.slice(64), (params.packet as any).authenticator, // params.packet.attributes['Message-Authenticator'], params.secret ) ] ] ]); // MS-MPPE-Send-Key // eapKeyData params.attributes.push([ 'Vendor-Specific', 311, [ [ 17, encodeTunnelPW( keyingMaterial.slice(0, 64), (params.packet as any).authenticator, // params.packet.attributes['Message-Authenticator'], params.secret ) ] ] ]); // MS-MPPE-Recv-Key }; return handlers.checkAuth(username, password, additionalAuthHandler); } }; // emit data to tls server currentConnection.events.emit('send', data); } }