From 78c57d77473c311286fa6d5b3c44c8793536ffaa Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sun, 24 Oct 2021 15:37:16 -0500 Subject: [PATCH] Initial CoreID changes to allow code-based integration --- .gitignore | 2 + LICENSE | 39 ++++++++++ package.json | 6 +- src/app.ts | 72 +++++++++++-------- src/auth.ts | 18 +++-- src/auth/GoogleLDAPAuth.ts | 14 ++-- src/auth/HTTPAuth.ts | 6 +- src/auth/IMAPAuth.ts | 8 ++- src/auth/LDAPAuth.ts | 8 ++- src/auth/SMTPAuth.ts | 7 +- src/interface.ts | 47 ++++++++++++ src/radius/RadiusService.ts | 27 +++++-- src/radius/handler/EAPPacketHandler.ts | 15 ++-- .../handler/UserPasswordPacketHandler.ts | 14 ++-- src/radius/handler/eap/eapMethods/EAP-GTC.ts | 7 +- src/radius/handler/eap/eapMethods/EAP-MD5.ts | 6 +- src/radius/handler/eap/eapMethods/EAP-TTLS.ts | 10 +-- src/server/UDPServer.ts | 11 ++- src/tls/crypt.ts | 41 ++++++----- src/types/PacketHandler.ts | 1 + 20 files changed, 261 insertions(+), 98 deletions(-) create mode 100644 LICENSE create mode 100644 src/interface.ts diff --git a/.gitignore b/.gitignore index 819fd6c..ddb1729 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ tsconfig.tsbuildinfo # custom certificates /ssl-*/ + +.idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..82a885f --- /dev/null +++ b/LICENSE @@ -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 . + +-- + +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 diff --git a/package.json b/package.json index 5e6eb74..a2f3299 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "radius-server", - "description": "radius server for google LDAP and TTLS", + "name": "@coreid/radius-server", + "description": "CoreID fork of radius server for google LDAP and TTLS", "version": "1.2.1", "engines": { "node": ">13.10.1" @@ -14,7 +14,7 @@ "dist", "ssl" ], - "homepage": "https://github.com/simllll/node-radius-server", + "homepage": "https://code.garrettmills.dev/starship/node-radius-server", "scripts": { "release": "npm run build && standard-version", "debug": "DEBUG=radius:* node --tls-min-v1.0 dist/app.js", diff --git a/src/app.ts b/src/app.ts index 2ee2672..1c59e0d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,39 +6,44 @@ import * as config from '../config'; import { Authentication } from './auth'; import { IAuthentication } from './types/Authentication'; import { startTLSServer } from './tls/crypt'; +import PackageInterface from './interface'; -/* test node version */ -const testSocket = startTLSServer(); -if (typeof testSocket.tls.exportKeyingMaterial !== 'function') { - console.error(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`); +const packageInterface = PackageInterface.get(); - console.log('min version supported is node js 14. run "sudo npx n 14"'); - process.exit(-1); -} +const prestartServer = () => { + /* test node version */ + const testSocket = startTLSServer(); + if (typeof testSocket.tls.exportKeyingMaterial !== 'function') { + packageInterface.log(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`); -const { argv } = yargs - .usage('NODE RADIUS Server\nUsage: radius-server') - .example('radius-server --port 1812 -s radiussecret', 'start on port 1812 with a secret') - .default({ - port: config.port || 1812, - s: config.secret || 'testing123', - authentication: config.authentication, - authenticationOptions: config.authenticationOptions, - }) - .describe('port', 'RADIUS server listener port') - .alias('s', 'secret') - .describe('secret', 'RADIUS secret') - .number('port') - .string(['secret', 'authentication']) as { - argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any }; -}; + packageInterface.log('min version supported is node js 14. run "sudo npx n 14"'); + process.exit(-1); + } + + const { argv } = yargs + .usage('NODE RADIUS Server\nUsage: radius-server') + .example('radius-server --port 1812 -s radiussecret', 'start on port 1812 with a secret') + .default({ + port: config.port || 1812, + s: config.secret || 'testing123', + authentication: config.authentication, + authenticationOptions: config.authenticationOptions, + }) + .describe('port', 'RADIUS server listener port') + .alias('s', 'secret') + .describe('secret', 'RADIUS secret') + .number('port') + .string(['secret', 'authentication']) as { + argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any }; + }; -console.log(`Listener Port: ${argv.port || 1812}`); -console.log(`RADIUS Secret: ${argv.secret}`); -console.log(`Auth ${argv.authentication}`); -console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined, 3)}`); + packageInterface.log(`Listener Port: ${argv.port || 1812}`); + packageInterface.log(`RADIUS Secret: ${argv.secret}`); + packageInterface.log(`Auth ${argv.authentication}`); + packageInterface.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined, 3)}`); +}; -(async () => { +const startServer = async () => { /* configure auth mechansim */ let auth: IAuthentication; try { @@ -47,7 +52,7 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined ]; auth = new AuthMechanismus(config.authenticationOptions); } catch (err) { - console.error('cannot load auth mechanismus', config.authentication); + packageInterface.log('cannot load auth mechanismus', config.authentication); throw err; } // start radius server @@ -66,7 +71,7 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined rinfo.address, (err, _bytes) => { if (err) { - console.log('Error sending response to ', rinfo); + packageInterface.log('Error sending response to ', rinfo); } }, response.expectAcknowledgment @@ -76,4 +81,9 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined // start server await server.start(); -})(); +}; + +if (packageInterface.start) { + prestartServer(); + startServer(); +} diff --git a/src/auth.ts b/src/auth.ts index 3baf804..5fb2ba0 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,8 +1,11 @@ import * as NodeCache from 'node-cache'; import { Cache, ExpirationStrategy, MemoryStorage } from '@hokify/node-ts-cache'; import { IAuthentication } from './types/Authentication'; +import PackageInterface from './interface'; const cacheStrategy = new ExpirationStrategy(new MemoryStorage()); +const packageInterface = PackageInterface.get(); + /** * this is just a simple abstraction to provide * an application layer for caching credentials @@ -12,18 +15,25 @@ export class Authentication implements IAuthentication { constructor(private authenticator: IAuthentication) {} - @Cache(cacheStrategy, { ttl: 60000 }) + @Cache(cacheStrategy, { ttl: packageInterface.cacheTTL }) async authenticate(username: string, password: string): Promise { const cacheKey = `usr:${username}|pwd:${password}`; const fromCache = this.cache.get(cacheKey) as undefined | boolean; 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; } const authResult = await this.authenticator.authenticate(username, password); - console.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 + packageInterface.log(`Auth Result for user ${username}`, authResult ? 'SUCCESS' : 'Failure'); + this.cache.set( + cacheKey, + authResult, + authResult ? packageInterface.cacheSuccessTTL : packageInterface.cacheFailTTL + ); // cache for one day on success, otherwise just for 60 seconds return authResult; } diff --git a/src/auth/GoogleLDAPAuth.ts b/src/auth/GoogleLDAPAuth.ts index 97702aa..367b6be 100644 --- a/src/auth/GoogleLDAPAuth.ts +++ b/src/auth/GoogleLDAPAuth.ts @@ -1,12 +1,14 @@ import { ClientOptions, createClient } from 'ldapjs'; -import debug from 'debug'; import * as tls from 'tls'; import * as fs from 'fs'; import { IAuthentication } from '../types/Authentication'; +import PackageInterface from '../interface'; + +const packageInterface = PackageInterface.get(); +const log = (...args) => packageInterface.log(...args); const usernameFields = ['posixUid', 'mail']; -const log = debug('radius:auth:google-ldap'); // TLS: // https://github.com/ldapjs/node-ldapjs/issues/307 @@ -55,7 +57,7 @@ export class GoogleLDAPAuth implements IAuthentication { }; 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((resolve, reject) => { const ldapDNClient = createClient(this.config).on('error', (error) => { - console.error('Error in ldap', error); + log('Error in ldap', error); reject(error); }); @@ -92,7 +94,7 @@ export class GoogleLDAPAuth implements IAuthentication { }); res.on('error', (ldapErr) => { - console.error(`error: ${JSON.stringify(ldapErr)}`); + log(`error: ${JSON.stringify(ldapErr)}`); reject(ldapErr); }); @@ -144,7 +146,7 @@ export class GoogleLDAPAuth implements IAuthentication { return this.authenticate(username, password, count, true); } // 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; } diff --git a/src/auth/HTTPAuth.ts b/src/auth/HTTPAuth.ts index 247acf3..18806c3 100644 --- a/src/auth/HTTPAuth.ts +++ b/src/auth/HTTPAuth.ts @@ -1,5 +1,9 @@ import axios from 'axios'; import { IAuthentication } from '../types/Authentication'; +import PackageInterface from '../interface'; + +const packageInterface = PackageInterface.get(); +const log = (...args) => packageInterface.log(...args); interface IHTTPAuthOptions { url: string; @@ -30,7 +34,7 @@ export class HTTPAuth implements IAuthentication { return true; } - console.log(`HTTP authentication failed, response code: ${result.status}`); + log(`HTTP authentication failed, response code: ${result.status}`); return false; } diff --git a/src/auth/IMAPAuth.ts b/src/auth/IMAPAuth.ts index f315b19..a2f2ae8 100644 --- a/src/auth/IMAPAuth.ts +++ b/src/auth/IMAPAuth.ts @@ -1,5 +1,9 @@ import * as imaps from 'imap-simple'; import { IAuthentication } from '../types/Authentication'; +import PackageInterface from '../interface'; + +const packageInterface = PackageInterface.get(); +const log = (...args) => packageInterface.log(...args); interface IIMAPAuthOptions { host: string; @@ -34,7 +38,7 @@ export class IMAPAuth implements IAuthentication { if (this.validHosts) { const domain = username.split('@').pop(); 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; } } @@ -57,7 +61,7 @@ export class IMAPAuth implements IAuthentication { connection.end(); } catch (err) { - console.error('imap auth failed', err); + log('imap auth failed', err); } return success; } diff --git a/src/auth/LDAPAuth.ts b/src/auth/LDAPAuth.ts index d032496..1daac1c 100644 --- a/src/auth/LDAPAuth.ts +++ b/src/auth/LDAPAuth.ts @@ -1,6 +1,10 @@ import * as LdapAuth from 'ldapauth-fork'; import * as fs from 'fs'; import { IAuthentication } from '../types/Authentication'; +import PackageInterface from '../interface'; + +const packageInterface = PackageInterface.get(); +const log = (...args) => packageInterface.log(...args); interface ILDAPAuthOptions { /** ldap url @@ -44,7 +48,7 @@ export class LDAPAuth implements IAuthentication { reconnect: true, }); 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) => { if (err) { resolve(false); - console.error('ldap error', err); + log('ldap error', err); // reject(err); } if (user) resolve(user); diff --git a/src/auth/SMTPAuth.ts b/src/auth/SMTPAuth.ts index 93a3330..d68db40 100644 --- a/src/auth/SMTPAuth.ts +++ b/src/auth/SMTPAuth.ts @@ -1,5 +1,8 @@ import { SMTPClient } from 'smtp-client'; import { IAuthentication } from '../types/Authentication'; +import PackageInterface from '../interface'; + +const log = (...args) => PackageInterface.get().log(...args); interface ISMTPAuthOptions { host: string; @@ -37,7 +40,7 @@ export class SMTPAuth implements IAuthentication { if (this.validHosts) { const domain = username.split('@').pop(); 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; } } @@ -61,7 +64,7 @@ export class SMTPAuth implements IAuthentication { s.close(); // runs QUIT command } catch (err) { - console.error('imap auth failed', err); + log('imap auth failed', err); } return success; } diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 0000000..e632e02 --- /dev/null +++ b/src/interface.ts @@ -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; + } +} diff --git a/src/radius/RadiusService.ts b/src/radius/RadiusService.ts index 1b0f28e..9ddfaf3 100644 --- a/src/radius/RadiusService.ts +++ b/src/radius/RadiusService.ts @@ -1,8 +1,11 @@ import * as radius from 'radius'; import { IAuthentication } from '../types/Authentication'; -import { IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler'; +import {IPacket, IPacketHandlerResult, PacketResponseCode} from '../types/PacketHandler'; import { PacketHandler } from './PacketHandler'; +import PackageInterface from '../interface'; + +const packageInterface = PackageInterface.get(); export class RadiusService { private packetHandler: PacketHandler; @@ -11,13 +14,29 @@ export class RadiusService { 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( msg: Buffer ): 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') { - console.error('unknown packet type: ', packet.code); + packageInterface.log('unknown packet type: ', packet.code); return undefined; } @@ -33,7 +52,7 @@ export class RadiusService { data: radius.encode_response({ packet, code: response.code, - secret: this.secret, + secret, attributes: response.attributes, }), // if message is accept or reject, we conside this as final message diff --git a/src/radius/handler/EAPPacketHandler.ts b/src/radius/handler/EAPPacketHandler.ts index 164515f..64c4230 100644 --- a/src/radius/handler/EAPPacketHandler.ts +++ b/src/radius/handler/EAPPacketHandler.ts @@ -1,13 +1,14 @@ // https://tools.ietf.org/html/rfc3748#section-4.1 import * as NodeCache from 'node-cache'; -import debug from 'debug'; import { makeid } from '../../helpers'; import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler'; import { IEAPMethod } from '../../types/EAPMethod'; 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 { 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 case 2: // notification log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {}); - console.info('notification'); + log('notification'); break; case 4: // md5-challenge log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {}); - console.info('md5-challenge'); + log('md5-challenge'); break; case 254: // expanded type - console.error('not implemented type', type); + log('not implemented type', type); break; case 3: // nak // console.log('got NAK', data); @@ -118,7 +119,7 @@ export class EAPPacketHandler implements IPacketHandler { method.getEAPType() ); - console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`); + log('unsupported type', type, `requesting: ${serverSupportedMethods}`); return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods)); } @@ -135,7 +136,7 @@ export class EAPPacketHandler implements IPacketHandler { // silently ignore; return {}; } catch (err) { - console.error( + log( 'decoding of (generic) EAP package failed', msg, err, diff --git a/src/radius/handler/UserPasswordPacketHandler.ts b/src/radius/handler/UserPasswordPacketHandler.ts index 9abb4d2..746fb5c 100644 --- a/src/radius/handler/UserPasswordPacketHandler.ts +++ b/src/radius/handler/UserPasswordPacketHandler.ts @@ -1,4 +1,3 @@ -import debug from 'debug'; import { IAuthentication } from '../../types/Authentication'; import { IPacket, @@ -6,8 +5,10 @@ import { IPacketHandlerResult, PacketResponseCode, } 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 { constructor(private authentication: IAuthentication) {} @@ -29,10 +30,11 @@ export class UserPasswordPacketHandler implements IPacketHandler { log('username', username, username.toString()); log('token', password, password.toString()); - const authenticated = await this.authentication.authenticate( - username.toString(), - password.toString() - ); + const [strUsername, strPassword] = packet.credentialMiddleware + ? packet.credentialMiddleware(username.toString(), password.toString()) + : [username.toString(), password.toString()]; + + const authenticated = await this.authentication.authenticate(strUsername, strPassword); if (authenticated) { // success return { diff --git a/src/radius/handler/eap/eapMethods/EAP-GTC.ts b/src/radius/handler/eap/eapMethods/EAP-GTC.ts index 8f64084..4aacd51 100644 --- a/src/radius/handler/eap/eapMethods/EAP-GTC.ts +++ b/src/radius/handler/eap/eapMethods/EAP-GTC.ts @@ -1,13 +1,14 @@ // 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 debug from 'debug'; import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler'; import { IEAPMethod } from '../../../../types/EAPMethod'; import { IAuthentication } from '../../../../types/Authentication'; 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 { getEAPType(): number { @@ -56,7 +57,7 @@ export class EAPGTC implements IEAPMethod { attributes: (success && [['User-Name', username]]) || undefined, }; } catch (err) { - console.error('decoding of EAP-GTC package failed', msg, err); + log('decoding of EAP-GTC package failed', msg, err); return { code: PacketResponseCode.AccessReject, }; diff --git a/src/radius/handler/eap/eapMethods/EAP-MD5.ts b/src/radius/handler/eap/eapMethods/EAP-MD5.ts index db4c1ef..8e653c8 100644 --- a/src/radius/handler/eap/eapMethods/EAP-MD5.ts +++ b/src/radius/handler/eap/eapMethods/EAP-MD5.ts @@ -2,10 +2,12 @@ // 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 { IPacketHandlerResult } from '../../../../types/PacketHandler'; import { IEAPMethod } from '../../../../types/EAPMethod'; import { IAuthentication } from '../../../../types/Authentication'; +import PackageInterface from '../../../../interface'; + +const packageInterface = PackageInterface.get(); export class EAPMD5 implements IEAPMethod { getEAPType(): number { @@ -27,7 +29,7 @@ export class EAPMD5 implements IEAPMethod { ): Promise { // not implemented - debug('eap md5 not implemented...'); + packageInterface.log('eap md5 not implemented...'); return {}; } diff --git a/src/radius/handler/eap/eapMethods/EAP-TTLS.ts b/src/radius/handler/eap/eapMethods/EAP-TTLS.ts index 31aed43..ab90ab3 100644 --- a/src/radius/handler/eap/eapMethods/EAP-TTLS.ts +++ b/src/radius/handler/eap/eapMethods/EAP-TTLS.ts @@ -6,8 +6,6 @@ import * as NodeCache from 'node-cache'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @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 { IPacket, @@ -20,8 +18,10 @@ import { MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from '../../../../helpe import { IEAPMethod } from '../../../../types/EAPMethod'; import { IAuthentication } from '../../../../types/Authentication'; 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 { 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)]], ]); // MS-MPPE-Recv-Key } else { - console.error( + log( '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 return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData); } catch (err) { - console.error('decoding of EAP-TTLS package failed', msg, err); + log('decoding of EAP-TTLS package failed', msg, err); return { code: PacketResponseCode.AccessReject, }; diff --git a/src/server/UDPServer.ts b/src/server/UDPServer.ts index 7767a64..d0a131e 100644 --- a/src/server/UDPServer.ts +++ b/src/server/UDPServer.ts @@ -4,6 +4,9 @@ import * as events from 'events'; import { EventEmitter } from 'events'; import { newDeferredPromise } from '../helpers'; import { IServer } from '../types/Server'; +import PackageInterface from '../interface'; + +const packageInterface = PackageInterface.get(); export class UDPServer extends events.EventEmitter implements IServer { static MAX_RETRIES = 3; @@ -53,7 +56,7 @@ export class UDPServer extends events.EventEmitter implements IServer { const startServer = newDeferredPromise(); this.server.on('listening', () => { 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(); startServer.resolve(); @@ -72,6 +75,12 @@ export class UDPServer extends events.EventEmitter implements IServer { return startServer.promise; } + stop(): Promise { + return new Promise((res) => { + this.server.close(() => res()); + }); + } + private setupListeners() { this.server.on('message', (message, rinfo) => this.emit('message', message, rinfo)); } diff --git a/src/tls/crypt.ts b/src/tls/crypt.ts index 6c69942..653f09f 100644 --- a/src/tls/crypt.ts +++ b/src/tls/crypt.ts @@ -3,33 +3,36 @@ import * as tls from 'tls'; import { createSecureContext } from 'tls'; import * as crypto from 'crypto'; import * as DuplexPair from 'native-duplexpair'; -import debug from 'debug'; import * as NodeCache from 'node-cache'; // 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 -const tlsOptions: tls.SecureContextOptions = { - ...config.certificate, -}; -log('tlsOptions', tlsOptions); -const secureContext = createSecureContext(tlsOptions); +export function getTLSSecureContext(): tls.SecureContext { + const tlsOptions: tls.SecureContextOptions = { + ...packageInterface.getConfig().certificate, + }; + + return createSecureContext(tlsOptions); +} export interface ITLSServer { events: events.EventEmitter; 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 { const duplexpair = new DuplexPair(); const emitter = new events.EventEmitter(); const cleartext = new tls.TLSSocket(duplexpair.socket1, { - secureContext, + secureContext: getTLSSecureContext(), isServer: true, // enableTrace: true, rejectUnauthorized: false, @@ -133,14 +136,14 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin // https://tools.ietf.org/html/rfc2548 /** - * Salt - The Salt field is two octets in length and is used to ensure the - uniqueness of the keys used to encrypt each of the encrypted - attributes occurring in a given Access-Accept packet. The most - significant bit (leftmost) of the Salt field MUST be set (1). The - contents of each Salt field in a given Access-Accept packet MUST - be unique. - */ + * Salt + The Salt field is two octets in length and is used to ensure the + uniqueness of the keys used to encrypt each of the encrypted + attributes occurring in a given Access-Accept packet. The most + significant bit (leftmost) of the Salt field MUST be set (1). The + contents of each Salt field in a given Access-Accept packet MUST + be unique. + */ const salt = crypto.randomBytes(2); // eslint-disable-next-line no-bitwise @@ -183,7 +186,7 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin is performed in the following manner ('+' indicates concatenation): - Zorn Informational [Page 21] + Zorn Informational [Page 21] RFC 2548 Microsoft Vendor-specific RADIUS Attributes March 1999 diff --git a/src/types/PacketHandler.ts b/src/types/PacketHandler.ts index 2945391..0eeb28f 100644 --- a/src/types/PacketHandler.ts +++ b/src/types/PacketHandler.ts @@ -16,6 +16,7 @@ export interface IPacketAttributes { export interface IPacket { attributes: { [key: string]: string | Buffer }; authenticator?: Buffer; + credentialMiddleware?: (username: string, password: string) => [string, string]; } export interface IPacketHandler {