Initial CoreID changes to allow code-based integration

This commit is contained in:
Garrett Mills 2021-10-24 15:37:16 -05:00
parent 1149fc054a
commit 78c57d7747
20 changed files with 261 additions and 98 deletions

2
.gitignore vendored
View File

@ -9,3 +9,5 @@ tsconfig.tsbuildinfo
# custom certificates # custom certificates
/ssl-*/ /ssl-*/
.idea

39
LICENSE Normal file
View File

@ -0,0 +1,39 @@
@coreid/node-radius-server - CoreID maintained fork
Copyright (C) 2021 Simon Tretter, Garrett Mills
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
--
Note: this is a fork of Simon Tretter's node-radius-server package
modified for use in Starship CoreID. Per the terms of GPLv3, the
modifications made are documented below:
- Introduced the PackageInterface helper class
- enables integrating the library into code rather than as a
standalone CLI app
- Modified existing code to use configuration on the PackageInterface
class instead of global imports
- Modified existing code to log through PackageInterface.logger
- Modified existing code to introduce a credentialMiddleware method
to IPacket, which allows external code to customize the credentials
before they are sent
- Modified RadiusService to allow specifying a custom packet decoder
method on PackageInterface.
- Modified UDPServer class to expose a stop() method

View File

@ -1,6 +1,6 @@
{ {
"name": "radius-server", "name": "@coreid/radius-server",
"description": "radius server for google LDAP and TTLS", "description": "CoreID fork of radius server for google LDAP and TTLS",
"version": "1.2.1", "version": "1.2.1",
"engines": { "engines": {
"node": ">13.10.1" "node": ">13.10.1"
@ -14,7 +14,7 @@
"dist", "dist",
"ssl" "ssl"
], ],
"homepage": "https://github.com/simllll/node-radius-server", "homepage": "https://code.garrettmills.dev/starship/node-radius-server",
"scripts": { "scripts": {
"release": "npm run build && standard-version", "release": "npm run build && standard-version",
"debug": "DEBUG=radius:* node --tls-min-v1.0 dist/app.js", "debug": "DEBUG=radius:* node --tls-min-v1.0 dist/app.js",

View File

@ -6,13 +6,17 @@ import * as config from '../config';
import { Authentication } from './auth'; import { Authentication } from './auth';
import { IAuthentication } from './types/Authentication'; import { IAuthentication } from './types/Authentication';
import { startTLSServer } from './tls/crypt'; import { startTLSServer } from './tls/crypt';
import PackageInterface from './interface';
const packageInterface = PackageInterface.get();
const prestartServer = () => {
/* test node version */ /* test node version */
const testSocket = startTLSServer(); const testSocket = startTLSServer();
if (typeof testSocket.tls.exportKeyingMaterial !== 'function') { if (typeof testSocket.tls.exportKeyingMaterial !== 'function') {
console.error(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`); packageInterface.log(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`);
console.log('min version supported is node js 14. run "sudo npx n 14"'); packageInterface.log('min version supported is node js 14. run "sudo npx n 14"');
process.exit(-1); process.exit(-1);
} }
@ -33,12 +37,13 @@ const { argv } = yargs
argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any }; argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any };
}; };
console.log(`Listener Port: ${argv.port || 1812}`); packageInterface.log(`Listener Port: ${argv.port || 1812}`);
console.log(`RADIUS Secret: ${argv.secret}`); packageInterface.log(`RADIUS Secret: ${argv.secret}`);
console.log(`Auth ${argv.authentication}`); packageInterface.log(`Auth ${argv.authentication}`);
console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined, 3)}`); packageInterface.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined, 3)}`);
};
(async () => { const startServer = async () => {
/* configure auth mechansim */ /* configure auth mechansim */
let auth: IAuthentication; let auth: IAuthentication;
try { try {
@ -47,7 +52,7 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined
]; ];
auth = new AuthMechanismus(config.authenticationOptions); auth = new AuthMechanismus(config.authenticationOptions);
} catch (err) { } catch (err) {
console.error('cannot load auth mechanismus', config.authentication); packageInterface.log('cannot load auth mechanismus', config.authentication);
throw err; throw err;
} }
// start radius server // start radius server
@ -66,7 +71,7 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined
rinfo.address, rinfo.address,
(err, _bytes) => { (err, _bytes) => {
if (err) { if (err) {
console.log('Error sending response to ', rinfo); packageInterface.log('Error sending response to ', rinfo);
} }
}, },
response.expectAcknowledgment response.expectAcknowledgment
@ -76,4 +81,9 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined
// start server // start server
await server.start(); await server.start();
})(); };
if (packageInterface.start) {
prestartServer();
startServer();
}

