feat: inner tunnel for TTSL support added

This commit is contained in:
simon 2020-02-24 18:52:21 +01:00
parent 191bb54264
commit 6aa4b9f92e
13 changed files with 419 additions and 136 deletions

View File

@ -18,6 +18,7 @@ export class Authentication implements IAuthentication {
} }
const authResult = await this.authenticator.authenticate(username, password); 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 this.cache.set(cacheKey, authResult, 86400); // cache for one day
return authResult; return authResult;

View File

@ -113,7 +113,7 @@ export class GoogleLDAPAuth implements IAuthentication {
if (!dnsFetched && !forceFetching) { if (!dnsFetched && !forceFetching) {
return this.authenticate(username, password, count, true); 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; return false;
} }

View File

@ -4,11 +4,21 @@ import { EAPPacketHandler } from './handler/EAPPacketHandler';
import { DefaultPacketHandler } from './handler/DefaultPacketHandler'; import { DefaultPacketHandler } from './handler/DefaultPacketHandler';
import { IPacketHandler, IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler'; 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 { export class RadiusService {
radiusPacketHandlers: IPacketHandler[] = []; radiusPacketHandlers: IPacketHandler[] = [];
constructor(private secret: string, private authentication: IAuthentication) { 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)); this.radiusPacketHandlers.push(new DefaultPacketHandler(authentication));
} }

View File

