fix(eap): catch decoding errors
This commit is contained in:
		
							parent
							
								
									6fc7301c60
								
							
						
					
					
						commit
						97ea3fad1d
					
				@ -3,7 +3,7 @@
 | 
			
		||||
import * as NodeCache from 'node-cache';
 | 
			
		||||
import debug from 'debug';
 | 
			
		||||
import { makeid } from '../../helpers';
 | 
			
		||||
import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler';
 | 
			
		||||
import { IPacket, IPacketHandler, IPacketHandlerResult, PacketResponseCode } from '../../types/PacketHandler';
 | 
			
		||||
import { IEAPMethod } from '../../types/EAPMethod';
 | 
			
		||||
import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper';
 | 
			
		||||
 | 
			
		||||
@ -34,99 +34,104 @@ export class EAPPacketHandler implements IPacketHandler {
 | 
			
		||||
		// EAP MESSAGE
 | 
			
		||||
		const msg = packet.attributes['EAP-Message'] as Buffer;
 | 
			
		||||
 | 
			
		||||
		const { code, type, identifier, data } = decodeEAPHeader(msg);
 | 
			
		||||
		try {
 | 
			
		||||
			const { code, type, identifier, data } = decodeEAPHeader(msg);
 | 
			
		||||
 | 
			
		||||
		const currentState = this.eapConnectionStates.get(stateID) as { validMethods: IEAPMethod[] };
 | 
			
		||||
			const currentState = this.eapConnectionStates.get(stateID) as { validMethods: IEAPMethod[] };
 | 
			
		||||
 | 
			
		||||
		switch (code) {
 | 
			
		||||
			case 1: // for request
 | 
			
		||||
			case 2: // for response
 | 
			
		||||
				switch (type) {
 | 
			
		||||
					case 1: // identifiy
 | 
			
		||||
						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);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						return buildEAPResponse(identifier, 3); // NAK
 | 
			
		||||
					case 2: // notification
 | 
			
		||||
						log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
 | 
			
		||||
						console.info('notification');
 | 
			
		||||
						break;
 | 
			
		||||
					case 4: // md5-challenge
 | 
			
		||||
						log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {});
 | 
			
		||||
 | 
			
		||||
						console.info('md5-challenge');
 | 
			
		||||
						break;
 | 
			
		||||
					case 254: // expanded type
 | 
			
		||||
						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
 | 
			
		||||
							const supportedEAPMethods: number[] = [];
 | 
			
		||||
							for (const supportedMethod of data) {
 | 
			
		||||
								supportedEAPMethods.push(supportedMethod);
 | 
			
		||||
			switch (code) {
 | 
			
		||||
				case 1: // for request
 | 
			
		||||
				case 2: // for response
 | 
			
		||||
					switch (type) {
 | 
			
		||||
						case 1: // identifiy
 | 
			
		||||
							log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', stateID, data.toString());
 | 
			
		||||
							if (data) {
 | 
			
		||||
								this.identities.set(stateID, data); // use token til binary 0.);
 | 
			
		||||
							} else {
 | 
			
		||||
								log('no msg');
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							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 = this.eapMethods.find((method) => {
 | 
			
		||||
							return type === method.getEAPType();
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						if (eapMethod) {
 | 
			
		||||
							return eapMethod.handleMessage(
 | 
			
		||||
								identifier,
 | 
			
		||||
								stateID,
 | 
			
		||||
								msg,
 | 
			
		||||
								packet,
 | 
			
		||||
								this.identities.get(stateID)
 | 
			
		||||
							return buildEAPResponse(identifier, 3); // NAK
 | 
			
		||||
						case 2: // notification
 | 
			
		||||
							log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
 | 
			
		||||
							console.info('notification');
 | 
			
		||||
							break;
 | 
			
		||||
						case 4: // md5-challenge
 | 
			
		||||
							log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {});
 | 
			
		||||
 | 
			
		||||
							console.info('md5-challenge');
 | 
			
		||||
							break;
 | 
			
		||||
						case 254: // expanded type
 | 
			
		||||
							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
 | 
			
		||||
								const supportedEAPMethods: number[] = [];
 | 
			
		||||
								for (const supportedMethod of data) {
 | 
			
		||||
									supportedEAPMethods.push(supportedMethod);
 | 
			
		||||
								}
 | 
			
		||||
 | 
			
		||||
								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 = this.eapMethods.find((method) => {
 | 
			
		||||
								return type === method.getEAPType();
 | 
			
		||||
							});
 | 
			
		||||
 | 
			
		||||
							if (eapMethod) {
 | 
			
		||||
								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()
 | 
			
		||||
							);
 | 
			
		||||
 | 
			
		||||
							console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`);
 | 
			
		||||
 | 
			
		||||
							return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods));
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						// we do not support this auth type, ask for something we support
 | 
			
		||||
						const serverSupportedMethods = currentState.validMethods.map((method) =>
 | 
			
		||||
							method.getEAPType()
 | 
			
		||||
						);
 | 
			
		||||
 | 
			
		||||
						console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`);
 | 
			
		||||
 | 
			
		||||
						return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods));
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case 3:
 | 
			
		||||
				log('Client Auth Success');
 | 
			
		||||
				break;
 | 
			
		||||
			case 4:
 | 
			
		||||
				log('Client Auth FAILURE');
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
					break;
 | 
			
		||||
				case 3:
 | 
			
		||||
					log('Client Auth Success');
 | 
			
		||||
					break;
 | 
			
		||||
				case 4:
 | 
			
		||||
					log('Client Auth FAILURE');
 | 
			
		||||
					break;
 | 
			
		||||
				default:
 | 
			
		||||
			}
 | 
			
		||||
			// silently ignore;
 | 
			
		||||
			return {};
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			console.error('decoding of (generic) EAP package failed', msg, err);
 | 
			
		||||
			return {};
 | 
			
		||||
		}
 | 
			
		||||
		// silently ignore;
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -37,22 +37,29 @@ export class EAPGTC implements IEAPMethod {
 | 
			
		||||
	): Promise<IPacketHandlerResult> {
 | 
			
		||||
		const username = identity; // this.loginData.get(stateID) as Buffer | undefined;
 | 
			
		||||
 | 
			
		||||
		const { data } = decodeEAPHeader(msg);
 | 
			
		||||
		try {
 | 
			
		||||
			const { data } = decodeEAPHeader(msg);
 | 
			
		||||
 | 
			
		||||
		const token = this.extractValue(data);
 | 
			
		||||
			const token = this.extractValue(data);
 | 
			
		||||
 | 
			
		||||
		if (!username) {
 | 
			
		||||
			throw new Error('no username');
 | 
			
		||||
			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,
 | 
			
		||||
				attributes: (success && [['User-Name', username]]) || undefined,
 | 
			
		||||
			};
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			console.error('decoding of EAP-GTC package failed', msg, err);
 | 
			
		||||
			return {
 | 
			
		||||
				code: PacketResponseCode.AccessReject,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
			attributes: (success && [['User-Name', username]]) || undefined,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -300,113 +300,120 @@ export class EAPTTLS implements IEAPMethod {
 | 
			
		||||
			return {};
 | 
			
		||||
		}
 | 
			
		||||
		this.lastProcessedIdentifier.set(stateID, identifier);
 | 
			
		||||
		const { data } = this.decodeTTLSMessage(msg);
 | 
			
		||||
		try {
 | 
			
		||||
			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) {
 | 
			
		||||
			const queuedData = this.queueData.get(stateID);
 | 
			
		||||
			if (queuedData instanceof Buffer && queuedData.length > 0) {
 | 
			
		||||
				log(`returning queued data for ${stateID}`);
 | 
			
		||||
				return this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, queuedData, false);
 | 
			
		||||
			// 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) {
 | 
			
		||||
				const queuedData = this.queueData.get(stateID);
 | 
			
		||||
				if (queuedData instanceof Buffer && queuedData.length > 0) {
 | 
			
		||||
					log(`returning queued data for ${stateID}`);
 | 
			
		||||
					return this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, queuedData, false);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				log(`empty data queue for ${stateID}`);
 | 
			
		||||
				return {};
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			log(`empty data queue for ${stateID}`);
 | 
			
		||||
			return {};
 | 
			
		||||
		}
 | 
			
		||||
			let connection = this.openTLSSockets.get(stateID) as ITLSServer;
 | 
			
		||||
 | 
			
		||||
		let connection = this.openTLSSockets.get(stateID) as ITLSServer;
 | 
			
		||||
			if (!connection) {
 | 
			
		||||
				connection = startTLSServer();
 | 
			
		||||
				this.openTLSSockets.set(stateID, connection);
 | 
			
		||||
 | 
			
		||||
		if (!connection) {
 | 
			
		||||
			connection = startTLSServer();
 | 
			
		||||
			this.openTLSSockets.set(stateID, connection);
 | 
			
		||||
				connection.events.on('end', () => {
 | 
			
		||||
					// cleanup socket
 | 
			
		||||
					log('ENDING SOCKET');
 | 
			
		||||
					this.openTLSSockets.del(stateID);
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			connection.events.on('end', () => {
 | 
			
		||||
				// cleanup socket
 | 
			
		||||
				log('ENDING SOCKET');
 | 
			
		||||
				this.openTLSSockets.del(stateID);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
			const sendResponsePromise = newDeferredPromise();
 | 
			
		||||
 | 
			
		||||
		const sendResponsePromise = newDeferredPromise();
 | 
			
		||||
			const incomingMessageHandler = async (incomingData: Buffer) => {
 | 
			
		||||
				const ret: any = {};
 | 
			
		||||
				ret.attributes = {};
 | 
			
		||||
				ret.raw_attributes = [];
 | 
			
		||||
 | 
			
		||||
		const incomingMessageHandler = async (incomingData: Buffer) => {
 | 
			
		||||
			const ret: any = {};
 | 
			
		||||
			ret.attributes = {};
 | 
			
		||||
			ret.raw_attributes = [];
 | 
			
		||||
				const AVPs = this.decodeAVPs(incomingData);
 | 
			
		||||
 | 
			
		||||
			const AVPs = this.decodeAVPs(incomingData);
 | 
			
		||||
				// build attributes for packet handler
 | 
			
		||||
				const attributes: IPacketAttributes = {};
 | 
			
		||||
				AVPs.forEach((avp) => {
 | 
			
		||||
					attributes[attr_id_to_name(avp.type)] = avp.data;
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
			// build attributes for packet handler
 | 
			
		||||
			const attributes: IPacketAttributes = {};
 | 
			
		||||
			AVPs.forEach((avp) => {
 | 
			
		||||
				attributes[attr_id_to_name(avp.type)] = avp.data;
 | 
			
		||||
			});
 | 
			
		||||
				attributes.State = `${stateID}-inner`;
 | 
			
		||||
 | 
			
		||||
			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),
 | 
			
		||||
							},
 | 
			
		||||
						}
 | 
			
		||||
					)
 | 
			
		||||
				// handle incoming package via inner tunnel
 | 
			
		||||
				const result = await this.innerTunnel.handlePacket(
 | 
			
		||||
					{
 | 
			
		||||
						attributes,
 | 
			
		||||
					},
 | 
			
		||||
					this.getEAPType()
 | 
			
		||||
				);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const eapMessage = result.attributes?.find((attr) => attr[0] === 'EAP-Message');
 | 
			
		||||
			if (!eapMessage) {
 | 
			
		||||
				throw new Error('no eap message found');
 | 
			
		||||
			}
 | 
			
		||||
				log('inner tunnel result', result);
 | 
			
		||||
 | 
			
		||||
			connection.events.emit(
 | 
			
		||||
				'encrypt',
 | 
			
		||||
				this.buildAVP(attr_name_to_id('EAP-Message'), eapMessage[1] as Buffer)
 | 
			
		||||
			);
 | 
			
		||||
		};
 | 
			
		||||
				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 responseHandler = (encryptedResponseData: Buffer) => {
 | 
			
		||||
			// send back...
 | 
			
		||||
			sendResponsePromise.resolve(
 | 
			
		||||
				this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData)
 | 
			
		||||
			);
 | 
			
		||||
		};
 | 
			
		||||
				const eapMessage = result.attributes?.find((attr) => attr[0] === 'EAP-Message');
 | 
			
		||||
				if (!eapMessage) {
 | 
			
		||||
					throw new Error('no eap message found');
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
		// register event listeners
 | 
			
		||||
		connection.events.on('incoming', incomingMessageHandler);
 | 
			
		||||
		connection.events.on('response', responseHandler);
 | 
			
		||||
				connection.events.emit(
 | 
			
		||||
					'encrypt',
 | 
			
		||||
					this.buildAVP(attr_name_to_id('EAP-Message'), eapMessage[1] as Buffer)
 | 
			
		||||
				);
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
		// emit data to tls server
 | 
			
		||||
		connection.events.emit('decrypt', data);
 | 
			
		||||
		const responseData = await sendResponsePromise.promise;
 | 
			
		||||
			const responseHandler = (encryptedResponseData: Buffer) => {
 | 
			
		||||
				// send back...
 | 
			
		||||
				sendResponsePromise.resolve(
 | 
			
		||||
					this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData)
 | 
			
		||||
				);
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
		// cleanup
 | 
			
		||||
		connection.events.off('incoming', incomingMessageHandler);
 | 
			
		||||
		connection.events.off('response', responseHandler);
 | 
			
		||||
			// register event listeners
 | 
			
		||||
			connection.events.on('incoming', incomingMessageHandler);
 | 
			
		||||
			connection.events.on('response', responseHandler);
 | 
			
		||||
 | 
			
		||||
		// send response
 | 
			
		||||
		return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData);
 | 
			
		||||
			// emit data to tls server
 | 
			
		||||
			connection.events.emit('decrypt', data);
 | 
			
		||||
			const responseData = await sendResponsePromise.promise;
 | 
			
		||||
 | 
			
		||||
			// cleanup
 | 
			
		||||
			connection.events.off('incoming', incomingMessageHandler);
 | 
			
		||||
			connection.events.off('response', responseHandler);
 | 
			
		||||
 | 
			
		||||
			// send response
 | 
			
		||||
			return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData);
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			console.error('decoding of EAP-TTLS package failed', msg, err);
 | 
			
		||||
			return {
 | 
			
		||||
				code: PacketResponseCode.AccessReject,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private transformAttributesArrayToMap(attributes: [string, Buffer | string][] | undefined) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user