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,39 +6,44 @@ 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';
/* test node version */ const packageInterface = PackageInterface.get();
const testSocket = startTLSServer();
if (typeof testSocket.tls.exportKeyingMaterial !== 'function') {
console.error(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`);
console.log('min version supported is node js 14. run "sudo npx n 14"'); const prestartServer = () => {
process.exit(-1); /* test node version */
} const testSocket = startTLSServer();
if (typeof testSocket.tls.exportKeyingMaterial !== 'function') {
packageInterface.log(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`);
const { argv } = yargs packageInterface.log('min version supported is node js 14. run "sudo npx n 14"');
.usage('NODE RADIUS Server\nUsage: radius-server') process.exit(-1);
.example('radius-server --port 1812 -s radiussecret', 'start on port 1812 with a secret') }
.default({
port: config.port || 1812, const { argv } = yargs
s: config.secret || 'testing123', .usage('NODE RADIUS Server\nUsage: radius-server')
authentication: config.authentication, .example('radius-server --port 1812 -s radiussecret', 'start on port 1812 with a secret')
authenticationOptions: config.authenticationOptions, .default({
}) port: config.port || 1812,
.describe('port', 'RADIUS server listener port') s: config.secret || 'testing123',
.alias('s', 'secret') authentication: config.authentication,
.describe('secret', 'RADIUS secret') authenticationOptions: config.authenticationOptions,
.number('port') })
.string(['secret', 'authentication']) as { .describe('port', 'RADIUS server listener port')
argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any }; .alias('s', 'secret')
.describe('secret', 'RADIUS secret')
.number('port')
.string(['secret', 'authentication']) as {
argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any };
};
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)}`);
}; };
console.log(`Listener Port: ${argv.port || 1812}`); const startServer = async () => {
console.log(`RADIUS Secret: ${argv.secret}`);
console.log(`Auth ${argv.authentication}`);
console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined, 3)}`);
(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
const tlsOptions: tls.SecureContextOptions = { export function getTLSSecureContext(): tls.SecureContext {
...config.certificate, const tlsOptions: tls.SecureContextOptions = {
}; ...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,
@ -133,14 +136,14 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
// https://tools.ietf.org/html/rfc2548 // https://tools.ietf.org/html/rfc2548
/** /**
* Salt * Salt
The Salt field is two octets in length and is used to ensure the 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 uniqueness of the keys used to encrypt each of the encrypted
attributes occurring in a given Access-Accept packet. The most attributes occurring in a given Access-Accept packet. The most
significant bit (leftmost) of the Salt field MUST be set (1). The significant bit (leftmost) of the Salt field MUST be set (1). The
contents of each Salt field in a given Access-Accept packet MUST contents of each Salt field in a given Access-Accept packet MUST
be unique. be unique.
*/ */
const salt = crypto.randomBytes(2); const salt = crypto.randomBytes(2);
// eslint-disable-next-line no-bitwise // 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 is performed in the following manner ('+' indicates
concatenation): concatenation):
Zorn Informational [Page 21] Zorn Informational [Page 21]
RFC 2548 Microsoft Vendor-specific RADIUS Attributes March 1999 RFC 2548 Microsoft Vendor-specific RADIUS Attributes March 1999

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 {