@ -3,103 +3,21 @@
import * as NodeCache from 'node-cache'; import * as NodeCache from 'node-cache';
import { RadiusPacket } from 'radius'; import { RadiusPacket } from 'radius';
import debug from 'debug'; import debug from 'debug';
import { EAPTTLS } from './eapMethods/EAPTTLS';
import { makeid } from '../../helpers'; import { makeid } from '../../helpers';
import { import { IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler';
IPacketHandler,
IPacketHandlerResult,
PacketResponseCode
} from '../../types/PacketHandler';
import { IAuthentication } from '../../types/Authentication';
import { IEAPMethod } from '../../types/EAPMethod'; import { IEAPMethod } from '../../types/EAPMethod';
import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper';
const log = debug('radius:eap'); const log = debug('radius:eap');
export class EAPPacketHandler implements IPacketHandler { export class EAPPacketHandler implements IPacketHandler {
private eapMethods: IEAPMethod[] = [];
// private eapConnectionStates: { [key: string]: { validMethods: IEAPMethod[] } } = {}; // private eapConnectionStates: { [key: string]: { validMethods: IEAPMethod[] } } = {};
private eapConnectionStates = new NodeCache({ useClones: false, stdTTL: 3600 }); // max for one hour private eapConnectionStates = new NodeCache({ useClones: false, stdTTL: 3600 }); // max for one hour
constructor(authentication: IAuthentication) { constructor(private eapMethods: IEAPMethod[]) {}
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<IPacketHandlerResult> {
/** 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
};
}
async handlePacket( async handlePacket(
attributes: { [key: string]: Buffer }, attributes: { [key: string]: Buffer | string },
orgRadiusPacket: RadiusPacket orgRadiusPacket: RadiusPacket
): Promise<IPacketHandlerResult> { ): Promise<IPacketHandlerResult> {
if (!attributes['EAP-Message']) { if (!attributes['EAP-Message']) {
@ -116,9 +34,9 @@ export class EAPPacketHandler implements IPacketHandler {
} }
// EAP MESSAGE // 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[] }; const currentState = this.eapConnectionStates.get(stateID) as { validMethods: IEAPMethod[] };
@ -130,10 +48,10 @@ export class EAPPacketHandler implements IPacketHandler {
log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', {}); log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', {});
// start identify // start identify
if (currentState.validMethods.length > 0) { 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 case 2: // notification
log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {}); log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
console.info('notification'); console.info('notification');
@ -180,7 +98,7 @@ export class EAPPacketHandler implements IPacketHandler {
console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`); console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`);
return this.buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods)); return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods));
} }
} }
break; break;

View File

@ -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
};
}

View File

@ -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<IPacketHandlerResult> {
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
};
}
}

View File

@ -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<IPacketHandlerResult> {
// not implemented
return {};
}
}

View File

@ -1,19 +1,20 @@
// https://tools.ietf.org/html/rfc5281 TTLS v0 // https://tools.ietf.org/html/rfc5281 TTLS v0
// https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented) // https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented)
/* eslint-disable no-bitwise */ /* eslint-disable no-bitwise */
import * as tls from 'tls'; import * as tls from 'tls';
import * as NodeCache from 'node-cache'; import * as NodeCache from 'node-cache';
import { RadiusPacket } from 'radius'; import { RadiusPacket } from 'radius';
import debug from 'debug'; import debug from 'debug';
import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../tls/crypt'; import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt';
import { ResponseAuthHandler } from '../../../types/Handler'; import { ResponseAuthHandler } from '../../../../types/Handler';
import { PAPChallenge } from './challenges/PAPChallenge'; import { PAPChallenge } from './challenges/PAPChallenge';
import { IPacketHandlerResult, PacketResponseCode } from '../../../types/PacketHandler'; import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler';
import { MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from '../../../helpers'; import { MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from '../../../../helpers';
import { IEAPMethod } from '../../../types/EAPMethod'; import { IEAPMethod } from '../../../../types/EAPMethod';
import { IAuthentication } from '../../../types/Authentication'; import { IAuthentication } from '../../../../types/Authentication';
import { secret } from '../../../../config'; import { secret } from '../../../../../config';
import { EAPPacketHandler } from '../../EAPPacketHandler';
import { EAPGTC } from './EAP-GTC';
const log = debug('radius:eap:ttls'); 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 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 { getEAPType(): number {
return 21; return 21;
} }
@ -48,23 +52,23 @@ export class EAPTTLS implements IEAPMethod {
constructor(private authentication: IAuthentication) {} constructor(private authentication: IAuthentication) {}
private buildEAPTTLSResponse( private buildEAPTTLS(
identifier: number, identifier: number,
msgType = 21, msgType = 21,
msgFlags = 0x00, msgFlags = 0x00,
stateID: string, stateID: string,
data?: Buffer, data?: Buffer,
newResponse = true newResponse = true,
): IPacketHandlerResult { maxSize = (MAX_RADIUS_ATTRIBUTE_SIZE - 5) * 4
const maxSize = (MAX_RADIUS_ATTRIBUTE_SIZE - 5) * 4; ): Buffer {
log('maxSize', maxSize); log('maxSize', maxSize);
/* it's the first one and we have more, therefore include length */ /* 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 // extract data party
const dataToSend = data && data.length > 0 && data.slice(0, maxSize); const dataToSend = maxSize > 0 ? data && data.length > 0 && data.slice(0, maxSize) : data;
const dataToQueue = data && data.length > maxSize && data.slice(maxSize); const dataToQueue = maxSize > 0 && data && data.length > maxSize && data.slice(maxSize);
/* /*
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
@ -129,6 +133,19 @@ export class EAPTTLS implements IEAPMethod {
this.queueData.del(stateID); 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)]]; const attributes: any = [['State', Buffer.from(stateID)]];
let sentDataSize = 0; let sentDataSize = 0;
do { do {
@ -162,7 +179,7 @@ export class EAPTTLS implements IEAPMethod {
Message Length | Data... Message Length | Data...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/ */
const identifier = msg.slice(1, 2).readUInt8(0);
const flags = msg.slice(5, 6).readUInt8(0); // .toString('hex'); const flags = msg.slice(5, 6).readUInt8(0); // .toString('hex');
/* /*
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
@ -191,10 +208,19 @@ export class EAPTTLS implements IEAPMethod {
let msglength; let msglength;
if (decodedFlags.lengthIncluded) { 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); 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 { return {
decodedFlags, decodedFlags,
msglength, msglength,
@ -255,13 +281,10 @@ export class EAPTTLS implements IEAPMethod {
msg: Buffer, msg: Buffer,
orgRadiusPacket: RadiusPacket orgRadiusPacket: RadiusPacket
): Promise<IPacketHandlerResult> { ): Promise<IPacketHandlerResult> {
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 // 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) {
log(
`>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS, ACK / NACK (no data, just a confirmation, ID: ${identifier})`
);
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) {
return this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, queuedData, false); return this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, queuedData, false);
@ -270,15 +293,6 @@ export class EAPTTLS implements IEAPMethod {
return {}; 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; let connection = this.openTLSSockets.get(stateID) as ITLSServer;
if (!connection) { if (!connection) {
@ -295,11 +309,18 @@ export class EAPTTLS implements IEAPMethod {
const sendResponsePromise = newDeferredPromise(); const sendResponsePromise = newDeferredPromise();
const incomingMessageHandler = async (incomingData: Buffer) => { 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); // const code = data.slice(4, 5).readUInt8(0);
switch (type) { switch (type) {
case 1: // PAP / CHAP case 1: // PAP
try { try {
const { username, password } = this.papChallenge.decode(incomingData); const { username, password } = this.papChallenge.decode(incomingData);
const authResult = await this.authentication.authenticate(username, password); const authResult = await this.authentication.authenticate(username, password);
@ -315,18 +336,52 @@ export class EAPTTLS implements IEAPMethod {
sendResponsePromise.resolve(this.buildEAPTTLSResponse(identifier, 3, 0, stateID)); sendResponsePromise.resolve(this.buildEAPTTLSResponse(identifier, 3, 0, stateID));
} }
break; 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', incomingData);
log('data str', incomingData.toString()); 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'); connection.events.emit(
// throw new Error(`unsupported auth type${type}`); 'encrypt',
sendResponsePromise.resolve( this.buildAVP(79, this.buildEAPTTLS(identifier, 3, 0, stateID, Buffer.from([1])))
this.buildEAPTTLSResponse(identifier, 3, 0, stateID, Buffer.from([1]))
); );
} }
}
}; };
const responseHandler = (encryptedResponseData: Buffer) => { const responseHandler = (encryptedResponseData: Buffer) => {
@ -341,7 +396,7 @@ export class EAPTTLS implements IEAPMethod {
connection.events.on('response', responseHandler); connection.events.on('response', responseHandler);
// emit data to tls server // emit data to tls server
connection.events.emit('send', data); connection.events.emit('decrypt', data);
const responseData = await sendResponsePromise.promise; const responseData = await sendResponsePromise.promise;
// cleanup // cleanup
@ -351,4 +406,114 @@ export class EAPTTLS implements IEAPMethod {
// send response // send response
return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData); 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;
}
} }

View File

View File

@ -1,5 +1,5 @@
import debug from 'debug'; import debug from 'debug';
import { IEAPChallenge } from '../../../../types/EAPChallenge'; import { IEAPChallenge } from '../../../../../types/EAPChallenge';
const log = debug('radius:eap:papchallenge'); const log = debug('radius:eap:papchallenge');

View File

@ -42,11 +42,16 @@ export function startTLSServer(): ITLSServer {
}); });
const encrypted = duplexpair.socket2; const encrypted = duplexpair.socket2;
emitter.on('send', (data: Buffer) => { emitter.on('decrypt', (data: Buffer) => {
encrypted.write(data); encrypted.write(data);
// encrypted.sync(); // encrypted.sync();
}); });
emitter.on('encrypt', (data: Buffer) => {
cleartext.write(data);
// encrypted.sync();
});
encrypted.on('data', (data: Buffer) => { encrypted.on('data', (data: Buffer) => {
// log('encrypted data', data, data.toString()); // log('encrypted data', data, data.toString());
emitter.emit('response', data); emitter.emit('response', data);

View File

@ -1,3 +1,3 @@
export interface IEAPChallenge { export interface IEAPChallenge {
decode(data: Buffer): { username: string; password: string }; decode(data: Buffer, stateID: string): { username: string; password?: string };
} }

View File

@ -4,7 +4,7 @@ import { IPacketHandlerResult } from './PacketHandler';
export interface IEAPMethod { export interface IEAPMethod {
getEAPType(): number; getEAPType(): number;
identify(identifier: number, stateID: string): IPacketHandlerResult; identify(identifier: number, stateID: string, msg?: Buffer): IPacketHandlerResult;
handleMessage( handleMessage(
identifier: number, identifier: number,