diff --git a/src/auth.ts b/src/auth.ts index aaf260c..9ac6157 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -18,6 +18,7 @@ export class Authentication implements IAuthentication { } const authResult = await this.authenticator.authenticate(username, password); + console.log(`Auth Result for user ${username}`, authResult ? 'SUCCESS' : 'Failure'); this.cache.set(cacheKey, authResult, 86400); // cache for one day return authResult; diff --git a/src/auth/GoogleLDAPAuth.ts b/src/auth/GoogleLDAPAuth.ts index 6989ce9..49803c9 100644 --- a/src/auth/GoogleLDAPAuth.ts +++ b/src/auth/GoogleLDAPAuth.ts @@ -113,7 +113,7 @@ export class GoogleLDAPAuth implements IAuthentication { if (!dnsFetched && !forceFetching) { return this.authenticate(username, password, count, true); } - console.error(`invalid username, not found in DN: ${username}`, this.allValidDNsCache); + console.error(`invalid username, not found in DN: ${username}`); // , this.allValidDNsCache); return false; } diff --git a/src/radius/RadiusService.ts b/src/radius/RadiusService.ts index 605c297..a7b263b 100644 --- a/src/radius/RadiusService.ts +++ b/src/radius/RadiusService.ts @@ -4,11 +4,21 @@ import { EAPPacketHandler } from './handler/EAPPacketHandler'; import { DefaultPacketHandler } from './handler/DefaultPacketHandler'; import { IPacketHandler, 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'; + export class RadiusService { radiusPacketHandlers: IPacketHandler[] = []; constructor(private secret: string, private authentication: IAuthentication) { - this.radiusPacketHandlers.push(new EAPPacketHandler(authentication)); + this.radiusPacketHandlers.push( + new EAPPacketHandler([ + new EAPTTLS(authentication), + new EAPGTC(authentication), + new EAPMD5(authentication) + ]) + ); this.radiusPacketHandlers.push(new DefaultPacketHandler(authentication)); } diff --git a/src/radius/handler/EAPPacketHandler.ts b/src/radius/handler/EAPPacketHandler.ts index c177c63..420f66c 100644 --- a/src/radius/handler/EAPPacketHandler.ts +++ b/src/radius/handler/EAPPacketHandler.ts @@ -3,103 +3,21 @@ import * as NodeCache from 'node-cache'; import { RadiusPacket } from 'radius'; import debug from 'debug'; -import { EAPTTLS } from './eapMethods/EAPTTLS'; import { makeid } from '../../helpers'; -import { - IPacketHandler, - IPacketHandlerResult, - PacketResponseCode -} from '../../types/PacketHandler'; -import { IAuthentication } from '../../types/Authentication'; +import { 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 eapMethods: IEAPMethod[] = []; - // private eapConnectionStates: { [key: string]: { validMethods: IEAPMethod[] } } = {}; private eapConnectionStates = new NodeCache({ useClones: false, stdTTL: 3600 }); // max for one hour - constructor(authentication: IAuthentication) { - this.eapMethods.push(new EAPTTLS(authentication)); - } - - /** - * - * @param data - * @param msgType 1 = identity, 21 = EAP-TTLS, 2 = notification, 4 = md5-challenge, 3 = NAK - */ - private async buildEAPResponse( - identifier: number, - msgType: number, - data?: Buffer - ): Promise { - /** build a package according to this: - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Code | Identifier | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Type-Data ... - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - */ - const buffer = Buffer.from([ - 1, // request - identifier, - 0, // length (1/2) - 0, // length (2/2) - msgType // 1 = identity, 21 = EAP-TTLS, 2 = notificaiton, 4 = md5-challenge, 3 = NAK - ]); - - const resBuffer = data ? Buffer.concat([buffer, data]) : buffer; - // set EAP length header - resBuffer.writeUInt16BE(resBuffer.byteLength, 2); - - return { - code: PacketResponseCode.AccessChallenge, - attributes: [['EAP-Message', buffer]] - }; - } - - private decodeEAPHeader(msg: Buffer) { - /** - * parse msg according to this: - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Code | Identifier | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Type-Data ... - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - */ - - /* - code: - 1 Request - 2 Response - 3 Success - 4 Failure - */ - const code = msg.slice(0, 1).readUInt8(0); - /* identifier is a number */ - const identifier = msg.slice(1, 2).readUInt8(0); - const length = msg.slice(2, 4).readInt16BE(0); - /* EAP type */ - const type = msg.slice(4, 5).readUInt8(0); - const data = msg.slice(5); - - return { - code, - identifier, - length, - type, - data - }; - } + constructor(private eapMethods: IEAPMethod[]) {} async handlePacket( - attributes: { [key: string]: Buffer }, + attributes: { [key: string]: Buffer | string }, orgRadiusPacket: RadiusPacket ): Promise { if (!attributes['EAP-Message']) { @@ -116,9 +34,9 @@ export class EAPPacketHandler implements IPacketHandler { } // EAP MESSAGE - const msg = attributes['EAP-Message']; + const msg = attributes['EAP-Message'] as Buffer; - const { code, type, identifier, data } = this.decodeEAPHeader(msg); + const { code, type, identifier, data } = decodeEAPHeader(msg); const currentState = this.eapConnectionStates.get(stateID) as { validMethods: IEAPMethod[] }; @@ -130,10 +48,10 @@ export class EAPPacketHandler implements IPacketHandler { log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', {}); // start identify if (currentState.validMethods.length > 0) { - return currentState.validMethods[0].identify(identifier, stateID); + return currentState.validMethods[0].identify(identifier, stateID, data); } - return this.buildEAPResponse(identifier, 3); + return buildEAPResponse(identifier, 3); // NAK case 2: // notification log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {}); console.info('notification'); @@ -180,7 +98,7 @@ export class EAPPacketHandler implements IPacketHandler { console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`); - return this.buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods)); + return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods)); } } break; diff --git a/src/radius/handler/eap/EAPHelper.ts b/src/radius/handler/eap/EAPHelper.ts new file mode 100644 index 0000000..1c3b9e8 --- /dev/null +++ b/src/radius/handler/eap/EAPHelper.ts @@ -0,0 +1,79 @@ +import { IPacketHandlerResult, PacketResponseCode } from '../../../types/PacketHandler'; + +export function buildEAP(identifier: number, msgType: number, data?: Buffer) { + /** build a package according to this: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Code | Identifier | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Type-Data ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + */ + const buffer = Buffer.from([ + 1, // request + identifier, + 0, // length (1/2) + 0, // length (2/2) + msgType // 1 = identity, 21 = EAP-TTLS, 2 = notificaiton, 4 = md5-challenge, 3 = NAK + ]); + + const resBuffer = data ? Buffer.concat([buffer, data]) : buffer; + + // set EAP length header + resBuffer.writeUInt16BE(resBuffer.byteLength, 2); + + return resBuffer; +} + +/** + * + * @param data + * @param msgType 1 = identity, 21 = EAP-TTLS, 2 = notification, 4 = md5-challenge, 3 = NAK + */ +export function buildEAPResponse( + identifier: number, + msgType: number, + data?: Buffer +): IPacketHandlerResult { + return { + code: PacketResponseCode.AccessChallenge, + attributes: [['EAP-Message', buildEAP(identifier, msgType, data)]] + }; +} + +export function decodeEAPHeader(msg: Buffer) { + /** + * parse msg according to this: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Code | Identifier | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Type-Data ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + */ + + /* + code: + 1 Request + 2 Response + 3 Success + 4 Failure + */ + const code = msg.slice(0, 1).readUInt8(0); + /* identifier is a number */ + const identifier = msg.slice(1, 2).readUInt8(0); + const length = msg.slice(2, 4).readUInt16BE(0); + /* EAP type */ + const type = msg.slice(4, 5).readUInt8(0); + const data = msg.slice(5); + + return { + code, + identifier, + length, + type, + data + }; +} diff --git a/src/radius/handler/eap/eapMethods/EAP-GTC.ts b/src/radius/handler/eap/eapMethods/EAP-GTC.ts new file mode 100644 index 0000000..6664f5c --- /dev/null +++ b/src/radius/handler/eap/eapMethods/EAP-GTC.ts @@ -0,0 +1,65 @@ +// 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'; +import { IAuthentication } from '../../../../types/Authentication'; +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'); + } + + return buildEAPResponse(identifier, 6, Buffer.from('Password: ')); + } + + constructor(private authentication: IAuthentication) {} + + async handleMessage( + _identifier: number, + stateID: string, + msg: Buffer + ): Promise { + const username = 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. + + if (!username) { + throw new Error('no username'); + } + + log('username', username, username.toString()); + log('token', token, token.toString()); + + const success = await this.authentication.authenticate(username.toString(), token.toString()); + + return { + code: success ? PacketResponseCode.AccessAccept : PacketResponseCode.AccessReject + }; + } +} diff --git a/src/radius/handler/eap/eapMethods/EAP-MD5.ts b/src/radius/handler/eap/eapMethods/EAP-MD5.ts new file mode 100644 index 0000000..8c2552e --- /dev/null +++ b/src/radius/handler/eap/eapMethods/EAP-MD5.ts @@ -0,0 +1,40 @@ +// 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 { RadiusPacket } from 'radius'; +import debug from 'debug'; +import { ResponseAuthHandler } from '../../../../types/Handler'; +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; +} + +export class EAPMD5 implements IEAPMethod { + getEAPType(): number { + return 4; + } + + identify(_identifier: number, _stateID: string): IPacketHandlerResult { + // NOT IMPLEMENTED + return {}; + } + + constructor(private authentication: IAuthentication) {} + + async handleMessage( + _identifier: number, + _stateID: string, + _msg: Buffer, + _orgRadiusPacket: RadiusPacket + ): Promise { + // not implemented + + return {}; + } +} diff --git a/src/radius/handler/eapMethods/EAPTTLS.ts b/src/radius/handler/eap/eapMethods/EAP-TTLS.ts similarity index 58% rename from src/radius/handler/eapMethods/EAPTTLS.ts rename to src/radius/handler/eap/eapMethods/EAP-TTLS.ts index 180b443..ab9db5b 100644 --- a/src/radius/handler/eapMethods/EAPTTLS.ts +++ b/src/radius/handler/eap/eapMethods/EAP-TTLS.ts @@ -1,19 +1,20 @@ // 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 tls from 'tls'; import * as NodeCache from 'node-cache'; import { RadiusPacket } from 'radius'; import debug from 'debug'; -import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../tls/crypt'; -import { ResponseAuthHandler } from '../../../types/Handler'; +import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt'; +import { ResponseAuthHandler } from '../../../../types/Handler'; import { PAPChallenge } from './challenges/PAPChallenge'; -import { 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 { 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'); @@ -38,6 +39,9 @@ export class EAPTTLS implements IEAPMethod { 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; } @@ -48,23 +52,23 @@ export class EAPTTLS implements IEAPMethod { constructor(private authentication: IAuthentication) {} - private buildEAPTTLSResponse( + private buildEAPTTLS( identifier: number, msgType = 21, msgFlags = 0x00, stateID: string, data?: Buffer, - newResponse = true - ): IPacketHandlerResult { - const maxSize = (MAX_RADIUS_ATTRIBUTE_SIZE - 5) * 4; + newResponse = true, + maxSize = (MAX_RADIUS_ATTRIBUTE_SIZE - 5) * 4 + ): Buffer { log('maxSize', maxSize); /* it's the first one and we have more, therefore include length */ - const includeLength = data && newResponse && data.length > maxSize; + const includeLength = maxSize > 0 && data && newResponse && data.length > maxSize; // extract data party - const dataToSend = data && data.length > 0 && data.slice(0, maxSize); - const dataToQueue = data && data.length > maxSize && data.slice(maxSize); + const dataToSend = maxSize > 0 ? data && data.length > 0 && data.slice(0, maxSize) : data; + const dataToQueue = maxSize > 0 && data && data.length > maxSize && data.slice(maxSize); /* 0 1 2 3 4 5 6 7 8 @@ -129,6 +133,19 @@ export class EAPTTLS implements IEAPMethod { this.queueData.del(stateID); } + return resBuffer; + } + + private buildEAPTTLSResponse( + identifier: number, + msgType = 21, + msgFlags = 0x00, + stateID: string, + data?: Buffer, + newResponse = true + ): IPacketHandlerResult { + const resBuffer = this.buildEAPTTLS(identifier, msgType, msgFlags, stateID, data, newResponse); + const attributes: any = [['State', Buffer.from(stateID)]]; let sentDataSize = 0; do { @@ -162,7 +179,7 @@ export class EAPTTLS implements IEAPMethod { Message Length | Data... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ - + 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 @@ -191,10 +208,19 @@ export class EAPTTLS implements IEAPMethod { let msglength; if (decodedFlags.lengthIncluded) { - msglength = msg.slice(6, 10).readInt32BE(0); // .readDoubleLE(0); // .toString('hex'); + msglength = msg.slice(6, 10).readUInt32BE(0); // .readDoubleLE(0); // .toString('hex'); } const data = msg.slice(decodedFlags.lengthIncluded ? 10 : 6, msg.length); + log('>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS', { + flags: `00000000${flags.toString(2)}`.substr(-8), + decodedFlags, + identifier, + msglength, + data + // dataStr: data.toString() + }); + return { decodedFlags, msglength, @@ -255,13 +281,10 @@ export class EAPTTLS implements IEAPMethod { msg: Buffer, orgRadiusPacket: RadiusPacket ): Promise { - const { decodedFlags, msglength, data } = this.decodeTTLSMessage(msg); + const { data } = this.decodeTTLSMessage(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) { - log( - `>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS, ACK / NACK (no data, just a confirmation, ID: ${identifier})` - ); const queuedData = this.queueData.get(stateID); if (queuedData instanceof Buffer && queuedData.length > 0) { return this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, queuedData, false); @@ -270,15 +293,6 @@ export class EAPTTLS implements IEAPMethod { return {}; } - log('>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS', { - // flags: `00000000${flags.toString(2)}`.substr(-8), - decodedFlags, - identifier, - msglength - // data, - // dataStr: data.toString() - }); - let connection = this.openTLSSockets.get(stateID) as ITLSServer; if (!connection) { @@ -295,11 +309,18 @@ export class EAPTTLS implements IEAPMethod { const sendResponsePromise = newDeferredPromise(); const incomingMessageHandler = async (incomingData: Buffer) => { - const type = incomingData.slice(3, 4).readUInt8(0); + const ret: any = {}; + ret.attributes = {}; + ret.raw_attributes = []; + + const { type, data: AVPdata, length: AVPlength } = this.decodeAVP(incomingData); + + console.log('AVP data', { AVPdata, AVPlength, AVPdataStr: AVPdata.toString() }); + // const code = data.slice(4, 5).readUInt8(0); switch (type) { - case 1: // PAP / CHAP + case 1: // PAP try { const { username, password } = this.papChallenge.decode(incomingData); const authResult = await this.authentication.authenticate(username, password); @@ -315,17 +336,51 @@ export class EAPTTLS implements IEAPMethod { sendResponsePromise.resolve(this.buildEAPTTLSResponse(identifier, 3, 0, stateID)); } break; - default: + case 79: { + const result = await this.tunnelEAP.handlePacket( + { + State: `${stateID}-inner`, + 'EAP-Message': AVPdata + }, + orgRadiusPacket + ); + + 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, + orgRadiusPacket + ) + ); + 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(79, eapMessage[1])); + break; + } + default: { log('data', incomingData); log('data str', incomingData.toString()); - // currentConnection!.events.emit('end'); + log('UNSUPPORTED AUTH TYPE, requesting identify again (we need PAP!)', type); - log('UNSUPPORTED AUTH TYPE, requesting PAP'); - // throw new Error(`unsupported auth type${type}`); - sendResponsePromise.resolve( - this.buildEAPTTLSResponse(identifier, 3, 0, stateID, Buffer.from([1])) + connection.events.emit( + 'encrypt', + this.buildAVP(79, this.buildEAPTTLS(identifier, 3, 0, stateID, Buffer.from([1]))) ); + } } }; @@ -341,7 +396,7 @@ export class EAPTTLS implements IEAPMethod { connection.events.on('response', responseHandler); // emit data to tls server - connection.events.emit('send', data); + connection.events.emit('decrypt', data); const responseData = await sendResponsePromise.promise; // cleanup @@ -351,4 +406,114 @@ export class EAPTTLS implements IEAPMethod { // send response return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData); } + + private decodeAVP(buffer: Buffer) { + /** + * 4.1. AVP Header + + The fields in the AVP header MUST be sent in network byte order. The + format of the header is: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | AVP Code | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V M P r r r r r| AVP Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Vendor-ID (opt) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 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 length = buffer.slice(5, 8).readUInt16BE(0); // actually a Int24BE + const length = buffer.slice(6, 8).readUInt16BE(0); // actually a Int24BE + + 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); + } + + return { + type, + flags: `00000000${flags.toString(2)}`.substr(-8), + decodedFlags, + length, + vendorId, + data + }; + } + + private buildAVP( + code: number, + data: Buffer, + flags: { VendorSpecific?: boolean; Mandatory?: boolean } = { Mandatory: true } + ) { + /** + * 4.1. AVP Header + + The fields in the AVP header MUST be sent in network byte order. The + format of the header is: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | AVP Code | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V M r r r r r r| AVP Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Vendor-ID (opt) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data ... + +-+-+-+-+-+-+-+-+ + */ + let b = Buffer.alloc(8); + + b.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 + present and the AVP Code is interpreted according to the namespace + defined by the vendor indicated in the Vendor-ID field. + + The 'M' (Mandatory) bit indicates whether support of the AVP is + required. If this bit is set to 0, this indicates that the AVP + may be safely ignored if the receiving party does not understand + or support it. If set to 1, this indicates that the receiving + party MUST fail the negotiation if it does not understand the AVP; + for a TTLS server, this would imply returning EAP-Failure, for a + client, this would imply abandoning the negotiation. + */ + let flagValue = 0; + if (flags.VendorSpecific) { + flagValue += 0b10000000; + } + if (flags.Mandatory) { + flagValue += 0b01000000; + } + + console.log('flagValue', flagValue, `00000000${flagValue.toString(2)}`.substr(-8)); + + b.writeInt8(flagValue, 4); // flags (set V..) + + b = Buffer.concat([b, 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) + + return b; + } } diff --git a/src/radius/handler/eap/eapMethods/EAP.ts b/src/radius/handler/eap/eapMethods/EAP.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/radius/handler/eapMethods/challenges/PAPChallenge.ts b/src/radius/handler/eap/eapMethods/challenges/PAPChallenge.ts similarity index 95% rename from src/radius/handler/eapMethods/challenges/PAPChallenge.ts rename to src/radius/handler/eap/eapMethods/challenges/PAPChallenge.ts index 0c7b672..a9e9012 100644 --- a/src/radius/handler/eapMethods/challenges/PAPChallenge.ts +++ b/src/radius/handler/eap/eapMethods/challenges/PAPChallenge.ts @@ -1,5 +1,5 @@ import debug from 'debug'; -import { IEAPChallenge } from '../../../../types/EAPChallenge'; +import { IEAPChallenge } from '../../../../../types/EAPChallenge'; const log = debug('radius:eap:papchallenge'); diff --git a/src/tls/crypt.ts b/src/tls/crypt.ts index 25c1894..043beb5 100644 --- a/src/tls/crypt.ts +++ b/src/tls/crypt.ts @@ -42,11 +42,16 @@ export function startTLSServer(): ITLSServer { }); const encrypted = duplexpair.socket2; - emitter.on('send', (data: Buffer) => { + emitter.on('decrypt', (data: Buffer) => { encrypted.write(data); // encrypted.sync(); }); + emitter.on('encrypt', (data: Buffer) => { + cleartext.write(data); + // encrypted.sync(); + }); + encrypted.on('data', (data: Buffer) => { // log('encrypted data', data, data.toString()); emitter.emit('response', data); diff --git a/src/types/EAPChallenge.ts b/src/types/EAPChallenge.ts index d2c4ddb..d99dd4f 100644 --- a/src/types/EAPChallenge.ts +++ b/src/types/EAPChallenge.ts @@ -1,3 +1,3 @@ export interface IEAPChallenge { - decode(data: Buffer): { username: string; password: string }; + decode(data: Buffer, stateID: string): { username: string; password?: string }; } diff --git a/src/types/EAPMethod.ts b/src/types/EAPMethod.ts index 97113f5..e68173e 100644 --- a/src/types/EAPMethod.ts +++ b/src/types/EAPMethod.ts @@ -4,7 +4,7 @@ import { IPacketHandlerResult } from './PacketHandler'; export interface IEAPMethod { getEAPType(): number; - identify(identifier: number, stateID: string): IPacketHandlerResult; + identify(identifier: number, stateID: string, msg?: Buffer): IPacketHandlerResult; handleMessage( identifier: number,