refactor: improve ttls tunnel stuff and fix some byte sizes for AVPs
This commit is contained in:
parent
6aa4b9f92e
commit
60ec84ae8e
10
README.md
10
README.md
@ -5,12 +5,16 @@ Basic RADIUS Server for node.js for Google LDAP Service and WPA2 Enteprise WLAN
|
||||
|
||||
Protect your WIFI access with a username and password by a credential provider you already use!
|
||||
|
||||
Authenticiation tested with Windows, Linux, Android and Apple devices.
|
||||
|
||||
## Known Issues / Disclaimer
|
||||
|
||||
This is a first implementation draft, which is currently only working with a nodejs fork (see https://github.com/nodejs/node/pull/31814).
|
||||
This is a first implementation, which is currently only working with node js nightly (node 13+) (see https://github.com/nodejs/node/pull/31814).
|
||||
|
||||
- PAP / CHAP RFC not found to implement this correctly
|
||||
- a lot of bugs
|
||||
- MD5 Challenge not implenented, but RFC says this is mandatory ;-)
|
||||
- Inner Tunnel does not act differently, even though spec says that EAP-message are not allowed to get fragmented,
|
||||
this is not a problem right now, as the messages of the inner tunnel are small enough, but it could be a bug in the future.
|
||||
ways to approach this: refactor that the inner tunnel can set max fragment size, or rebuild eap fragments in ttls after inner tunnel response
|
||||
|
||||
CONTRIBUTIONS WELCOME! If you are willing to help, just open a PR or contact me via bug system or simon.tretter@hokify.com.
|
||||
|
||||
|
@ -39,7 +39,6 @@ export class LDAPAuth implements IAuthentication {
|
||||
}
|
||||
|
||||
async authenticate(username: string, password: string) {
|
||||
// console.log('AUTH', this.ldap);
|
||||
const authResult: boolean = await new Promise((resolve, reject) => {
|
||||
this.ldap.authenticate(username, password, function(err, user) {
|
||||
if (err) {
|
||||
|
40
src/radius/PacketHandler.ts
Normal file
40
src/radius/PacketHandler.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { IPacket, IPacketHandler, IPacketHandlerResult } from '../types/PacketHandler';
|
||||
import { IAuthentication } from '../types/Authentication';
|
||||
import { EAPPacketHandler } from './handler/EAPPacketHandler';
|
||||
import { EAPTTLS } from './handler/eap/eapMethods/EAP-TTLS';
|
||||
import { EAPGTC } from './handler/eap/eapMethods/EAP-GTC';
|
||||
import { EAPMD5 } from './handler/eap/eapMethods/EAP-MD5';
|
||||
import { UserPasswordPacketHandler } from './handler/UserPasswordPacketHandler';
|
||||
|
||||
export class PacketHandler implements IPacketHandler {
|
||||
packetHandlers: IPacketHandler[] = [];
|
||||
|
||||
constructor(authentication: IAuthentication) {
|
||||
this.packetHandlers.push(
|
||||
new EAPPacketHandler([
|
||||
new EAPTTLS(authentication, this),
|
||||
new EAPGTC(authentication),
|
||||
new EAPMD5(authentication)
|
||||
])
|
||||
);
|
||||
this.packetHandlers.push(new UserPasswordPacketHandler(authentication));
|
||||
}
|
||||
|
||||
async handlePacket(packet: IPacket, handlingType?: number) {
|
||||
let response: IPacketHandlerResult;
|
||||
|
||||
let i = 0;
|
||||
if (!this.packetHandlers[i]) {
|
||||
throw new Error('no packet handlers registered');
|
||||
}
|
||||
|
||||
// process packet handlers until we get a response from one
|
||||
do {
|
||||
/* response is of type IPacketHandlerResult */
|
||||
response = await this.packetHandlers[i].handlePacket(packet, handlingType);
|
||||
i++;
|
||||
} while (this.packetHandlers[i] && (!response || !response.code));
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
@ -1,25 +1,14 @@
|
||||
import * as radius from 'radius';
|
||||
import { IAuthentication } from '../types/Authentication';
|
||||
import { EAPPacketHandler } from './handler/EAPPacketHandler';
|
||||
import { DefaultPacketHandler } from './handler/DefaultPacketHandler';
|
||||
import { IPacketHandler, IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler';
|
||||
import { 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';
|
||||
import { PacketHandler } from './PacketHandler';
|
||||
|
||||
export class RadiusService {
|
||||
radiusPacketHandlers: IPacketHandler[] = [];
|
||||
private packetHandler: PacketHandler;
|
||||
|
||||
constructor(private secret: string, private authentication: IAuthentication) {
|
||||
this.radiusPacketHandlers.push(
|
||||
new EAPPacketHandler([
|
||||
new EAPTTLS(authentication),
|
||||
new EAPGTC(authentication),
|
||||
new EAPMD5(authentication)
|
||||
])
|
||||
);
|
||||
this.radiusPacketHandlers.push(new DefaultPacketHandler(authentication));
|
||||
constructor(private secret: string, authentication: IAuthentication) {
|
||||
this.packetHandler = new PacketHandler(authentication);
|
||||
}
|
||||
|
||||
async handleMessage(
|
||||
@ -32,19 +21,7 @@ export class RadiusService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let response: IPacketHandlerResult;
|
||||
|
||||
let i = 0;
|
||||
if (!this.radiusPacketHandlers[i]) {
|
||||
throw new Error('no packet handlers registered');
|
||||
}
|
||||
|
||||
// process packet handlers until we get a response from one
|
||||
do {
|
||||
/* response is of type IPacketHandlerResult */
|
||||
response = await this.radiusPacketHandlers[i].handlePacket(packet.attributes, packet);
|
||||
i++;
|
||||
} while (this.radiusPacketHandlers[i] && (!response || !response.code));
|
||||
const response: IPacketHandlerResult = await this.packetHandler.handlePacket(packet);
|
||||
|
||||
// still no response, we are done here
|
||||
if (!response || !response.code) {
|
||||
|
@ -1,40 +1,38 @@
|
||||
// https://tools.ietf.org/html/rfc3748#section-4.1
|
||||
|
||||
import * as NodeCache from 'node-cache';
|
||||
import { RadiusPacket } from 'radius';
|
||||
import debug from 'debug';
|
||||
import { makeid } from '../../helpers';
|
||||
import { IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler';
|
||||
import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler';
|
||||
import { IEAPMethod } from '../../types/EAPMethod';
|
||||
import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper';
|
||||
|
||||
const log = debug('radius:eap');
|
||||
|
||||
export class EAPPacketHandler implements IPacketHandler {
|
||||
private identities = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds
|
||||
|
||||
// private eapConnectionStates: { [key: string]: { validMethods: IEAPMethod[] } } = {};
|
||||
private eapConnectionStates = new NodeCache({ useClones: false, stdTTL: 3600 }); // max for one hour
|
||||
|
||||
constructor(private eapMethods: IEAPMethod[]) {}
|
||||
|
||||
async handlePacket(
|
||||
attributes: { [key: string]: Buffer | string },
|
||||
orgRadiusPacket: RadiusPacket
|
||||
): Promise<IPacketHandlerResult> {
|
||||
if (!attributes['EAP-Message']) {
|
||||
async handlePacket(packet: IPacket, handlingType?: number): Promise<IPacketHandlerResult> {
|
||||
if (!packet.attributes['EAP-Message']) {
|
||||
// not an EAP message
|
||||
return {};
|
||||
}
|
||||
|
||||
const stateID = (attributes.State && attributes.State.toString()) || makeid(16);
|
||||
const stateID = (packet.attributes.State && packet.attributes.State.toString()) || makeid(16);
|
||||
|
||||
if (!this.eapConnectionStates.get(stateID)) {
|
||||
this.eapConnectionStates.set(stateID, {
|
||||
validMethods: this.eapMethods // on init all registered eap methods are valid, we kick them out in case we get a NAK response
|
||||
validMethods: this.eapMethods.filter(eap => eap.getEAPType() !== handlingType) // on init all registered eap methods are valid, we kick them out in case we get a NAK response
|
||||
});
|
||||
}
|
||||
|
||||
// EAP MESSAGE
|
||||
const msg = attributes['EAP-Message'] as Buffer;
|
||||
const msg = packet.attributes['EAP-Message'] as Buffer;
|
||||
|
||||
const { code, type, identifier, data } = decodeEAPHeader(msg);
|
||||
|
||||
@ -45,7 +43,13 @@ export class EAPPacketHandler implements IPacketHandler {
|
||||
case 2: // for response
|
||||
switch (type) {
|
||||
case 1: // identifiy
|
||||
log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', {});
|
||||
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);
|
||||
@ -65,6 +69,7 @@ export class EAPPacketHandler implements IPacketHandler {
|
||||
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
|
||||
@ -73,27 +78,38 @@ export class EAPPacketHandler implements IPacketHandler {
|
||||
supportedEAPMethods.push(supportedMethod);
|
||||
}
|
||||
|
||||
this.eapConnectionStates.set(stateID, {
|
||||
...currentState,
|
||||
validMethods: currentState.validMethods.filter(method => {
|
||||
return supportedEAPMethods.includes(method.getEAPType()); // kick it out?
|
||||
})
|
||||
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 = currentState.validMethods.find(method => {
|
||||
const eapMethod = this.eapMethods.find(method => {
|
||||
return type === method.getEAPType();
|
||||
});
|
||||
|
||||
if (eapMethod) {
|
||||
return eapMethod.handleMessage(identifier, stateID, msg, orgRadiusPacket);
|
||||
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
|
||||
const serverSupportedMethods = currentState.validMethods.map(method =>
|
||||
method.getEAPType()
|
||||
);
|
||||
|
||||
console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`);
|
||||
|
@ -1,22 +1,29 @@
|
||||
import debug from 'debug';
|
||||
import { IAuthentication } from '../../types/Authentication';
|
||||
import {
|
||||
IPacket,
|
||||
IPacketHandler,
|
||||
IPacketHandlerResult,
|
||||
PacketResponseCode
|
||||
} from '../../types/PacketHandler';
|
||||
|
||||
export class DefaultPacketHandler implements IPacketHandler {
|
||||
const log = debug('radius:user-pwd');
|
||||
|
||||
export class UserPasswordPacketHandler implements IPacketHandler {
|
||||
constructor(private authentication: IAuthentication) {}
|
||||
|
||||
async handlePacket(attributes: { [key: string]: Buffer }): Promise<IPacketHandlerResult> {
|
||||
const username = attributes['User-Name'];
|
||||
const password = attributes['User-Password'];
|
||||
async handlePacket(packet: IPacket): Promise<IPacketHandlerResult> {
|
||||
const username = packet.attributes['User-Name'];
|
||||
const password = packet.attributes['User-Password'];
|
||||
|
||||
if (!username || !password) {
|
||||
// params missing, this handler cannot continue...
|
||||
return {};
|
||||
}
|
||||
|
||||
log('username', username, username.toString());
|
||||
log('token', password, password.toString());
|
||||
|
||||
const authenticated = await this.authentication.authenticate(
|
||||
username.toString(),
|
||||
password.toString()
|
||||
@ -24,7 +31,8 @@ export class DefaultPacketHandler implements IPacketHandler {
|
||||
if (authenticated) {
|
||||
// success
|
||||
return {
|
||||
code: PacketResponseCode.AccessAccept
|
||||
code: PacketResponseCode.AccessAccept,
|
||||
attributes: [['User-Name', username]]
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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';
|
||||
@ -11,24 +10,19 @@ 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');
|
||||
extractValue(msg: Buffer) {
|
||||
let tillBinary0 = msg.findIndex(v => v === 0) || msg.length;
|
||||
if (tillBinary0 < 0) {
|
||||
tillBinary0 = msg.length - 1;
|
||||
}
|
||||
return msg.slice(0, tillBinary0 + 1); // use token til binary 0.
|
||||
}
|
||||
|
||||
identify(identifier: number, _stateID: string): IPacketHandlerResult {
|
||||
return buildEAPResponse(identifier, 6, Buffer.from('Password: '));
|
||||
}
|
||||
|
||||
@ -36,18 +30,16 @@ export class EAPGTC implements IEAPMethod {
|
||||
|
||||
async handleMessage(
|
||||
_identifier: number,
|
||||
stateID: string,
|
||||
msg: Buffer
|
||||
_stateID: string,
|
||||
msg: Buffer,
|
||||
_,
|
||||
identity?: string
|
||||
): Promise<IPacketHandlerResult> {
|
||||
const username = this.loginData.get(stateID) as Buffer | undefined;
|
||||
const username = identity; // 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.
|
||||
const token = this.extractValue(data);
|
||||
|
||||
if (!username) {
|
||||
throw new Error('no username');
|
||||
@ -59,7 +51,8 @@ export class EAPGTC implements IEAPMethod {
|
||||
const success = await this.authentication.authenticate(username.toString(), token.toString());
|
||||
|
||||
return {
|
||||
code: success ? PacketResponseCode.AccessAccept : PacketResponseCode.AccessReject
|
||||
code: success ? PacketResponseCode.AccessAccept : PacketResponseCode.AccessReject,
|
||||
attributes: (success && [['User-Name', username]]) || undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ 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;
|
||||
|
@ -3,18 +3,23 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
import * as tls from 'tls';
|
||||
import * as NodeCache from 'node-cache';
|
||||
import { RadiusPacket } from 'radius';
|
||||
// @ts-ignore
|
||||
import { attr_id_to_name, attr_name_to_id } from 'radius';
|
||||
import debug from 'debug';
|
||||
|
||||
import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt';
|
||||
import { ResponseAuthHandler } from '../../../../types/Handler';
|
||||
import { PAPChallenge } from './challenges/PAPChallenge';
|
||||
import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler';
|
||||
import {
|
||||
IPacket,
|
||||
IPacketAttributes,
|
||||
IPacketHandler,
|
||||
IPacketHandlerResult,
|
||||
PacketResponseCode
|
||||
} from '../../../../types/PacketHandler';
|
||||
import { MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from '../../../../helpers';
|
||||
import { IEAPMethod } from '../../../../types/EAPMethod';
|
||||
import { IAuthentication } from '../../../../types/Authentication';
|
||||
import { secret } from '../../../../../config';
|
||||
import { EAPPacketHandler } from '../../EAPPacketHandler';
|
||||
import { EAPGTC } from './EAP-GTC';
|
||||
|
||||
const log = debug('radius:eap:ttls');
|
||||
|
||||
@ -31,17 +36,24 @@ function tlsHasExportKeyingMaterial(
|
||||
return typeof (tlsSocket as any).exportKeyingMaterial === 'function';
|
||||
}
|
||||
|
||||
export class EAPTTLS implements IEAPMethod {
|
||||
private papChallenge: PAPChallenge = new PAPChallenge();
|
||||
interface IAVPEntry {
|
||||
type: number;
|
||||
flags: string;
|
||||
decodedFlags: {
|
||||
V: boolean;
|
||||
M: boolean;
|
||||
};
|
||||
length: number;
|
||||
vendorId?: number;
|
||||
data: Buffer;
|
||||
}
|
||||
|
||||
export class EAPTTLS implements IEAPMethod {
|
||||
// { [key: string]: Buffer } = {};
|
||||
private queueData = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds
|
||||
|
||||
private openTLSSockets = new NodeCache({ useClones: false, stdTTL: 3600 }); // keep sockets for about one hour
|
||||
|
||||
// EAP TUNNEL
|
||||
tunnelEAP = new EAPPacketHandler([new EAPGTC(this.authentication)]); // tunnel with GTC support
|
||||
|
||||
getEAPType(): number {
|
||||
return 21;
|
||||
}
|
||||
@ -50,7 +62,7 @@ export class EAPTTLS implements IEAPMethod {
|
||||
return this.buildEAPTTLSResponse(identifier, 21, 0x20, stateID);
|
||||
}
|
||||
|
||||
constructor(private authentication: IAuthentication) {}
|
||||
constructor(private authentication: IAuthentication, private innerTunnel: IPacketHandler) {}
|
||||
|
||||
private buildEAPTTLS(
|
||||
identifier: number,
|
||||
@ -179,7 +191,7 @@ export class EAPTTLS implements IEAPMethod {
|
||||
Message Length | Data...
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
const identifier = msg.slice(1, 2).readUInt8(0);
|
||||
// const identifier = msg.slice(1, 2).readUInt8(0);
|
||||
const flags = msg.slice(5, 6).readUInt8(0); // .toString('hex');
|
||||
/*
|
||||
0 1 2 3 4 5 6 7
|
||||
@ -215,7 +227,7 @@ export class EAPTTLS implements IEAPMethod {
|
||||
log('>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS', {
|
||||
flags: `00000000${flags.toString(2)}`.substr(-8),
|
||||
decodedFlags,
|
||||
identifier,
|
||||
// identifier,
|
||||
msglength,
|
||||
data
|
||||
// dataStr: data.toString()
|
||||
@ -232,7 +244,7 @@ export class EAPTTLS implements IEAPMethod {
|
||||
identifier: number,
|
||||
success: boolean,
|
||||
socket: tls.TLSSocket,
|
||||
packet: RadiusPacket
|
||||
packet: IPacket
|
||||
): IPacketHandlerResult {
|
||||
const buffer = Buffer.from([
|
||||
success ? 3 : 4, // 3.. success, 4... failure
|
||||
@ -246,22 +258,26 @@ export class EAPTTLS implements IEAPMethod {
|
||||
|
||||
if (packet.attributes && packet.attributes['User-Name']) {
|
||||
// reappend username to response
|
||||
attributes.push(['User-Name', packet.attributes['User-Name']]);
|
||||
attributes.push(['User-Name', packet.attributes['User-Name'].toString()]);
|
||||
}
|
||||
|
||||
if (tlsHasExportKeyingMaterial(socket)) {
|
||||
const keyingMaterial = (socket as any).exportKeyingMaterial(128, 'ttls keying material');
|
||||
const keyingMaterial = socket.exportKeyingMaterial(128, 'ttls keying material');
|
||||
|
||||
if (!packet.authenticator) {
|
||||
throw new Error('FATAL: no packet authenticator variable set');
|
||||
}
|
||||
|
||||
attributes.push([
|
||||
'Vendor-Specific',
|
||||
311,
|
||||
[[16, encodeTunnelPW(keyingMaterial.slice(64), (packet as any).authenticator, secret)]]
|
||||
[[16, encodeTunnelPW(keyingMaterial.slice(64), packet.authenticator, secret)]]
|
||||
]); // MS-MPPE-Send-Key
|
||||
|
||||
attributes.push([
|
||||
'Vendor-Specific',
|
||||
311,
|
||||
[[17, encodeTunnelPW(keyingMaterial.slice(0, 64), (packet as any).authenticator, secret)]]
|
||||
[[17, encodeTunnelPW(keyingMaterial.slice(0, 64), packet.authenticator, secret)]]
|
||||
]); // MS-MPPE-Recv-Key
|
||||
} else {
|
||||
console.error(
|
||||
@ -279,7 +295,7 @@ export class EAPTTLS implements IEAPMethod {
|
||||
identifier: number,
|
||||
stateID: string,
|
||||
msg: Buffer,
|
||||
orgRadiusPacket: RadiusPacket
|
||||
packet: IPacket
|
||||
): Promise<IPacketHandlerResult> {
|
||||
const { data } = this.decodeTTLSMessage(msg);
|
||||
|
||||
@ -313,12 +329,58 @@ export class EAPTTLS implements IEAPMethod {
|
||||
ret.attributes = {};
|
||||
ret.raw_attributes = [];
|
||||
|
||||
const { type, data: AVPdata, length: AVPlength } = this.decodeAVP(incomingData);
|
||||
const AVPs = this.decodeAVPs(incomingData);
|
||||
|
||||
console.log('AVP data', { AVPdata, AVPlength, AVPdataStr: AVPdata.toString() });
|
||||
// build attributes for packet handler
|
||||
const attributes: IPacketAttributes = {};
|
||||
AVPs.forEach(avp => {
|
||||
attributes[attr_id_to_name(avp.type)] = avp.data;
|
||||
});
|
||||
|
||||
// const code = data.slice(4, 5).readUInt8(0);
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
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(attr_name_to_id('EAP-Message'), eapMessage[1] as Buffer)
|
||||
);
|
||||
|
||||
/*
|
||||
switch (type) {
|
||||
case 1: // PAP
|
||||
try {
|
||||
@ -381,7 +443,7 @@ export class EAPTTLS implements IEAPMethod {
|
||||
this.buildAVP(79, this.buildEAPTTLS(identifier, 3, 0, stateID, Buffer.from([1])))
|
||||
);
|
||||
}
|
||||
}
|
||||
} */
|
||||
};
|
||||
|
||||
const responseHandler = (encryptedResponseData: Buffer) => {
|
||||
@ -407,9 +469,31 @@ export class EAPTTLS implements IEAPMethod {
|
||||
return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData);
|
||||
}
|
||||
|
||||
private decodeAVP(buffer: Buffer) {
|
||||
/**
|
||||
* 4.1. AVP Header
|
||||
private transformAttributesArrayToMap(attributes: [string, Buffer | string][] | undefined) {
|
||||
const result = {};
|
||||
attributes?.forEach(([key, value]) => {
|
||||
result[key] = value;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private decodeAVPs(buffer: Buffer): IAVPEntry[] {
|
||||
const results: {
|
||||
type: number;
|
||||
flags: string;
|
||||
decodedFlags: {
|
||||
V: boolean;
|
||||
M: boolean;
|
||||
};
|
||||
length: number;
|
||||
vendorId?: number;
|
||||
data: Buffer;
|
||||
}[] = [];
|
||||
|
||||
let currentBuffer = buffer;
|
||||
do {
|
||||
/**
|
||||
* 4.1. AVP Header
|
||||
|
||||
The fields in the AVP header MUST be sent in network byte order. The
|
||||
format of the header is:
|
||||
@ -425,37 +509,47 @@ export class EAPTTLS implements IEAPMethod {
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 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 type = currentBuffer.slice(0, 4).readUInt32BE(0);
|
||||
const flags = currentBuffer.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
|
||||
// const length = buffer.slice(5, 8).readUInt16BE(0); // actually a Int24BE
|
||||
const length = currentBuffer.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);
|
||||
}
|
||||
let vendorId;
|
||||
let data;
|
||||
if (flags & 0b010000000) {
|
||||
// V flag set
|
||||
vendorId = currentBuffer.slice(8, 12).readUInt32BE(0);
|
||||
data = currentBuffer.slice(12, length);
|
||||
} else {
|
||||
data = currentBuffer.slice(8, length);
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
flags: `00000000${flags.toString(2)}`.substr(-8),
|
||||
decodedFlags,
|
||||
length,
|
||||
vendorId,
|
||||
data
|
||||
};
|
||||
results.push({
|
||||
type,
|
||||
flags: `00000000${flags.toString(2)}`.substr(-8),
|
||||
decodedFlags,
|
||||
length,
|
||||
vendorId,
|
||||
data
|
||||
});
|
||||
|
||||
// ensure length is a multiple of 4 octect
|
||||
let totalAVPSize = length;
|
||||
while (totalAVPSize % 4 !== 0) {
|
||||
totalAVPSize += 1;
|
||||
}
|
||||
currentBuffer = currentBuffer.slice(totalAVPSize);
|
||||
} while (currentBuffer.length > 0);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private buildAVP(
|
||||
@ -481,9 +575,9 @@ export class EAPTTLS implements IEAPMethod {
|
||||
| Data ...
|
||||
+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
let b = Buffer.alloc(8);
|
||||
let AVP = Buffer.alloc(8);
|
||||
|
||||
b.writeInt32BE(code, 0); // EAP-Message
|
||||
AVP.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
|
||||
@ -506,14 +600,19 @@ export class EAPTTLS implements IEAPMethod {
|
||||
flagValue += 0b01000000;
|
||||
}
|
||||
|
||||
console.log('flagValue', flagValue, `00000000${flagValue.toString(2)}`.substr(-8));
|
||||
// log('flagValue', flagValue, `00000000${flagValue.toString(2)}`.substr(-8));
|
||||
|
||||
b.writeInt8(flagValue, 4); // flags (set V..)
|
||||
AVP.writeInt8(flagValue, 4); // flags (set V..)
|
||||
|
||||
b = Buffer.concat([b, data]); // , Buffer.from('\0')]);
|
||||
AVP = Buffer.concat([AVP, 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)
|
||||
AVP.writeInt16BE(AVP.byteLength, 6); // write size (actually we would need a Int24BE here, but it is good to go with 16bits)
|
||||
|
||||
return b;
|
||||
// fill up with 0x00 till we have % 4
|
||||
while (AVP.length % 4 !== 0) {
|
||||
AVP = Buffer.concat([AVP, Buffer.from([0x00])]);
|
||||
}
|
||||
|
||||
return AVP;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import * as crypto from 'crypto';
|
||||
import * as DuplexPair from 'native-duplexpair';
|
||||
import * as constants from 'constants';
|
||||
import debug from 'debug';
|
||||
import { makeid } from '../helpers';
|
||||
import * as config from '../../config';
|
||||
|
||||
const log = debug('radius:tls');
|
||||
@ -136,11 +135,10 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
|
||||
contents of each Salt field in a given Access-Accept packet MUST
|
||||
be unique.
|
||||
*/
|
||||
const salt = Buffer.concat([
|
||||
// eslint-disable-next-line no-bitwise
|
||||
Buffer.from((Number(makeid(1)) & 0b10000000).toString()), // ensure left most bit is set (1)
|
||||
Buffer.from(makeid(1))
|
||||
]);
|
||||
const salt = crypto.randomBytes(2);
|
||||
|
||||
// eslint-disable-next-line no-bitwise
|
||||
salt[0] |= 0b10000000; // ensure leftmost bit is set to 1
|
||||
|
||||
/*
|
||||
String
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { RadiusPacket } from 'radius';
|
||||
import { IPacketHandlerResult } from './PacketHandler';
|
||||
import { IPacket, IPacketHandlerResult } from './PacketHandler';
|
||||
|
||||
export interface IEAPMethod {
|
||||
getEAPType(): number;
|
||||
@ -10,6 +9,7 @@ export interface IEAPMethod {
|
||||
identifier: number,
|
||||
stateID: string,
|
||||
msg: Buffer,
|
||||
orgRadiusPacket?: RadiusPacket
|
||||
packet?: IPacket,
|
||||
identity?: string
|
||||
): Promise<IPacketHandlerResult>;
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { RadiusPacket } from 'radius';
|
||||
|
||||
export enum PacketResponseCode {
|
||||
AccessChallenge = 'Access-Challenge',
|
||||
AccessAccept = 'Access-Accept',
|
||||
@ -8,12 +6,19 @@ export enum PacketResponseCode {
|
||||
|
||||
export interface IPacketHandlerResult {
|
||||
code?: PacketResponseCode;
|
||||
attributes?: [string, Buffer][];
|
||||
attributes?: [string, Buffer | string][];
|
||||
}
|
||||
|
||||
export interface IPacketAttributes {
|
||||
[key: string]: string | Buffer;
|
||||
}
|
||||
|
||||
export interface IPacket {
|
||||
attributes: { [key: string]: string | Buffer };
|
||||
authenticator?: Buffer;
|
||||
}
|
||||
|
||||
export interface IPacketHandler {
|
||||
handlePacket(
|
||||
attributes: { [key: string]: Buffer },
|
||||
orgRadiusPacket: RadiusPacket
|
||||
): Promise<IPacketHandlerResult>;
|
||||
/** handlingType is the attreibute ID of the currently processing type (e.g. TTLS, GTC, MD5,..) */
|
||||
handlePacket(packet: IPacket, handlingType?: number): Promise<IPacketHandlerResult>;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user