feat: inner tunnel for TTSL support added
This commit is contained in:
parent
191bb54264
commit
6aa4b9f92e
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
79
src/radius/handler/eap/EAPHelper.ts
Normal file
79
src/radius/handler/eap/EAPHelper.ts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
65
src/radius/handler/eap/eapMethods/EAP-GTC.ts
Normal file
65
src/radius/handler/eap/eapMethods/EAP-GTC.ts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
40
src/radius/handler/eap/eapMethods/EAP-MD5.ts
Normal file
40
src/radius/handler/eap/eapMethods/EAP-MD5.ts
Normal 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 {};
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
0
src/radius/handler/eap/eapMethods/EAP.ts
Normal file
0
src/radius/handler/eap/eapMethods/EAP.ts
Normal 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');
|
||||||
|
|
@ -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);
|
||||||
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user