fix(eap): catch decoding errors

This commit is contained in:
simon 2020-06-25 11:17:19 +02:00
parent 6fc7301c60
commit 97ea3fad1d
3 changed files with 203 additions and 184 deletions

View File

@ -3,7 +3,7 @@
import * as NodeCache from 'node-cache'; import * as NodeCache from 'node-cache';
import debug from 'debug'; import debug from 'debug';
import { makeid } from '../../helpers'; 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 { IEAPMethod } from '../../types/EAPMethod';
import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper'; import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper';
@ -34,99 +34,104 @@ export class EAPPacketHandler implements IPacketHandler {
// EAP MESSAGE // EAP MESSAGE
const msg = packet.attributes['EAP-Message'] as Buffer; 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) { switch (code) {
case 1: // for request case 1: // for request
case 2: // for response case 2: // for response
switch (type) { switch (type) {
case 1: // identifiy case 1: // identifiy
log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', stateID, data.toString()); log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', stateID, data.toString());
if (data) { if (data) {
this.identities.set(stateID, data); // use token til binary 0.); this.identities.set(stateID, data); // use token til binary 0.);
} else { } else {
log('no msg'); 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);
} }
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 // start identify
if (currentState.validMethods.length > 0) { if (currentState.validMethods.length > 0) {
return currentState.validMethods[0].identify(identifier, stateID, data); 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 buildEAPResponse(identifier, 3); // NAK
return eapMethod.handleMessage( case 2: // notification
identifier, log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
stateID, console.info('notification');
msg, break;
packet, case 4: // md5-challenge
this.identities.get(stateID) 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;
break; case 3:
case 3: log('Client Auth Success');
log('Client Auth Success'); break;
break; case 4:
case 4: log('Client Auth FAILURE');
log('Client Auth FAILURE'); break;
break; default:
default: }
// silently ignore;
return {};
} catch (err) {
console.error('decoding of (generic) EAP package failed', msg, err);
return {};
} }
// silently ignore;
return {};
} }
} }

View File

@ -37,22 +37,29 @@ export class EAPGTC implements IEAPMethod {
): Promise<IPacketHandlerResult> { ): Promise<IPacketHandlerResult> {
const username = identity; // this.loginData.get(stateID) as Buffer | undefined; 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) { if (!username) {
throw new Error('no 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,
};
} }
} }

View File

@ -300,113 +300,120 @@ export class EAPTTLS implements IEAPMethod {
return {}; return {};
} }
this.lastProcessedIdentifier.set(stateID, identifier); 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 // 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) { if (!data || data.length === 0) {
const queuedData = this.queueData.get(stateID); const queuedData = this.queueData.get(stateID);
if (queuedData instanceof Buffer && queuedData.length > 0) { if (queuedData instanceof Buffer && queuedData.length > 0) {
log(`returning queued data for ${stateID}`); log(`returning queued data for ${stateID}`);
return this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, queuedData, false); return this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, queuedData, false);
}
log(`empty data queue for ${stateID}`);
return {};
} }
log(`empty data queue for ${stateID}`); let connection = this.openTLSSockets.get(stateID) as ITLSServer;
return {};
}
let connection = this.openTLSSockets.get(stateID) as ITLSServer; if (!connection) {
connection = startTLSServer();
this.openTLSSockets.set(stateID, connection);
if (!connection) { connection.events.on('end', () => {
connection = startTLSServer(); // cleanup socket
this.openTLSSockets.set(stateID, connection); log('ENDING SOCKET');
this.openTLSSockets.del(stateID);
});
}
connection.events.on('end', () => { const sendResponsePromise = newDeferredPromise();
// cleanup socket
log('ENDING SOCKET');
this.openTLSSockets.del(stateID);
});
}
const sendResponsePromise = newDeferredPromise(); const incomingMessageHandler = async (incomingData: Buffer) => {
const ret: any = {};
ret.attributes = {};
ret.raw_attributes = [];
const incomingMessageHandler = async (incomingData: Buffer) => { const AVPs = this.decodeAVPs(incomingData);
const ret: any = {};
ret.attributes = {};
ret.raw_attributes = [];
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 attributes.State = `${stateID}-inner`;
const attributes: IPacketAttributes = {};
AVPs.forEach((avp) => {
attributes[attr_id_to_name(avp.type)] = avp.data;
});
attributes.State = `${stateID}-inner`; // handle incoming package via inner tunnel
const result = await this.innerTunnel.handlePacket(
// handle incoming package via inner tunnel {
const result = await this.innerTunnel.handlePacket( attributes,
{ },
attributes, this.getEAPType()
},
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'); log('inner tunnel result', result);
if (!eapMessage) {
throw new Error('no eap message found');
}
connection.events.emit( if (
'encrypt', result.code === PacketResponseCode.AccessReject ||
this.buildAVP(attr_name_to_id('EAP-Message'), eapMessage[1] as Buffer) 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) => { const eapMessage = result.attributes?.find((attr) => attr[0] === 'EAP-Message');
// send back... if (!eapMessage) {
sendResponsePromise.resolve( throw new Error('no eap message found');
this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData) }
);
};
// register event listeners connection.events.emit(
connection.events.on('incoming', incomingMessageHandler); 'encrypt',
connection.events.on('response', responseHandler); this.buildAVP(attr_name_to_id('EAP-Message'), eapMessage[1] as Buffer)
);
};
// emit data to tls server const responseHandler = (encryptedResponseData: Buffer) => {
connection.events.emit('decrypt', data); // send back...
const responseData = await sendResponsePromise.promise; sendResponsePromise.resolve(
this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData)
);
};
// cleanup // register event listeners
connection.events.off('incoming', incomingMessageHandler); connection.events.on('incoming', incomingMessageHandler);
connection.events.off('response', responseHandler); connection.events.on('response', responseHandler);
// send response // emit data to tls server
return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData); 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) { private transformAttributesArrayToMap(attributes: [string, Buffer | string][] | undefined) {