From 60ec84ae8ed37e1489d99bab85d0803d5d704fd6 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 25 Feb 2020 11:54:57 +0100 Subject: [PATCH] refactor: improve ttls tunnel stuff and fix some byte sizes for AVPs --- README.md | 10 +- src/auth/LDAPAuth.ts | 1 - src/radius/PacketHandler.ts | 40 ++++ src/radius/RadiusService.ts | 35 +-- src/radius/handler/EAPPacketHandler.ts | 56 +++-- ...andler.ts => UserPasswordPacketHandler.ts} | 18 +- src/radius/handler/eap/eapMethods/EAP-GTC.ts | 37 ++- src/radius/handler/eap/eapMethods/EAP-MD5.ts | 2 - src/radius/handler/eap/eapMethods/EAP-TTLS.ts | 221 +++++++++++++----- src/radius/handler/eap/eapMethods/EAP.ts | 0 src/tls/crypt.ts | 10 +- src/types/EAPMethod.ts | 6 +- src/types/PacketHandler.ts | 19 +- 13 files changed, 296 insertions(+), 159 deletions(-) create mode 100644 src/radius/PacketHandler.ts rename src/radius/handler/{DefaultPacketHandler.ts => UserPasswordPacketHandler.ts} (53%) delete mode 100644 src/radius/handler/eap/eapMethods/EAP.ts diff --git a/README.md b/README.md index 4d46b6f..f188ba7 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,16 @@ Basic RADIUS Server for node.js for Google LDAP Service and WPA2 Enteprise WLAN Protect your WIFI access with a username and password by a credential provider you already use! +Authenticiation tested with Windows, Linux, Android and Apple devices. + ## Known Issues / Disclaimer -This is a first implementation draft, which is currently only working with a nodejs fork (see https://github.com/nodejs/node/pull/31814). +This is a first implementation, which is currently only working with node js nightly (node 13+) (see https://github.com/nodejs/node/pull/31814). -- PAP / CHAP RFC not found to implement this correctly -- a lot of bugs +- MD5 Challenge not implenented, but RFC says this is mandatory ;-) +- Inner Tunnel does not act differently, even though spec says that EAP-message are not allowed to get fragmented, +this is not a problem right now, as the messages of the inner tunnel are small enough, but it could be a bug in the future. +ways to approach this: refactor that the inner tunnel can set max fragment size, or rebuild eap fragments in ttls after inner tunnel response CONTRIBUTIONS WELCOME! If you are willing to help, just open a PR or contact me via bug system or simon.tretter@hokify.com. diff --git a/src/auth/LDAPAuth.ts b/src/auth/LDAPAuth.ts index cda9543..fc6572c 100644 --- a/src/auth/LDAPAuth.ts +++ b/src/auth/LDAPAuth.ts @@ -39,7 +39,6 @@ export class LDAPAuth implements IAuthentication { } async authenticate(username: string, password: string) { - // console.log('AUTH', this.ldap); const authResult: boolean = await new Promise((resolve, reject) => { this.ldap.authenticate(username, password, function(err, user) { if (err) { diff --git a/src/radius/PacketHandler.ts b/src/radius/PacketHandler.ts new file mode 100644 index 0000000..68a29cf --- /dev/null +++ b/src/radius/PacketHandler.ts @@ -0,0 +1,40 @@ +import { IPacket, IPacketHandler, IPacketHandlerResult } from '../types/PacketHandler'; +import { IAuthentication } from '../types/Authentication'; +import { EAPPacketHandler } from './handler/EAPPacketHandler'; +import { EAPTTLS } from './handler/eap/eapMethods/EAP-TTLS'; +import { EAPGTC } from './handler/eap/eapMethods/EAP-GTC'; +import { EAPMD5 } from './handler/eap/eapMethods/EAP-MD5'; +import { UserPasswordPacketHandler } from './handler/UserPasswordPacketHandler'; + +export class PacketHandler implements IPacketHandler { + packetHandlers: IPacketHandler[] = []; + + constructor(authentication: IAuthentication) { + this.packetHandlers.push( + new EAPPacketHandler([ + new EAPTTLS(authentication, this), + new EAPGTC(authentication), + new EAPMD5(authentication) + ]) + ); + this.packetHandlers.push(new UserPasswordPacketHandler(authentication)); + } + + async handlePacket(packet: IPacket, handlingType?: number) { + let response: IPacketHandlerResult; + + let i = 0; + if (!this.packetHandlers[i]) { + throw new Error('no packet handlers registered'); + } + + // process packet handlers until we get a response from one + do { + /* response is of type IPacketHandlerResult */ + response = await this.packetHandlers[i].handlePacket(packet, handlingType); + i++; + } while (this.packetHandlers[i] && (!response || !response.code)); + + return response; + } +} diff --git a/src/radius/RadiusService.ts b/src/radius/RadiusService.ts index a7b263b..2ff5ec0 100644 --- a/src/radius/RadiusService.ts +++ b/src/radius/RadiusService.ts @@ -1,25 +1,14 @@ import * as radius from 'radius'; import { IAuthentication } from '../types/Authentication'; -import { EAPPacketHandler } from './handler/EAPPacketHandler'; -import { DefaultPacketHandler } from './handler/DefaultPacketHandler'; -import { IPacketHandler, IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler'; +import { IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler'; -import { EAPTTLS } from './handler/eap/eapMethods/EAP-TTLS'; -import { EAPMD5 } from './handler/eap/eapMethods/EAP-MD5'; -import { EAPGTC } from './handler/eap/eapMethods/EAP-GTC'; +import { PacketHandler } from './PacketHandler'; export class RadiusService { - radiusPacketHandlers: IPacketHandler[] = []; + private packetHandler: PacketHandler; - constructor(private secret: string, private authentication: IAuthentication) { - this.radiusPacketHandlers.push( - new EAPPacketHandler([ - new EAPTTLS(authentication), - new EAPGTC(authentication), - new EAPMD5(authentication) - ]) - ); - this.radiusPacketHandlers.push(new DefaultPacketHandler(authentication)); + constructor(private secret: string, authentication: IAuthentication) { + this.packetHandler = new PacketHandler(authentication); } async handleMessage( @@ -32,19 +21,7 @@ export class RadiusService { return undefined; } - let response: IPacketHandlerResult; - - let i = 0; - if (!this.radiusPacketHandlers[i]) { - throw new Error('no packet handlers registered'); - } - - // process packet handlers until we get a response from one - do { - /* response is of type IPacketHandlerResult */ - response = await this.radiusPacketHandlers[i].handlePacket(packet.attributes, packet); - i++; - } while (this.radiusPacketHandlers[i] && (!response || !response.code)); + const response: IPacketHandlerResult = await this.packetHandler.handlePacket(packet); // still no response, we are done here if (!response || !response.code) { diff --git a/src/radius/handler/EAPPacketHandler.ts b/src/radius/handler/EAPPacketHandler.ts index 420f66c..2e2bfc8 100644 --- a/src/radius/handler/EAPPacketHandler.ts +++ b/src/radius/handler/EAPPacketHandler.ts @@ -1,40 +1,38 @@ // https://tools.ietf.org/html/rfc3748#section-4.1 import * as NodeCache from 'node-cache'; -import { RadiusPacket } from 'radius'; import debug from 'debug'; import { makeid } from '../../helpers'; -import { IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler'; +import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler'; import { IEAPMethod } from '../../types/EAPMethod'; import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper'; const log = debug('radius:eap'); export class EAPPacketHandler implements IPacketHandler { + private identities = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds + // private eapConnectionStates: { [key: string]: { validMethods: IEAPMethod[] } } = {}; private eapConnectionStates = new NodeCache({ useClones: false, stdTTL: 3600 }); // max for one hour constructor(private eapMethods: IEAPMethod[]) {} - async handlePacket( - attributes: { [key: string]: Buffer | string }, - orgRadiusPacket: RadiusPacket - ): Promise { - if (!attributes['EAP-Message']) { + async handlePacket(packet: IPacket, handlingType?: number): Promise { + if (!packet.attributes['EAP-Message']) { // not an EAP message return {}; } - const stateID = (attributes.State && attributes.State.toString()) || makeid(16); + const stateID = (packet.attributes.State && packet.attributes.State.toString()) || makeid(16); if (!this.eapConnectionStates.get(stateID)) { this.eapConnectionStates.set(stateID, { - validMethods: this.eapMethods // on init all registered eap methods are valid, we kick them out in case we get a NAK response + validMethods: this.eapMethods.filter(eap => eap.getEAPType() !== handlingType) // on init all registered eap methods are valid, we kick them out in case we get a NAK response }); } // EAP MESSAGE - const msg = attributes['EAP-Message'] as Buffer; + const msg = packet.attributes['EAP-Message'] as Buffer; const { code, type, identifier, data } = decodeEAPHeader(msg); @@ -45,7 +43,13 @@ export class EAPPacketHandler implements IPacketHandler { case 2: // for response switch (type) { case 1: // identifiy - log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', {}); + log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', stateID, data.toString()); + if (data) { + this.identities.set(stateID, data); // use token til binary 0.); + } else { + log('no msg'); + } + // start identify if (currentState.validMethods.length > 0) { return currentState.validMethods[0].identify(identifier, stateID, data); @@ -65,6 +69,7 @@ export class EAPPacketHandler implements IPacketHandler { console.error('not implemented type', type); break; case 3: // nak + // console.log('got NAK', data); if (data) { // if there is data, each data octect reprsents a eap method the clients supports, // kick out all unsupported ones @@ -73,27 +78,38 @@ export class EAPPacketHandler implements IPacketHandler { supportedEAPMethods.push(supportedMethod); } - this.eapConnectionStates.set(stateID, { - ...currentState, - validMethods: currentState.validMethods.filter(method => { - return supportedEAPMethods.includes(method.getEAPType()); // kick it out? - }) + currentState.validMethods = currentState.validMethods.filter(method => { + return supportedEAPMethods.includes(method.getEAPType()); // kick it out? }); + // save + this.eapConnectionStates.set(stateID, currentState); + + // new identidy request + // start identify + if (currentState.validMethods.length > 0) { + return currentState.validMethods[0].identify(identifier, stateID, data); + } } // continue with responding a NAK and add rest of supported methods // eslint-disable-next-line no-fallthrough default: { - const eapMethod = currentState.validMethods.find(method => { + const eapMethod = this.eapMethods.find(method => { return type === method.getEAPType(); }); if (eapMethod) { - return eapMethod.handleMessage(identifier, stateID, msg, orgRadiusPacket); + return eapMethod.handleMessage( + identifier, + stateID, + msg, + packet, + this.identities.get(stateID) + ); } // we do not support this auth type, ask for something we support - const serverSupportedMethods = currentState.validMethods.map( - method => method.getEAPType + const serverSupportedMethods = currentState.validMethods.map(method => + method.getEAPType() ); console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`); diff --git a/src/radius/handler/DefaultPacketHandler.ts b/src/radius/handler/UserPasswordPacketHandler.ts similarity index 53% rename from src/radius/handler/DefaultPacketHandler.ts rename to src/radius/handler/UserPasswordPacketHandler.ts index 6f87643..1153019 100644 --- a/src/radius/handler/DefaultPacketHandler.ts +++ b/src/radius/handler/UserPasswordPacketHandler.ts @@ -1,22 +1,29 @@ +import debug from 'debug'; import { IAuthentication } from '../../types/Authentication'; import { + IPacket, IPacketHandler, IPacketHandlerResult, PacketResponseCode } from '../../types/PacketHandler'; -export class DefaultPacketHandler implements IPacketHandler { +const log = debug('radius:user-pwd'); + +export class UserPasswordPacketHandler implements IPacketHandler { constructor(private authentication: IAuthentication) {} - async handlePacket(attributes: { [key: string]: Buffer }): Promise { - const username = attributes['User-Name']; - const password = attributes['User-Password']; + async handlePacket(packet: IPacket): Promise { + const username = packet.attributes['User-Name']; + const password = packet.attributes['User-Password']; if (!username || !password) { // params missing, this handler cannot continue... return {}; } + log('username', username, username.toString()); + log('token', password, password.toString()); + const authenticated = await this.authentication.authenticate( username.toString(), password.toString() @@ -24,7 +31,8 @@ export class DefaultPacketHandler implements IPacketHandler { if (authenticated) { // success return { - code: PacketResponseCode.AccessAccept + code: PacketResponseCode.AccessAccept, + attributes: [['User-Name', username]] }; } diff --git a/src/radius/handler/eap/eapMethods/EAP-GTC.ts b/src/radius/handler/eap/eapMethods/EAP-GTC.ts index 6664f5c..8b883ec 100644 --- a/src/radius/handler/eap/eapMethods/EAP-GTC.ts +++ b/src/radius/handler/eap/eapMethods/EAP-GTC.ts @@ -1,7 +1,6 @@ // https://tools.ietf.org/html/rfc5281 TTLS v0 // https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented) /* eslint-disable no-bitwise */ -import * as NodeCache from 'node-cache'; import debug from 'debug'; import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler'; import { IEAPMethod } from '../../../../types/EAPMethod'; @@ -11,24 +10,19 @@ import { buildEAPResponse, decodeEAPHeader } from '../EAPHelper'; const log = debug('radius:eap:gtc'); export class EAPGTC implements IEAPMethod { - private loginData = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds - getEAPType(): number { return 6; } - identify(identifier: number, stateID: string, msg?: Buffer): IPacketHandlerResult { - if (msg) { - const parsedMsg = msg.slice( - 0, - msg.findIndex(v => v === 0) - ); - log('identify', parsedMsg, parsedMsg.toString()); - this.loginData.set(stateID, parsedMsg); // use token til binary 0.); - } else { - log('no msg'); + extractValue(msg: Buffer) { + let tillBinary0 = msg.findIndex(v => v === 0) || msg.length; + if (tillBinary0 < 0) { + tillBinary0 = msg.length - 1; } + return msg.slice(0, tillBinary0 + 1); // use token til binary 0. + } + identify(identifier: number, _stateID: string): IPacketHandlerResult { return buildEAPResponse(identifier, 6, Buffer.from('Password: ')); } @@ -36,18 +30,16 @@ export class EAPGTC implements IEAPMethod { async handleMessage( _identifier: number, - stateID: string, - msg: Buffer + _stateID: string, + msg: Buffer, + _, + identity?: string ): Promise { - const username = this.loginData.get(stateID) as Buffer | undefined; + const username = identity; // this.loginData.get(stateID) as Buffer | undefined; const { data } = decodeEAPHeader(msg); - let tillBinary0 = data.findIndex(v => v === 0) || data.length; - if (tillBinary0 < 0) { - tillBinary0 = data.length - 1; - } - const token = data.slice(0, tillBinary0 + 1); // use token til binary 0. + const token = this.extractValue(data); if (!username) { throw new Error('no username'); @@ -59,7 +51,8 @@ export class EAPGTC implements IEAPMethod { const success = await this.authentication.authenticate(username.toString(), token.toString()); return { - code: success ? PacketResponseCode.AccessAccept : PacketResponseCode.AccessReject + code: success ? PacketResponseCode.AccessAccept : PacketResponseCode.AccessReject, + attributes: (success && [['User-Name', username]]) || undefined }; } } diff --git a/src/radius/handler/eap/eapMethods/EAP-MD5.ts b/src/radius/handler/eap/eapMethods/EAP-MD5.ts index 8c2552e..5c7ebf5 100644 --- a/src/radius/handler/eap/eapMethods/EAP-MD5.ts +++ b/src/radius/handler/eap/eapMethods/EAP-MD5.ts @@ -8,8 +8,6 @@ import { IPacketHandlerResult } from '../../../../types/PacketHandler'; import { IEAPMethod } from '../../../../types/EAPMethod'; import { IAuthentication } from '../../../../types/Authentication'; -const log = debug('radius:eap:md5'); - interface IEAPResponseHandlers { response: (respData?: Buffer, msgType?: number) => void; checkAuth: ResponseAuthHandler; diff --git a/src/radius/handler/eap/eapMethods/EAP-TTLS.ts b/src/radius/handler/eap/eapMethods/EAP-TTLS.ts index ab9db5b..79fe1cc 100644 --- a/src/radius/handler/eap/eapMethods/EAP-TTLS.ts +++ b/src/radius/handler/eap/eapMethods/EAP-TTLS.ts @@ -3,18 +3,23 @@ /* eslint-disable no-bitwise */ import * as tls from 'tls'; import * as NodeCache from 'node-cache'; -import { RadiusPacket } from 'radius'; +// @ts-ignore +import { attr_id_to_name, attr_name_to_id } from 'radius'; import debug from 'debug'; + import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt'; import { ResponseAuthHandler } from '../../../../types/Handler'; -import { PAPChallenge } from './challenges/PAPChallenge'; -import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler'; +import { + IPacket, + IPacketAttributes, + IPacketHandler, + IPacketHandlerResult, + PacketResponseCode +} from '../../../../types/PacketHandler'; import { MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from '../../../../helpers'; import { IEAPMethod } from '../../../../types/EAPMethod'; import { IAuthentication } from '../../../../types/Authentication'; import { secret } from '../../../../../config'; -import { EAPPacketHandler } from '../../EAPPacketHandler'; -import { EAPGTC } from './EAP-GTC'; const log = debug('radius:eap:ttls'); @@ -31,17 +36,24 @@ function tlsHasExportKeyingMaterial( return typeof (tlsSocket as any).exportKeyingMaterial === 'function'; } -export class EAPTTLS implements IEAPMethod { - private papChallenge: PAPChallenge = new PAPChallenge(); +interface IAVPEntry { + type: number; + flags: string; + decodedFlags: { + V: boolean; + M: boolean; + }; + length: number; + vendorId?: number; + data: Buffer; +} +export class EAPTTLS implements IEAPMethod { // { [key: string]: Buffer } = {}; private queueData = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds private openTLSSockets = new NodeCache({ useClones: false, stdTTL: 3600 }); // keep sockets for about one hour - // EAP TUNNEL - tunnelEAP = new EAPPacketHandler([new EAPGTC(this.authentication)]); // tunnel with GTC support - getEAPType(): number { return 21; } @@ -50,7 +62,7 @@ export class EAPTTLS implements IEAPMethod { return this.buildEAPTTLSResponse(identifier, 21, 0x20, stateID); } - constructor(private authentication: IAuthentication) {} + constructor(private authentication: IAuthentication, private innerTunnel: IPacketHandler) {} private buildEAPTTLS( identifier: number, @@ -179,7 +191,7 @@ export class EAPTTLS implements IEAPMethod { Message Length | Data... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ - const identifier = msg.slice(1, 2).readUInt8(0); + // const identifier = msg.slice(1, 2).readUInt8(0); const flags = msg.slice(5, 6).readUInt8(0); // .toString('hex'); /* 0 1 2 3 4 5 6 7 @@ -215,7 +227,7 @@ export class EAPTTLS implements IEAPMethod { log('>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS', { flags: `00000000${flags.toString(2)}`.substr(-8), decodedFlags, - identifier, + // identifier, msglength, data // dataStr: data.toString() @@ -232,7 +244,7 @@ export class EAPTTLS implements IEAPMethod { identifier: number, success: boolean, socket: tls.TLSSocket, - packet: RadiusPacket + packet: IPacket ): IPacketHandlerResult { const buffer = Buffer.from([ success ? 3 : 4, // 3.. success, 4... failure @@ -246,22 +258,26 @@ export class EAPTTLS implements IEAPMethod { if (packet.attributes && packet.attributes['User-Name']) { // reappend username to response - attributes.push(['User-Name', packet.attributes['User-Name']]); + attributes.push(['User-Name', packet.attributes['User-Name'].toString()]); } if (tlsHasExportKeyingMaterial(socket)) { - const keyingMaterial = (socket as any).exportKeyingMaterial(128, 'ttls keying material'); + const keyingMaterial = socket.exportKeyingMaterial(128, 'ttls keying material'); + + if (!packet.authenticator) { + throw new Error('FATAL: no packet authenticator variable set'); + } attributes.push([ 'Vendor-Specific', 311, - [[16, encodeTunnelPW(keyingMaterial.slice(64), (packet as any).authenticator, secret)]] + [[16, encodeTunnelPW(keyingMaterial.slice(64), packet.authenticator, secret)]] ]); // MS-MPPE-Send-Key attributes.push([ 'Vendor-Specific', 311, - [[17, encodeTunnelPW(keyingMaterial.slice(0, 64), (packet as any).authenticator, secret)]] + [[17, encodeTunnelPW(keyingMaterial.slice(0, 64), packet.authenticator, secret)]] ]); // MS-MPPE-Recv-Key } else { console.error( @@ -279,7 +295,7 @@ export class EAPTTLS implements IEAPMethod { identifier: number, stateID: string, msg: Buffer, - orgRadiusPacket: RadiusPacket + packet: IPacket ): Promise { const { data } = this.decodeTTLSMessage(msg); @@ -313,12 +329,58 @@ export class EAPTTLS implements IEAPMethod { ret.attributes = {}; ret.raw_attributes = []; - const { type, data: AVPdata, length: AVPlength } = this.decodeAVP(incomingData); + const AVPs = this.decodeAVPs(incomingData); - console.log('AVP data', { AVPdata, AVPlength, AVPdataStr: AVPdata.toString() }); + // build attributes for packet handler + const attributes: IPacketAttributes = {}; + AVPs.forEach(avp => { + attributes[attr_id_to_name(avp.type)] = avp.data; + }); - // const code = data.slice(4, 5).readUInt8(0); + attributes.State = `${stateID}-inner`; + // handle incoming package via inner tunnel + const result = await this.innerTunnel.handlePacket( + { + attributes + }, + this.getEAPType() + ); + + log('inner tunnel result', result); + + if ( + result.code === PacketResponseCode.AccessReject || + result.code === PacketResponseCode.AccessAccept + ) { + sendResponsePromise.resolve( + this.authResponse( + identifier, + result.code === PacketResponseCode.AccessAccept, + connection.tls, + { + ...packet, + attributes: { + ...packet.attributes, + ...this.transformAttributesArrayToMap(result.attributes) + } + } + ) + ); + return; + } + + const eapMessage = result.attributes?.find(attr => attr[0] === 'EAP-Message'); + if (!eapMessage) { + throw new Error('no eap message found'); + } + + connection.events.emit( + 'encrypt', + this.buildAVP(attr_name_to_id('EAP-Message'), eapMessage[1] as Buffer) + ); + + /* switch (type) { case 1: // PAP try { @@ -381,7 +443,7 @@ export class EAPTTLS implements IEAPMethod { this.buildAVP(79, this.buildEAPTTLS(identifier, 3, 0, stateID, Buffer.from([1]))) ); } - } + } */ }; const responseHandler = (encryptedResponseData: Buffer) => { @@ -407,9 +469,31 @@ export class EAPTTLS implements IEAPMethod { return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData); } - private decodeAVP(buffer: Buffer) { - /** - * 4.1. AVP Header + private transformAttributesArrayToMap(attributes: [string, Buffer | string][] | undefined) { + const result = {}; + attributes?.forEach(([key, value]) => { + result[key] = value; + }); + return result; + } + + private decodeAVPs(buffer: Buffer): IAVPEntry[] { + const results: { + type: number; + flags: string; + decodedFlags: { + V: boolean; + M: boolean; + }; + length: number; + vendorId?: number; + data: Buffer; + }[] = []; + + let currentBuffer = buffer; + do { + /** + * 4.1. AVP Header The fields in the AVP header MUST be sent in network byte order. The format of the header is: @@ -425,37 +509,47 @@ export class EAPTTLS implements IEAPMethod { +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data ... +-+-+-+-+-+-+-+-+ - */ - const type = buffer.slice(0, 4).readUInt32BE(0); - const flags = buffer.slice(4, 5).readUInt8(0); - const decodedFlags = { - // L - V: !!(flags & 0b10000000), - // M - M: !!(flags & 0b01000000) - }; + */ + const type = currentBuffer.slice(0, 4).readUInt32BE(0); + const flags = currentBuffer.slice(4, 5).readUInt8(0); + const decodedFlags = { + // L + V: !!(flags & 0b10000000), + // M + M: !!(flags & 0b01000000) + }; + + // const length = buffer.slice(5, 8).readUInt16BE(0); // actually a Int24BE + const length = currentBuffer.slice(6, 8).readUInt16BE(0); // actually a Int24BE + + let vendorId; + let data; + if (flags & 0b010000000) { + // V flag set + vendorId = currentBuffer.slice(8, 12).readUInt32BE(0); + data = currentBuffer.slice(12, length); + } else { + data = currentBuffer.slice(8, length); + } - // const length = buffer.slice(5, 8).readUInt16BE(0); // actually a Int24BE - const length = buffer.slice(6, 8).readUInt16BE(0); // actually a Int24BE + results.push({ + type, + flags: `00000000${flags.toString(2)}`.substr(-8), + decodedFlags, + length, + vendorId, + data + }); - let vendorId; - let data; - if (flags & 0b010000000) { - // V flag set - vendorId = buffer.slice(8, 12).readUInt32BE(0); - data = buffer.slice(8, 12); - } else { - data = buffer.slice(8); - } + // ensure length is a multiple of 4 octect + let totalAVPSize = length; + while (totalAVPSize % 4 !== 0) { + totalAVPSize += 1; + } + currentBuffer = currentBuffer.slice(totalAVPSize); + } while (currentBuffer.length > 0); - return { - type, - flags: `00000000${flags.toString(2)}`.substr(-8), - decodedFlags, - length, - vendorId, - data - }; + return results; } private buildAVP( @@ -481,9 +575,9 @@ export class EAPTTLS implements IEAPMethod { | Data ... +-+-+-+-+-+-+-+-+ */ - let b = Buffer.alloc(8); + let AVP = Buffer.alloc(8); - b.writeInt32BE(code, 0); // EAP-Message + AVP.writeInt32BE(code, 0); // EAP-Message /** * The 'V' (Vendor-Specific) bit indicates whether the optional Vendor-ID field is present. When set to 1, the Vendor-ID field is @@ -506,14 +600,19 @@ export class EAPTTLS implements IEAPMethod { flagValue += 0b01000000; } - console.log('flagValue', flagValue, `00000000${flagValue.toString(2)}`.substr(-8)); + // log('flagValue', flagValue, `00000000${flagValue.toString(2)}`.substr(-8)); - b.writeInt8(flagValue, 4); // flags (set V..) + AVP.writeInt8(flagValue, 4); // flags (set V..) - b = Buffer.concat([b, data]); // , Buffer.from('\0')]); + AVP = Buffer.concat([AVP, data]); // , Buffer.from('\0')]); - b.writeInt16BE(b.byteLength, 6); // write size (actually we would need a Int24BE here, but it is good to go with 16bits) + AVP.writeInt16BE(AVP.byteLength, 6); // write size (actually we would need a Int24BE here, but it is good to go with 16bits) + + // fill up with 0x00 till we have % 4 + while (AVP.length % 4 !== 0) { + AVP = Buffer.concat([AVP, Buffer.from([0x00])]); + } - return b; + return AVP; } } diff --git a/src/radius/handler/eap/eapMethods/EAP.ts b/src/radius/handler/eap/eapMethods/EAP.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/tls/crypt.ts b/src/tls/crypt.ts index 043beb5..bb315e7 100644 --- a/src/tls/crypt.ts +++ b/src/tls/crypt.ts @@ -5,7 +5,6 @@ import * as crypto from 'crypto'; import * as DuplexPair from 'native-duplexpair'; import * as constants from 'constants'; import debug from 'debug'; -import { makeid } from '../helpers'; import * as config from '../../config'; const log = debug('radius:tls'); @@ -136,11 +135,10 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin contents of each Salt field in a given Access-Accept packet MUST be unique. */ - const salt = Buffer.concat([ - // eslint-disable-next-line no-bitwise - Buffer.from((Number(makeid(1)) & 0b10000000).toString()), // ensure left most bit is set (1) - Buffer.from(makeid(1)) - ]); + const salt = crypto.randomBytes(2); + + // eslint-disable-next-line no-bitwise + salt[0] |= 0b10000000; // ensure leftmost bit is set to 1 /* String diff --git a/src/types/EAPMethod.ts b/src/types/EAPMethod.ts index e68173e..8c5d3b2 100644 --- a/src/types/EAPMethod.ts +++ b/src/types/EAPMethod.ts @@ -1,5 +1,4 @@ -import { RadiusPacket } from 'radius'; -import { IPacketHandlerResult } from './PacketHandler'; +import { IPacket, IPacketHandlerResult } from './PacketHandler'; export interface IEAPMethod { getEAPType(): number; @@ -10,6 +9,7 @@ export interface IEAPMethod { identifier: number, stateID: string, msg: Buffer, - orgRadiusPacket?: RadiusPacket + packet?: IPacket, + identity?: string ): Promise; } diff --git a/src/types/PacketHandler.ts b/src/types/PacketHandler.ts index 3bd6f5b..01aa6b0 100644 --- a/src/types/PacketHandler.ts +++ b/src/types/PacketHandler.ts @@ -1,5 +1,3 @@ -import { RadiusPacket } from 'radius'; - export enum PacketResponseCode { AccessChallenge = 'Access-Challenge', AccessAccept = 'Access-Accept', @@ -8,12 +6,19 @@ export enum PacketResponseCode { export interface IPacketHandlerResult { code?: PacketResponseCode; - attributes?: [string, Buffer][]; + attributes?: [string, Buffer | string][]; +} + +export interface IPacketAttributes { + [key: string]: string | Buffer; +} + +export interface IPacket { + attributes: { [key: string]: string | Buffer }; + authenticator?: Buffer; } export interface IPacketHandler { - handlePacket( - attributes: { [key: string]: Buffer }, - orgRadiusPacket: RadiusPacket - ): Promise; + /** handlingType is the attreibute ID of the currently processing type (e.g. TTLS, GTC, MD5,..) */ + handlePacket(packet: IPacket, handlingType?: number): Promise; }