View File

@ -1,8 +1,11 @@
import * as NodeCache from 'node-cache'; import * as NodeCache from 'node-cache';
import { Cache, ExpirationStrategy, MemoryStorage } from '@hokify/node-ts-cache'; import { Cache, ExpirationStrategy, MemoryStorage } from '@hokify/node-ts-cache';
import { IAuthentication } from './types/Authentication'; import { IAuthentication } from './types/Authentication';
import PackageInterface from './interface';
const cacheStrategy = new ExpirationStrategy(new MemoryStorage()); const cacheStrategy = new ExpirationStrategy(new MemoryStorage());
const packageInterface = PackageInterface.get();
/** /**
* this is just a simple abstraction to provide * this is just a simple abstraction to provide
* an application layer for caching credentials * an application layer for caching credentials
@ -12,18 +15,25 @@ export class Authentication implements IAuthentication {
constructor(private authenticator: IAuthentication) {} constructor(private authenticator: IAuthentication) {}
@Cache(cacheStrategy, { ttl: 60000 }) @Cache(cacheStrategy, { ttl: packageInterface.cacheTTL })
async authenticate(username: string, password: string): Promise<boolean> { async authenticate(username: string, password: string): Promise<boolean> {
const cacheKey = `usr:${username}|pwd:${password}`; const cacheKey = `usr:${username}|pwd:${password}`;
const fromCache = this.cache.get(cacheKey) as undefined | boolean; const fromCache = this.cache.get(cacheKey) as undefined | boolean;
if (fromCache !== undefined) { if (fromCache !== undefined) {
console.log(`Cached Auth Result for user ${username}`, fromCache ? 'SUCCESS' : 'Failure'); packageInterface.log(
`Cached Auth Result for user ${username}`,
fromCache ? 'SUCCESS' : 'Failure'
);
return fromCache; return fromCache;
} }
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'); packageInterface.log(`Auth Result for user ${username}`, authResult ? 'SUCCESS' : 'Failure');
this.cache.set(cacheKey, authResult, authResult ? 86400 : 60); // cache for one day on success, otherwise just for 60 seconds this.cache.set(
cacheKey,
authResult,
authResult ? packageInterface.cacheSuccessTTL : packageInterface.cacheFailTTL
); // cache for one day on success, otherwise just for 60 seconds
return authResult; return authResult;
} }

View File

@ -1,12 +1,14 @@
import { ClientOptions, createClient } from 'ldapjs'; import { ClientOptions, createClient } from 'ldapjs';
import debug from 'debug';
import * as tls from 'tls'; import * as tls from 'tls';
import * as fs from 'fs'; import * as fs from 'fs';
import { IAuthentication } from '../types/Authentication'; import { IAuthentication } from '../types/Authentication';
import PackageInterface from '../interface';
const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
const usernameFields = ['posixUid', 'mail']; const usernameFields = ['posixUid', 'mail'];
const log = debug('radius:auth:google-ldap');
// TLS: // TLS:
// https://github.com/ldapjs/node-ldapjs/issues/307 // https://github.com/ldapjs/node-ldapjs/issues/307
@ -55,7 +57,7 @@ export class GoogleLDAPAuth implements IAuthentication {
}; };
this.fetchDNs().catch((err) => { this.fetchDNs().catch((err) => {
console.error('fatal error google ldap auth, cannot fetch DNs', err); log('fatal error google ldap auth, cannot fetch DNs', err);
}); });
} }
@ -64,7 +66,7 @@ export class GoogleLDAPAuth implements IAuthentication {
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const ldapDNClient = createClient(this.config).on('error', (error) => { const ldapDNClient = createClient(this.config).on('error', (error) => {
console.error('Error in ldap', error); log('Error in ldap', error);
reject(error); reject(error);
}); });
@ -92,7 +94,7 @@ export class GoogleLDAPAuth implements IAuthentication {
}); });
res.on('error', (ldapErr) => { res.on('error', (ldapErr) => {
console.error(`error: ${JSON.stringify(ldapErr)}`); log(`error: ${JSON.stringify(ldapErr)}`);
reject(ldapErr); reject(ldapErr);
}); });
@ -144,7 +146,7 @@ export class GoogleLDAPAuth implements IAuthentication {
return this.authenticate(username, password, count, true); return this.authenticate(username, password, count, true);
} }
// console.log('this.allValidDNsCache', this.allValidDNsCache); // console.log('this.allValidDNsCache', this.allValidDNsCache);
console.error(`invalid username, not found in DN: ${username}`); // , this.allValidDNsCache); log(`invalid username, not found in DN: ${username}`); // , this.allValidDNsCache);
return false; return false;
} }

View File

@ -1,5 +1,9 @@
import axios from 'axios'; import axios from 'axios';
import { IAuthentication } from '../types/Authentication'; import { IAuthentication } from '../types/Authentication';
import PackageInterface from '../interface';
const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
interface IHTTPAuthOptions { interface IHTTPAuthOptions {
url: string; url: string;
@ -30,7 +34,7 @@ export class HTTPAuth implements IAuthentication {
return true; return true;
} }
console.log(`HTTP authentication failed, response code: ${result.status}`); log(`HTTP authentication failed, response code: ${result.status}`);
return false; return false;
} }

View File

@ -1,5 +1,9 @@
import * as imaps from 'imap-simple'; import * as imaps from 'imap-simple';
import { IAuthentication } from '../types/Authentication'; import { IAuthentication } from '../types/Authentication';
import PackageInterface from '../interface';
const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
interface IIMAPAuthOptions { interface IIMAPAuthOptions {
host: string; host: string;
@ -34,7 +38,7 @@ export class IMAPAuth implements IAuthentication {
if (this.validHosts) { if (this.validHosts) {
const domain = username.split('@').pop(); const domain = username.split('@').pop();
if (!domain || !this.validHosts.includes(domain)) { if (!domain || !this.validHosts.includes(domain)) {
console.info('invalid or no domain in username', username, domain); log('invalid or no domain in username', username, domain);
return false; return false;
} }
} }
@ -57,7 +61,7 @@ export class IMAPAuth implements IAuthentication {
connection.end(); connection.end();
} catch (err) { } catch (err) {
console.error('imap auth failed', err); log('imap auth failed', err);
} }
return success; return success;
} }

View File

@ -1,6 +1,10 @@
import * as LdapAuth from 'ldapauth-fork'; import * as LdapAuth from 'ldapauth-fork';
import * as fs from 'fs'; import * as fs from 'fs';
import { IAuthentication } from '../types/Authentication'; import { IAuthentication } from '../types/Authentication';
import PackageInterface from '../interface';
const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
interface ILDAPAuthOptions { interface ILDAPAuthOptions {
/** ldap url /** ldap url
@ -44,7 +48,7 @@ export class LDAPAuth implements IAuthentication {
reconnect: true, reconnect: true,
}); });
this.ldap.on('error', (err) => { this.ldap.on('error', (err) => {
console.error('LdapAuth: ', err); log('LdapAuth: ', err);
}); });
} }
@ -53,7 +57,7 @@ export class LDAPAuth implements IAuthentication {
this.ldap.authenticate(username, password, (err, user) => { this.ldap.authenticate(username, password, (err, user) => {
if (err) { if (err) {
resolve(false); resolve(false);
console.error('ldap error', err); log('ldap error', err);
// reject(err); // reject(err);
} }
if (user) resolve(user); if (user) resolve(user);

View File

@ -1,5 +1,8 @@
import { SMTPClient } from 'smtp-client'; import { SMTPClient } from 'smtp-client';
import { IAuthentication } from '../types/Authentication'; import { IAuthentication } from '../types/Authentication';
import PackageInterface from '../interface';
const log = (...args) => PackageInterface.get().log(...args);
interface ISMTPAuthOptions { interface ISMTPAuthOptions {
host: string; host: string;
@ -37,7 +40,7 @@ export class SMTPAuth implements IAuthentication {
if (this.validHosts) { if (this.validHosts) {
const domain = username.split('@').pop(); const domain = username.split('@').pop();
if (!domain || !this.validHosts.includes(domain)) { if (!domain || !this.validHosts.includes(domain)) {
console.info('invalid or no domain in username', username, domain); log('invalid or no domain in username', username, domain);
return false; return false;
} }
} }
@ -61,7 +64,7 @@ export class SMTPAuth implements IAuthentication {
s.close(); // runs QUIT command s.close(); // runs QUIT command
} catch (err) { } catch (err) {
console.error('imap auth failed', err); log('imap auth failed', err);
} }
return success; return success;
} }

47
src/interface.ts Normal file
View File

@ -0,0 +1,47 @@
import * as radius from 'radius';
import * as config from '../config';
import { IPacket } from './types/PacketHandler';
export type PacketDecoder = (msg: Buffer) => {
packet?: radius.RadiusPacket & IPacket;
secret: string;
};
export default class PackageInterface {
private static _instance?: PackageInterface;
public static get(): PackageInterface {
if (!this._instance) {
this._instance = new PackageInterface();
}
return this._instance;
}
public packetDecoder?: PacketDecoder;
public start = true;
public cacheTTL = 60000;
public cacheSuccessTTL = 86400;
public cacheFailTTL = 60;
public logger: (...any: unknown[]) => unknown = console.log; // eslint-disable-line no-console
private config: any = config;
public log(...any: unknown[]): void {
this.logger(...any);
}
public getConfig(): any {
return this.config;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public setConfig(inConfig: any) {
this.config = inConfig;
}
}

View File

@ -1,8 +1,11 @@
import * as radius from 'radius'; import * as radius from 'radius';
import { IAuthentication } from '../types/Authentication'; import { IAuthentication } from '../types/Authentication';
import { IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler'; import {IPacket, IPacketHandlerResult, PacketResponseCode} from '../types/PacketHandler';
import { PacketHandler } from './PacketHandler'; import { PacketHandler } from './PacketHandler';
import PackageInterface from '../interface';
const packageInterface = PackageInterface.get();
export class RadiusService { export class RadiusService {
private packetHandler: PacketHandler; private packetHandler: PacketHandler;
@ -11,13 +14,29 @@ export class RadiusService {
this.packetHandler = new PacketHandler(authentication); this.packetHandler = new PacketHandler(authentication);
} }
defaultDecoder(msg: Buffer): { packet?: radius.RadiusPacket & IPacket; secret: string } {
const packet = radius.decode({ packet: msg, secret: this.secret });
return {
packet,
secret: this.secret,
};
}
async handleMessage( async handleMessage(
msg: Buffer msg: Buffer
): Promise<{ data: Buffer; expectAcknowledgment?: boolean } | undefined> { ): Promise<{ data: Buffer; expectAcknowledgment?: boolean } | undefined> {
const packet = radius.decode({ packet: msg, secret: this.secret }); const { packet, secret } = packageInterface.packetDecoder
? packageInterface.packetDecoder(msg)
: this.defaultDecoder(msg);
if (!packet) {
packageInterface.log('Unable to parse packet from message.');
return undefined;
}
if (packet.code !== 'Access-Request') { if (packet.code !== 'Access-Request') {
console.error('unknown packet type: ', packet.code); packageInterface.log('unknown packet type: ', packet.code);
return undefined; return undefined;
} }
@ -33,7 +52,7 @@ export class RadiusService {
data: radius.encode_response({ data: radius.encode_response({
packet, packet,
code: response.code, code: response.code,
secret: this.secret, secret,
attributes: response.attributes, attributes: response.attributes,
}), }),
// if message is accept or reject, we conside this as final message // if message is accept or reject, we conside this as final message

View File

@ -1,13 +1,14 @@
// https://tools.ietf.org/html/rfc3748#section-4.1 // https://tools.ietf.org/html/rfc3748#section-4.1
import * as NodeCache from 'node-cache'; import * as NodeCache from 'node-cache';
import debug from 'debug';
import { makeid } from '../../helpers'; import { makeid } from '../../helpers';
import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler'; import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler';
import { IEAPMethod } from '../../types/EAPMethod'; import { IEAPMethod } from '../../types/EAPMethod';
import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper'; import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper';
import PackageInterface from '../../interface';
const log = debug('radius:eap'); const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
export class EAPPacketHandler implements IPacketHandler { export class EAPPacketHandler implements IPacketHandler {
private identities = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds private identities = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds
@ -66,15 +67,15 @@ export class EAPPacketHandler implements IPacketHandler {
return buildEAPResponse(identifier, 3); // NAK return buildEAPResponse(identifier, 3); // NAK
case 2: // notification case 2: // notification
log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {}); log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
console.info('notification'); log('notification');
break; break;
case 4: // md5-challenge case 4: // md5-challenge
log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {}); log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {});
console.info('md5-challenge'); log('md5-challenge');
break; break;
case 254: // expanded type case 254: // expanded type
console.error('not implemented type', type); log('not implemented type', type);
break; break;
case 3: // nak case 3: // nak
// console.log('got NAK', data); // console.log('got NAK', data);
@ -118,7 +119,7 @@ export class EAPPacketHandler implements IPacketHandler {
method.getEAPType() method.getEAPType()
); );
console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`); log('unsupported type', type, `requesting: ${serverSupportedMethods}`);
return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods)); return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods));
} }
@ -135,7 +136,7 @@ export class EAPPacketHandler implements IPacketHandler {
// silently ignore; // silently ignore;
return {}; return {};
} catch (err) { } catch (err) {
console.error( log(
'decoding of (generic) EAP package failed', 'decoding of (generic) EAP package failed',
msg, msg,
err, err,

View File

@ -1,4 +1,3 @@
import debug from 'debug';
import { IAuthentication } from '../../types/Authentication'; import { IAuthentication } from '../../types/Authentication';
import { import {
IPacket, IPacket,
@ -6,8 +5,10 @@ import {
IPacketHandlerResult, IPacketHandlerResult,
PacketResponseCode, PacketResponseCode,
} from '../../types/PacketHandler'; } from '../../types/PacketHandler';
import PackageInterface from '../../interface';
const log = debug('radius:user-pwd'); const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
export class UserPasswordPacketHandler implements IPacketHandler { export class UserPasswordPacketHandler implements IPacketHandler {
constructor(private authentication: IAuthentication) {} constructor(private authentication: IAuthentication) {}
@ -29,10 +30,11 @@ export class UserPasswordPacketHandler implements IPacketHandler {
log('username', username, username.toString()); log('username', username, username.toString());
log('token', password, password.toString()); log('token', password, password.toString());
const authenticated = await this.authentication.authenticate( const [strUsername, strPassword] = packet.credentialMiddleware
username.toString(), ? packet.credentialMiddleware(username.toString(), password.toString())
password.toString() : [username.toString(), password.toString()];
);
const authenticated = await this.authentication.authenticate(strUsername, strPassword);
if (authenticated) { if (authenticated) {
// success // success
return { return {

View File

@ -1,13 +1,14 @@
// 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 debug from 'debug';
import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler'; import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler';
import { IEAPMethod } from '../../../../types/EAPMethod'; import { IEAPMethod } from '../../../../types/EAPMethod';
import { IAuthentication } from '../../../../types/Authentication'; import { IAuthentication } from '../../../../types/Authentication';
import { buildEAPResponse, decodeEAPHeader } from '../EAPHelper'; import { buildEAPResponse, decodeEAPHeader } from '../EAPHelper';
import PackageInterface from '../../../../interface';
const log = debug('radius:eap:gtc'); const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
export class EAPGTC implements IEAPMethod { export class EAPGTC implements IEAPMethod {
getEAPType(): number { getEAPType(): number {
@ -56,7 +57,7 @@ export class EAPGTC implements IEAPMethod {
attributes: (success && [['User-Name', username]]) || undefined, attributes: (success && [['User-Name', username]]) || undefined,
}; };
} catch (err) { } catch (err) {
console.error('decoding of EAP-GTC package failed', msg, err); log('decoding of EAP-GTC package failed', msg, err);
return { return {
code: PacketResponseCode.AccessReject, code: PacketResponseCode.AccessReject,
}; };

View File

@ -2,10 +2,12 @@
// 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 { RadiusPacket } from 'radius'; import { RadiusPacket } from 'radius';
import debug from 'debug';
import { IPacketHandlerResult } from '../../../../types/PacketHandler'; import { IPacketHandlerResult } from '../../../../types/PacketHandler';
import { IEAPMethod } from '../../../../types/EAPMethod'; import { IEAPMethod } from '../../../../types/EAPMethod';
import { IAuthentication } from '../../../../types/Authentication'; import { IAuthentication } from '../../../../types/Authentication';
import PackageInterface from '../../../../interface';
const packageInterface = PackageInterface.get();
export class EAPMD5 implements IEAPMethod { export class EAPMD5 implements IEAPMethod {
getEAPType(): number { getEAPType(): number {
@ -27,7 +29,7 @@ export class EAPMD5 implements IEAPMethod {
): Promise<IPacketHandlerResult> { ): Promise<IPacketHandlerResult> {
// not implemented // not implemented
debug('eap md5 not implemented...'); packageInterface.log('eap md5 not implemented...');
return {}; return {};
} }

View File

@ -6,8 +6,6 @@ import * as NodeCache from 'node-cache';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import { attr_id_to_name, attr_name_to_id } from 'radius'; import { attr_id_to_name, attr_name_to_id } from 'radius';
import debug from 'debug';
import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt'; import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt';
import { import {
IPacket, IPacket,
@ -20,8 +18,10 @@ import { MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from '../../../../helpe
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 PackageInterface from '../../../../interface';
const log = debug('radius:eap:ttls'); const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
function tlsHasExportKeyingMaterial(tlsSocket): tlsSocket is { function tlsHasExportKeyingMaterial(tlsSocket): tlsSocket is {
exportKeyingMaterial: (length: number, label: string, context?: Buffer) => Buffer; exportKeyingMaterial: (length: number, label: string, context?: Buffer) => Buffer;
@ -276,7 +276,7 @@ export class EAPTTLS implements IEAPMethod {
[[17, encodeTunnelPW(keyingMaterial.slice(0, 64), packet.authenticator, secret)]], [[17, encodeTunnelPW(keyingMaterial.slice(0, 64), packet.authenticator, secret)]],
]); // MS-MPPE-Recv-Key ]); // MS-MPPE-Recv-Key
} else { } else {
console.error( log(
'FATAL: no exportKeyingMaterial method available!!!, you need latest NODE JS, see https://github.com/nodejs/node/pull/31814' 'FATAL: no exportKeyingMaterial method available!!!, you need latest NODE JS, see https://github.com/nodejs/node/pull/31814'
); );
} }
@ -420,7 +420,7 @@ 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);
} catch (err) { } catch (err) {
console.error('decoding of EAP-TTLS package failed', msg, err); log('decoding of EAP-TTLS package failed', msg, err);
return { return {
code: PacketResponseCode.AccessReject, code: PacketResponseCode.AccessReject,
}; };

View File

@ -4,6 +4,9 @@ import * as events from 'events';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { newDeferredPromise } from '../helpers'; import { newDeferredPromise } from '../helpers';
import { IServer } from '../types/Server'; import { IServer } from '../types/Server';
import PackageInterface from '../interface';
const packageInterface = PackageInterface.get();
export class UDPServer extends events.EventEmitter implements IServer { export class UDPServer extends events.EventEmitter implements IServer {
static MAX_RETRIES = 3; static MAX_RETRIES = 3;
@ -53,7 +56,7 @@ export class UDPServer extends events.EventEmitter implements IServer {
const startServer = newDeferredPromise(); const startServer = newDeferredPromise();
this.server.on('listening', () => { this.server.on('listening', () => {
const address = this.server.address(); const address = this.server.address();
console.log(`radius server listening ${address.address}:${address.port}`); packageInterface.log(`radius server listening ${address.address}:${address.port}`);
this.setupListeners(); this.setupListeners();
startServer.resolve(); startServer.resolve();
@ -72,6 +75,12 @@ export class UDPServer extends events.EventEmitter implements IServer {
return startServer.promise; return startServer.promise;
} }
stop(): Promise<void> {
return new Promise<void>((res) => {
this.server.close(() => res());
});
}
private setupListeners() { private setupListeners() {
this.server.on('message', (message, rinfo) => this.emit('message', message, rinfo)); this.server.on('message', (message, rinfo) => this.emit('message', message, rinfo));
} }

View File

@ -3,33 +3,36 @@ import * as tls from 'tls';
import { createSecureContext } from 'tls'; import { createSecureContext } from 'tls';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import * as DuplexPair from 'native-duplexpair'; import * as DuplexPair from 'native-duplexpair';
import debug from 'debug';
import * as NodeCache from 'node-cache'; import * as NodeCache from 'node-cache';
// import * as constants from 'constants'; // import * as constants from 'constants';
import * as config from '../../config'; import PackageInterface from '../interface';
const log = debug('radius:tls'); const packageInterface = PackageInterface.get();
const log = (...args) => packageInterface.log(...args);
// https://nodejs.org/api/tls.html // https://nodejs.org/api/tls.html
export function getTLSSecureContext(): tls.SecureContext {
const tlsOptions: tls.SecureContextOptions = { const tlsOptions: tls.SecureContextOptions = {
...config.certificate, ...packageInterface.getConfig().certificate,
}; };
log('tlsOptions', tlsOptions);
const secureContext = createSecureContext(tlsOptions); return createSecureContext(tlsOptions);
}
export interface ITLSServer { export interface ITLSServer {
events: events.EventEmitter; events: events.EventEmitter;
tls: tls.TLSSocket; tls: tls.TLSSocket;
} }
const resumeSessions = new NodeCache({ stdTTL: 86400 }); // session reidentification maximum 1 day const resumeSessions = new NodeCache({ stdTTL: packageInterface.cacheTTL }); // session reidentification maximum 1 day
export function startTLSServer(): ITLSServer { export function startTLSServer(): ITLSServer {
const duplexpair = new DuplexPair(); const duplexpair = new DuplexPair();
const emitter = new events.EventEmitter(); const emitter = new events.EventEmitter();
const cleartext = new tls.TLSSocket(duplexpair.socket1, { const cleartext = new tls.TLSSocket(duplexpair.socket1, {
secureContext, secureContext: getTLSSecureContext(),
isServer: true, isServer: true,
// enableTrace: true, // enableTrace: true,
rejectUnauthorized: false, rejectUnauthorized: false,

View File

@ -16,6 +16,7 @@ export interface IPacketAttributes {
export interface IPacket { export interface IPacket {
attributes: { [key: string]: string | Buffer }; attributes: { [key: string]: string | Buffer };
authenticator?: Buffer; authenticator?: Buffer;
credentialMiddleware?: (username: string, password: string) => [string, string];
} }
export interface IPacketHandler { export interface IPacketHandler {