@ -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 : Radius Packet
packet : I Packet
) : 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: Radius Packet
packet: I Packet
) : 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 & 0 b10000000 ) ,
// M
M : ! ! ( flags & 0 b01000000 )
} ;
* /
const type = currentBuffer . slice ( 0 , 4 ) . readUInt32BE ( 0 ) ;
const flags = currentBuffer . slice ( 4 , 5 ) . readUInt8 ( 0 ) ;
const decodedFlags = {
// L
V : ! ! ( flags & 0 b10000000 ) ,
// M
M : ! ! ( flags & 0 b01000000 )
} ;
// 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 & 0 b010000000 ) {
// V flag set
vendorId = currentBuffer . slice ( 8 , 12 ) . readUInt32BE ( 0 ) ;
data = currentBuffer . slice ( 12 , length ) ;
} else {
data = currentBuffer . slice ( 8 , length ) ;
}
// const length = buffer.slice(5, 8).readUInt16BE(0); // actually a Int24BE
const length = buffer . slice ( 6 , 8 ) . readUInt16BE ( 0 ) ; // actually a Int24BE
results . push ( {
type ,
flags : ` 00000000 ${ flags . toString ( 2 ) } ` . substr ( - 8 ) ,
decodedFlags ,
length ,
vendorId ,
data
} ) ;
let vendorId ;
let data ;
if ( flags & 0 b010000000 ) {
// V flag set
vendorId = buffer . slice ( 8 , 12 ) . readUInt32BE ( 0 ) ;
data = buffer . slice ( 8 , 12 ) ;
} else {
data = buffer . slice ( 8 ) ;
}
// 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 {
type ,
flags : ` 00000000 ${ flags . toString ( 2 ) } ` . substr ( - 8 ) ,
decodedFlags ,
length ,
vendorId ,
data
} ;
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 += 0 b01000000 ;
}
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)
// fill up with 0x00 till we have % 4
while ( AVP . length % 4 !== 0 ) {
AVP = Buffer . concat ( [ AVP , Buffer . from ( [ 0x00 ] ) ] ) ;
}
return b ;
return AVP ;
}
}