initial commit
This commit is contained in:
commit
807e1508d7
41
README.md
Normal file
41
README.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Basic RADIUS Server for node.js for Google LDAP Service and WPA2 Enteprise WLAN Authentification.
|
||||||
|
* Only implements LDAP as Authentification Backend
|
||||||
|
* Only WPA TTLS implemented (as this is the only one that works with Google LDAP Service)
|
||||||
|
|
||||||
|
## Known Issues / Disclaimer
|
||||||
|
|
||||||
|
This is a first implementation draft, which is currently NOT WORKING:
|
||||||
|
|
||||||
|
There is still one major issue left to get things going:
|
||||||
|
https://github.com/nodejs/node/issues/31802
|
||||||
|
that's why it's currently not possible to calculate MS-MPPE-Send-Key and MS-MPPE-Recv-Key.
|
||||||
|
|
||||||
|
* PAP / CHAP RFC not found to implement this correctly
|
||||||
|
* Project needs more structure and interfaces to extend it more easily in the future (make a full radius server out of it ;)?)
|
||||||
|
* No package queuing or any kind of respsecting the MTU size
|
||||||
|
* a lot of bugs
|
||||||
|
|
||||||
|
CONTRIBUTIONS WELCOME!
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This app provides a radius server to authenticate against google's SLDAP service. To get this running
|
||||||
|
you need:
|
||||||
|
1.) Running LDAP Service (E.g. Google Suite Enterprise or Gloud Identity Premium)
|
||||||
|
2.) Use stunnel to connect to the LDAP service and connect this app to the stunnel (I didn't get the client ldap authentication working in here yet)
|
||||||
|
3.) Install a SSL certificate (e.g. self signed via npm run create-certificate)
|
||||||
|
4.) Install und build server: npm install && npm run build
|
||||||
|
5.) Start server node dist/app.ts --secret {RADIUS secret} --baseDN dc=hokify,dc=com
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You need to specify at least a radius password and the base DN for LDAP:
|
||||||
|
|
||||||
|
node dist/app.ts --secret {RADIUS secret} --baseDN dc=hokify,dc=com
|
||||||
|
|
BIN
eapol_test
Executable file
BIN
eapol_test
Executable file
Binary file not shown.
3
notes
Normal file
3
notes
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
https://github.com/retailnext/node-radius/issues/29
|
||||||
|
|
||||||
|
https://stackoverflow.com/questions/60232165/ssl-export-keying-material-in-node-js
|
3797
package-lock.json
generated
Normal file
3797
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "radius-server",
|
||||||
|
"description": "radius server for google LDAP and TTLT",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node dist/app.js",
|
||||||
|
"build": "tsc",
|
||||||
|
"dev": "ts-node src/app.ts",
|
||||||
|
"test-ttls-pap": "eapol_test -c ./ttls-pap.conf -s testing123",
|
||||||
|
"create-certificate": "sh ./ssl/create.sh && sh ./ssl/sign.sh"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"native-duplexpair": "^1.0.0",
|
||||||
|
"ldapjs": "^1.0.2",
|
||||||
|
"node-cache": "^5.1.0",
|
||||||
|
"radius": "~1.1.4",
|
||||||
|
"ts-node": "^8.6.2",
|
||||||
|
"type-cacheable": "^4.0.0",
|
||||||
|
"yargs": "~15.1.0",
|
||||||
|
"md5": "^2.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/ldapjs": "^1.0.5",
|
||||||
|
"@types/radius": "0.0.28",
|
||||||
|
"@hokify/eslint-config": "^0.2.2",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"typescript": "^3.7.5"
|
||||||
|
}
|
||||||
|
}
|
190
src/app.ts
Normal file
190
src/app.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import * as dgram from 'dgram';
|
||||||
|
import * as radius from 'radius';
|
||||||
|
// import * as dgram from "dgram";
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { EAPHandler } from './eap';
|
||||||
|
import { encodeTunnelPW } from './tls/crypt';
|
||||||
|
import { makeid } from './helpers';
|
||||||
|
|
||||||
|
import { LDAPAuth } from './ldap';
|
||||||
|
|
||||||
|
const server = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
// not used right now, using stunnel to connect to ldap
|
||||||
|
const tlsOptions = {
|
||||||
|
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
||||||
|
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt'),
|
||||||
|
|
||||||
|
// This is necessary only if using the client certificate authentication.
|
||||||
|
requestCert: true,
|
||||||
|
|
||||||
|
// This is necessary only if the client uses the self-signed certificate.
|
||||||
|
ca: [fs.readFileSync('ldap.gsuite.hokify.com.40567.key')]
|
||||||
|
};
|
||||||
|
|
||||||
|
const { argv } = require('yargs')
|
||||||
|
.usage('Simple Google LDAP <> RADIUS Server\nUsage: $0')
|
||||||
|
.example('$0 --port 1812 -s radiussecret')
|
||||||
|
.default({
|
||||||
|
port: 1812,
|
||||||
|
s: 'testing123',
|
||||||
|
baseDN: 'dc=hokify,dc=com',
|
||||||
|
ldapServer: 'ldap://127.0.0.1:1636'
|
||||||
|
})
|
||||||
|
.describe('baseDN', 'LDAP Base DN')
|
||||||
|
.describe('ldapServer', 'LDAP Server')
|
||||||
|
.describe('port', 'RADIUS server listener port')
|
||||||
|
.alias('s', 'secret')
|
||||||
|
.describe('secret', 'RADIUS secret')
|
||||||
|
.string(['secret', 'baseDN'])
|
||||||
|
.demand('secret');
|
||||||
|
|
||||||
|
console.log(`Listener Port: ${argv.port}`);
|
||||||
|
console.log(`RADIUS Secret: ${argv.secret}`);
|
||||||
|
console.log(`LDAP Base DN: ${argv.baseDN}`);
|
||||||
|
console.log(`LDAP Server: ${argv.ldapServer}`);
|
||||||
|
|
||||||
|
// const ldap = new LDAPAuth({url: 'ldap://ldap.google.com', base: 'dc=hokify,dc=com', uid: 'uid', tlsOptions});
|
||||||
|
|
||||||
|
const ldap = new LDAPAuth(argv.ldapServer, argv.baseDN);
|
||||||
|
|
||||||
|
const eapHandler = new EAPHandler();
|
||||||
|
|
||||||
|
server.on('message', async function(msg, rinfo) {
|
||||||
|
const packet = radius.decode({ packet: msg, secret: argv.secret });
|
||||||
|
|
||||||
|
if (packet.code !== 'Access-Request') {
|
||||||
|
console.log('unknown packet type: ', packet.code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('packet.attributes', packet.attributes);
|
||||||
|
|
||||||
|
// console.log('rinfo', rinfo);
|
||||||
|
|
||||||
|
async function checkAuth(username: string, password: string, EAPMessageIdentifier?: number) {
|
||||||
|
console.log(`Access-Request for ${username}`);
|
||||||
|
let code: 'Access-Accept' | 'Access-Reject';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ldap.authenticate(username, password);
|
||||||
|
code = 'Access-Accept';
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
code = 'Access-Reject';
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes: any[] = [];
|
||||||
|
if (EAPMessageIdentifier) {
|
||||||
|
const buffer = Buffer.from([
|
||||||
|
code === 'Access-Accept' ? 3 : 4, // 3.. success, 4... failure
|
||||||
|
EAPMessageIdentifier,
|
||||||
|
0, // length (1/2)
|
||||||
|
4 // length (2/2)
|
||||||
|
]);
|
||||||
|
|
||||||
|
attributes.push(['EAP-Message', buffer]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.attributes && packet.attributes['User-Name']) {
|
||||||
|
// reappend username to response
|
||||||
|
attributes.push(['User-Name', packet.attributes['User-Name']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (sess->eap_if->eapKeyDataLen > 64) {
|
||||||
|
len = 32;
|
||||||
|
} else {
|
||||||
|
len = sess->eap_if->eapKeyDataLen / 2;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// eapKeyData + len
|
||||||
|
attributes.push([
|
||||||
|
'Vendor-Specific',
|
||||||
|
311,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
16,
|
||||||
|
encodeTunnelPW(
|
||||||
|
(packet as any).authenticator,
|
||||||
|
packet.attributes['Message-Authenticator'],
|
||||||
|
argv.secret
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]); // MS-MPPE-Send-Key
|
||||||
|
|
||||||
|
// eapKeyData
|
||||||
|
attributes.push([
|
||||||
|
'Vendor-Specific',
|
||||||
|
311,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
17,
|
||||||
|
encodeTunnelPW(
|
||||||
|
(packet as any).authenticator,
|
||||||
|
packet.attributes['Message-Authenticator'],
|
||||||
|
argv.secret
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]); // MS-MPPE-Recv-Key
|
||||||
|
|
||||||
|
const response = radius.encode_response({
|
||||||
|
packet,
|
||||||
|
code,
|
||||||
|
secret: argv.secret,
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
console.log(`Sending ${code} for user ${username}`);
|
||||||
|
|
||||||
|
server.send(response, 0, response.length, rinfo.port, rinfo.address, function(err, _bytes) {
|
||||||
|
if (err) {
|
||||||
|
console.log('Error sending response to ', rinfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.attributes['EAP-Message']) {
|
||||||
|
const state = (packet.attributes.State && packet.attributes.State.toString()) || makeid(16);
|
||||||
|
// EAP MESSAGE
|
||||||
|
eapHandler.handleEAPMessage(packet.attributes['EAP-Message'], state, {
|
||||||
|
response: (EAPMessage: Buffer) => {
|
||||||
|
const attributes: any = [['State', Buffer.from(state)]];
|
||||||
|
let sentDataSize = 0;
|
||||||
|
do {
|
||||||
|
if (EAPMessage.length > 0) {
|
||||||
|
attributes.push(['EAP-Message', EAPMessage.slice(sentDataSize, sentDataSize + 253)]);
|
||||||
|
sentDataSize += 253;
|
||||||
|
}
|
||||||
|
} while (sentDataSize < EAPMessage.length);
|
||||||
|
|
||||||
|
const response = radius.encode_response({
|
||||||
|
packet,
|
||||||
|
code: 'Access-Challenge',
|
||||||
|
secret: argv.secret,
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
|
||||||
|
server.send(response, 0, response.length, rinfo.port, rinfo.address, function(err, _bytes) {
|
||||||
|
if (err) {
|
||||||
|
console.log('Error sending response to ', rinfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkAuth
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const username = packet.attributes['User-Name'];
|
||||||
|
const password = packet.attributes['User-Password'];
|
||||||
|
|
||||||
|
checkAuth(username, password);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('listening', function() {
|
||||||
|
const address = server.address();
|
||||||
|
console.log(`radius server listening ${address.address}:${address.port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.bind(argv.port);
|
203
src/eap.ts
Normal file
203
src/eap.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
// https://tools.ietf.org/html/rfc3748#section-4.1
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc5281 TTLS v0
|
||||||
|
// https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented)
|
||||||
|
import { IResponseHandlers, ResponseHandler } from './types/Handler';
|
||||||
|
import { EAPTTLS } from './eap/eap-ttls';
|
||||||
|
|
||||||
|
export class EAPHandler {
|
||||||
|
maxFragmentSize = 1400; // @todo .. take framed-mtu into account from AVPs
|
||||||
|
|
||||||
|
eapTTLS: EAPTTLS;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.eapTTLS = new EAPTTLS(this.sendEAPResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @param type 1 = identity, 21 = EAP-TTLS, 2 = notification, 4 = md5-challenge, 3 = NAK
|
||||||
|
*/
|
||||||
|
private sendEAPResponse(
|
||||||
|
response: ResponseHandler,
|
||||||
|
identifier: number,
|
||||||
|
data?: Buffer,
|
||||||
|
msgType = 21,
|
||||||
|
msgFlags = 0b00000000
|
||||||
|
) {
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const fragmentMaxPart =
|
||||||
|
data && (i + 1) * this.maxFragmentSize > data.length
|
||||||
|
? undefined
|
||||||
|
: (i + 1) * this.maxFragmentSize;
|
||||||
|
const sslPart = data && data.slice(i * this.maxFragmentSize, fragmentMaxPart);
|
||||||
|
|
||||||
|
const includeLength =
|
||||||
|
data &&
|
||||||
|
i === 0 &&
|
||||||
|
fragmentMaxPart !== undefined; /* firsrt one and we have more, therefore include length */
|
||||||
|
|
||||||
|
// console.log('includeLength', includeLength, fragmentMaxPart, i)
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
0 1 2 3 4 5 6 7 8
|
||||||
|
+-+-+-+-+-+-+-+-+
|
||||||
|
|L M R R R R R R|
|
||||||
|
+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
L = Length included
|
||||||
|
M = More fragments
|
||||||
|
R = Reserved
|
||||||
|
|
||||||
|
The L bit (length included) is set to indicate the presence of the
|
||||||
|
four-octet TLS Message Length field, and MUST be set for the first
|
||||||
|
fragment of a fragmented TLS message or set of messages. The M
|
||||||
|
bit (more fragments) is set on all but the last fragment.
|
||||||
|
Implementations of this specification MUST set the reserved bits
|
||||||
|
to zero, and MUST ignore them on reception.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const flags =
|
||||||
|
msgFlags +
|
||||||
|
(includeLength ? 0b10000000 : 0) + // set L bit
|
||||||
|
(fragmentMaxPart /* we have more */ ? 0b01000000 : 0); // set M bit
|
||||||
|
|
||||||
|
let buffer = Buffer.from([
|
||||||
|
1, // request
|
||||||
|
identifier + 1,
|
||||||
|
0, // length (1/2)
|
||||||
|
0, // length (2/2)
|
||||||
|
msgType, // 1 = identity, 21 = EAP-TTLS, 2 = notificaiton, 4 = md5-challenge, 3 = NAK
|
||||||
|
flags // flags: 000000 (L include lenghts, M .. more to come)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// append length
|
||||||
|
if (includeLength && data) {
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeInt32BE(data.byteLength, 0);
|
||||||
|
|
||||||
|
buffer = Buffer.concat([buffer, length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resBuffer = sslPart ? Buffer.concat([buffer, sslPart]) : buffer;
|
||||||
|
resBuffer.writeUInt16BE(resBuffer.byteLength, 2);
|
||||||
|
|
||||||
|
console.log('EAP RESPONSE', {
|
||||||
|
code: 1,
|
||||||
|
identifier: identifier + 1,
|
||||||
|
length: (includeLength && data && data.byteLength) || 0,
|
||||||
|
msgType,
|
||||||
|
flags,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
|
||||||
|
// uffer.from([1,identifier, 0, 0, 21, 0]);
|
||||||
|
// buffer.writeUInt16BE(sslResponse.length, 2); // length
|
||||||
|
// buffer.writeInt8(21, 4); // eap-ttls
|
||||||
|
// buffer.writeInt8(0, 5); // flags
|
||||||
|
|
||||||
|
/*
|
||||||
|
@todo: this is wrong,
|
||||||
|
if there are more messages, add them to a queue
|
||||||
|
and process the next one when client has ack. (message without data)
|
||||||
|
*/
|
||||||
|
response(resBuffer);
|
||||||
|
} while (data && i * this.maxFragmentSize < data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEAPMessage(msg: Buffer, state: string, handlers: IResponseHandlers) {
|
||||||
|
// const b = Buffer.from([2,0x242,0x0,0x18,0x1,0x115,0x105,0x109,0x111,0x110,0x46,0x116,0x114,0x101,0x116,0x116,0x101,0x114]);
|
||||||
|
// const msg = Buffer.from([2, 162, 0, 18, 1, 115, 105, 109, 111, 110, 46, 116, 114, 101, 116, 116, 101, 114])
|
||||||
|
|
||||||
|
/*
|
||||||
|
1 Request
|
||||||
|
2 Response
|
||||||
|
3 Success
|
||||||
|
4 Failure
|
||||||
|
*/
|
||||||
|
|
||||||
|
const code = msg.slice(0, 1).readUInt8(0);
|
||||||
|
const identifier = msg.slice(1, 2).readUInt8(0); // .toString('hex');
|
||||||
|
// const length = msg.slice(2, 4).readInt16BE(0); // .toString('binary');
|
||||||
|
const type = msg.slice(4, 5).readUInt8(0); // .slice(3,0x5).toString('hex');
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log("CODE", code);
|
||||||
|
console.log('ID', identifier);
|
||||||
|
console.log('length', length);
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 1: // for request
|
||||||
|
case 2: // for response
|
||||||
|
switch (type) {
|
||||||
|
case 1: // identifiy
|
||||||
|
/**
|
||||||
|
* The EAP-TTLS packet format is shown below. The fields are
|
||||||
|
transmitted left to right.
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Code | Identifier | Length |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Flags | Message Length
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
Message Length | Data...
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
|
||||||
|
console.log('RESPONDING WITH IDENTIDY / START');
|
||||||
|
|
||||||
|
this.sendEAPResponse(handlers.response, identifier, undefined, 21, 0x20);
|
||||||
|
|
||||||
|
/*
|
||||||
|
handlers.response(
|
||||||
|
Buffer.from([
|
||||||
|
1, // request
|
||||||
|
identifier + 1,
|
||||||
|
0, // length (1/2)
|
||||||
|
6, // length (2/2)
|
||||||
|
21, // EAP-TTLS
|
||||||
|
0x20 // flags: 001000 start flag
|
||||||
|
])
|
||||||
|
); */
|
||||||
|
break;
|
||||||
|
case 21: // EAP TTLS
|
||||||
|
this.eapTTLS.handleMessage(msg, state, handlers, identifier);
|
||||||
|
return;
|
||||||
|
case 3: // nak
|
||||||
|
this.sendEAPResponse(handlers.response, identifier, undefined, 3);
|
||||||
|
break;
|
||||||
|
case 2: // notification
|
||||||
|
console.info('notification');
|
||||||
|
break;
|
||||||
|
case 4: // md5-challenge
|
||||||
|
console.info('md5-challenge');
|
||||||
|
break;
|
||||||
|
case 254: // expanded type
|
||||||
|
console.error('not implemented type', type);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error('unsupported type', type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
console.log('Client Auth Success');
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
console.log('Client Auth FAILURE');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
// silently ignor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/eap/challenges/pap.ts
Normal file
44
src/eap/challenges/pap.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { IAuthChallenge } from '../../types/AuthChallenge';
|
||||||
|
|
||||||
|
export class PAPChallenge implements IAuthChallenge {
|
||||||
|
// i couldn't find any documentation about it, therefore best guess how this is processed...
|
||||||
|
// http://www.networksorcery.com/enp/rfc/rfc1334.txt ?
|
||||||
|
|
||||||
|
decode(data: Buffer) {
|
||||||
|
const usrNameLength = data.slice(7, 8).readUInt8(0);
|
||||||
|
const user = data.slice(8, usrNameLength);
|
||||||
|
console.log('user', user, user.toString().trim());
|
||||||
|
|
||||||
|
let pwdStart = usrNameLength; // data.slice(usrNameLength);
|
||||||
|
const passwordDelimeter = Buffer.from([0x02, 0x40, 0x00, 0x00]);
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
let pwd: Buffer;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const possibleDelimieter = data.slice(pwdStart, pwdStart + passwordDelimeter.length);
|
||||||
|
if (possibleDelimieter.equals(passwordDelimeter)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
pwdStart++;
|
||||||
|
}
|
||||||
|
} while (!found && pwdStart < data.length);
|
||||||
|
if (!found) {
|
||||||
|
throw new Error("couldn't extract password");
|
||||||
|
}
|
||||||
|
// console.log('pwdStart+passwordDelimeter.length', pwdStart+passwordDelimeter.length);
|
||||||
|
// console.log('length', pwdStart + data.readUInt8(pwdStart+passwordDelimeter.length));
|
||||||
|
// first byte is a length property.. we ignore for now
|
||||||
|
pwd = data.slice(pwdStart + passwordDelimeter.length + 1); // , pwdStart+ data.readUInt8(pwdStart+passwordDelimeter.length));
|
||||||
|
// trim pwd
|
||||||
|
pwd = pwd.slice(0, pwd.indexOf(0x00));
|
||||||
|
|
||||||
|
console.log('pwd', pwd, pwd.toString().trim().length, pwd.toString());
|
||||||
|
|
||||||
|
return {
|
||||||
|
username: user.toString(),
|
||||||
|
password: pwd.toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
110
src/eap/eap-ttls.ts
Normal file
110
src/eap/eap-ttls.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import * as events from 'events';
|
||||||
|
import { openTLSSockets, startTLSServer } from '../tls/crypt';
|
||||||
|
import { IResponseHandlers } from '../types/Handler';
|
||||||
|
import { PAPChallenge } from './challenges/pap';
|
||||||
|
import { IEAPType } from '../types/EAPType';
|
||||||
|
|
||||||
|
export class EAPTTLS implements IEAPType {
|
||||||
|
papChallenge: PAPChallenge;
|
||||||
|
|
||||||
|
constructor(private sendEAPResponse) {
|
||||||
|
this.papChallenge = new PAPChallenge();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage(msg: Buffer, state: string, handlers, identifier: number) {
|
||||||
|
const flags = msg.slice(5, 6); // .toString('hex');
|
||||||
|
|
||||||
|
// if (flags)
|
||||||
|
// @todo check if "L" flag is set in flags
|
||||||
|
const msglength = msg.slice(6, 10).readInt32BE(0); // .toString('hex');
|
||||||
|
const data = msg.slice(6, msg.length); // 10); //.toString('hex');
|
||||||
|
|
||||||
|
// check if no data package is there and we have something in the queue, if so.. empty the queue first
|
||||||
|
if (!data) {
|
||||||
|
// @todo: queue processing
|
||||||
|
console.warn('no data, just a confirmation!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('incoming EAP TTLS', {
|
||||||
|
flags /*
|
||||||
|
0 1 2 3 4 5 6 7
|
||||||
|
+---+---+---+---+---+---+---+---+
|
||||||
|
| L | M | S | R | R | V |
|
||||||
|
+---+---+---+---+---+---+---+---+
|
||||||
|
|
||||||
|
L = Length included
|
||||||
|
M = More fragments
|
||||||
|
S = Start
|
||||||
|
R = Reserved
|
||||||
|
V = Version (000 for EAP-TTLSv0)
|
||||||
|
*/,
|
||||||
|
msglength,
|
||||||
|
data,
|
||||||
|
dataStr: data.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
let sslLayer = openTLSSockets.get(state) as
|
||||||
|
| { socket: events.EventEmitter; currentHandlers: IResponseHandlers }
|
||||||
|
| undefined;
|
||||||
|
if (!sslLayer) {
|
||||||
|
const newSocket = startTLSServer();
|
||||||
|
sslLayer = { socket: newSocket, currentHandlers: handlers };
|
||||||
|
openTLSSockets.set(state, sslLayer);
|
||||||
|
|
||||||
|
// register event listeners
|
||||||
|
newSocket.on('incoming', (incomingData: Buffer) => {
|
||||||
|
const type = incomingData.slice(3, 4).readUInt8(0);
|
||||||
|
// const code = data.slice(4, 5).readUInt8(0);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 1: // PAP / CHAP
|
||||||
|
try {
|
||||||
|
const { username, password } = this.papChallenge.decode(incomingData);
|
||||||
|
sslLayer!.currentHandlers.checkAuth(username, password, identifier);
|
||||||
|
} catch (err) {
|
||||||
|
// pwd not found..
|
||||||
|
console.error('pwd not found', err);
|
||||||
|
// NAK
|
||||||
|
this.sendEAPResponse(sslLayer!.currentHandlers.response, identifier, undefined, 3);
|
||||||
|
newSocket.emit('end');
|
||||||
|
throw new Error(`pwd not found`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('data', incomingData);
|
||||||
|
console.log('data str', incomingData.toString());
|
||||||
|
|
||||||
|
newSocket.emit('end');
|
||||||
|
throw new Error(`unsupported auth type${type}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on('response', (responseData: Buffer) => {
|
||||||
|
console.log('sending encrypted data back to client', responseData);
|
||||||
|
|
||||||
|
// send back...
|
||||||
|
this.sendEAPResponse(sslLayer!.currentHandlers.response, identifier, responseData);
|
||||||
|
// this.sendMessage(TYPE.PRELOGIN, data, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on('end', () => {
|
||||||
|
// cleanup socket
|
||||||
|
console.log('ENDING SOCKET');
|
||||||
|
openTLSSockets.del(state);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('using existing socket');
|
||||||
|
}
|
||||||
|
|
||||||
|
// update handlers
|
||||||
|
sslLayer.currentHandlers = {
|
||||||
|
...handlers,
|
||||||
|
checkAuth: (username: string, password: string) =>
|
||||||
|
handlers.checkAuth(username, password, identifier)
|
||||||
|
};
|
||||||
|
|
||||||
|
// emit data to tls server
|
||||||
|
sslLayer.socket.emit('send', data);
|
||||||
|
}
|
||||||
|
}
|
9
src/helpers.ts
Normal file
9
src/helpers.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function makeid(length) {
|
||||||
|
let result = '';
|
||||||
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
const charactersLength = characters.length;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
124
src/ldap.ts
Normal file
124
src/ldap.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import * as NodeCache from 'node-cache';
|
||||||
|
|
||||||
|
import { createClient, Client } from 'ldapjs';
|
||||||
|
|
||||||
|
const usernameFields = ['posixUid', 'mail'];
|
||||||
|
|
||||||
|
// TLS:
|
||||||
|
// https://github.com/ldapjs/node-ldapjs/issues/307
|
||||||
|
|
||||||
|
export class LDAPAuth {
|
||||||
|
cache = new NodeCache();
|
||||||
|
|
||||||
|
ldap: Client;
|
||||||
|
|
||||||
|
lastDNsFetch: Date;
|
||||||
|
|
||||||
|
allValidDNsCache: { [key: string]: string };
|
||||||
|
|
||||||
|
constructor(private url: string, private base: string, tlsOptions?) {
|
||||||
|
this.ldap = createClient({ url, tlsOptions }).on('error', error => {
|
||||||
|
console.error('Error in ldap', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetchDNs();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchDNs() {
|
||||||
|
const dns: { [key: string]: string } = {};
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.ldap.search(
|
||||||
|
this.base,
|
||||||
|
{
|
||||||
|
scope: 'sub'
|
||||||
|
},
|
||||||
|
(err, res) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.on('searchEntry', function(entry) {
|
||||||
|
// console.log('entry: ' + JSON.stringify(entry.object));
|
||||||
|
usernameFields.forEach(field => {
|
||||||
|
const index = entry.object[field] as string;
|
||||||
|
dns[index] = entry.object.dn;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('searchReference', function(referral) {
|
||||||
|
console.log(`referral: ${referral.uris.join()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('error', function(ldapErr) {
|
||||||
|
console.error(`error: ${ldapErr.message}`);
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', result => {
|
||||||
|
console.log(`status: ${result?.status}`);
|
||||||
|
|
||||||
|
// replace with new dns
|
||||||
|
this.allValidDNsCache = dns;
|
||||||
|
// console.log('allValidDNsCache', this.allValidDNsCache);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.lastDNsFetch = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticate(username: string, password: string, count = 0, forceFetching = false) {
|
||||||
|
const cacheKey = `usr:${username}|pwd:${password}`;
|
||||||
|
const fromCache = this.cache.get(cacheKey);
|
||||||
|
if (fromCache) {
|
||||||
|
return fromCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheValidTime = new Date();
|
||||||
|
cacheValidTime.setHours(cacheValidTime.getHours() - 12);
|
||||||
|
|
||||||
|
let dnsFetched = false;
|
||||||
|
|
||||||
|
if (!this.lastDNsFetch || this.lastDNsFetch < cacheValidTime || forceFetching) {
|
||||||
|
console.log('fetching dns');
|
||||||
|
await this.fetchDNs();
|
||||||
|
dnsFetched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 5) {
|
||||||
|
throw new Error('Failed to authenticate with LDAP!');
|
||||||
|
}
|
||||||
|
// const dn = ;
|
||||||
|
const dn = this.allValidDNsCache[username];
|
||||||
|
if (!dn) {
|
||||||
|
if (!dnsFetched && !forceFetching) {
|
||||||
|
return this.authenticate(username, password, count, true);
|
||||||
|
}
|
||||||
|
throw new Error(`invalid username, not found in DN: ${username}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.ldap.bind(dn, password, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
if (err && (err as any).stack && (err as any).stack.includes(`${this.url} closed`)) {
|
||||||
|
count++;
|
||||||
|
// wait 1 second to give the ldap error handler time to reconnect
|
||||||
|
setTimeout(() => resolve(this.authenticate(dn, password)), 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error('ldap error', err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
if (res) resolve(res);
|
||||||
|
else reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cache.set(cacheKey, true, 86400);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
225
src/tls/crypt.ts
Normal file
225
src/tls/crypt.ts
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import * as NodeCache from 'node-cache';
|
||||||
|
import * as events from 'events';
|
||||||
|
import * as tls from 'tls';
|
||||||
|
import { createSecureContext } from 'tls';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import * as DuplexPair from 'native-duplexpair';
|
||||||
|
import { makeid } from '../helpers';
|
||||||
|
|
||||||
|
// https://nodejs.org/api/tls.html
|
||||||
|
const tlsOptions = {
|
||||||
|
cert: fs.readFileSync('./ssl/public-cert.pem'),
|
||||||
|
key: fs.readFileSync('./ssl/private-key.pem'),
|
||||||
|
ecdhCurve: 'auto'
|
||||||
|
};
|
||||||
|
const secureContext = createSecureContext(tlsOptions);
|
||||||
|
export const openTLSSockets = new NodeCache({ useClones: false, stdTTL: 3600 }); // keep sockets for about one hour
|
||||||
|
|
||||||
|
export function startTLSServer(): events.EventEmitter {
|
||||||
|
const duplexpair = new DuplexPair();
|
||||||
|
const emitter = new events.EventEmitter();
|
||||||
|
|
||||||
|
const cleartext = new tls.TLSSocket(duplexpair.socket1, {
|
||||||
|
secureContext,
|
||||||
|
isServer: true
|
||||||
|
});
|
||||||
|
const encrypted = duplexpair.socket2;
|
||||||
|
|
||||||
|
emitter.on('send', (data: Buffer) => {
|
||||||
|
encrypted.write(data);
|
||||||
|
// encrypted.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
encrypted.on('data', (data: Buffer) => {
|
||||||
|
// console.log('encrypted data', data, data.toString());
|
||||||
|
emitter.emit('response', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
cleartext.on('secure', () => {
|
||||||
|
const cipher = cleartext.getCipher();
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log('Authorized', cleartext.authorized);
|
||||||
|
console.log('getTLSTicket', cleartext.getTLSTicket());
|
||||||
|
console.log('getEphemeralKeyInfo', cleartext.getEphemeralKeyInfo());
|
||||||
|
console.log('getPeerCertificate', cleartext.getPeerCertificate());
|
||||||
|
console.log('getSharedSigalgs', cleartext.getSharedSigalgs());
|
||||||
|
console.log('getCertificate', cleartext.getCertificate());
|
||||||
|
console.log('getSession', cleartext.getSession());
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (cipher) {
|
||||||
|
console.log(`TLS negotiated (${cipher.name}, ${cipher.version})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleartext.on('data', (data: Buffer) => {
|
||||||
|
// console.log('cleartext data', data, data.toString());
|
||||||
|
emitter.emit('incoming', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
cleartext.once('close', (data: Buffer) => {
|
||||||
|
console.log('cleartext close');
|
||||||
|
emitter.emit('end');
|
||||||
|
});
|
||||||
|
|
||||||
|
cleartext.on('keylog', line => {
|
||||||
|
console.log('############ KEYLOG #############', line);
|
||||||
|
// cleartext.getTicketKeys()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('*********** new client connection established / secured ********');
|
||||||
|
// this.emit('secure', securePair.cleartext);
|
||||||
|
// this.encryptAllFutureTraffic();
|
||||||
|
});
|
||||||
|
|
||||||
|
cleartext.on('error', (err?: Error) => {
|
||||||
|
console.log('cleartext error', err);
|
||||||
|
|
||||||
|
encrypted.destroy();
|
||||||
|
cleartext.destroy(err);
|
||||||
|
|
||||||
|
emitter.emit('end');
|
||||||
|
});
|
||||||
|
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function md5Hex(buffer: Buffer): Buffer {
|
||||||
|
const hasher = crypto.createHash('md5');
|
||||||
|
hasher.update(buffer);
|
||||||
|
return hasher.digest(); // new Buffer(hasher.digest("binary"), "binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: string): Buffer {
|
||||||
|
// see freeradius TTLS implementation how to obtain "key"......
|
||||||
|
|
||||||
|
// key should be:
|
||||||
|
// https://www.openssl.org/docs/man1.0.2/man3/SSL_export_keying_material.html
|
||||||
|
// https://github.com/nodejs/ffi/blob/master/deps/openssl/openssl/doc/man3/SSL_export_keying_material.pod
|
||||||
|
|
||||||
|
// but not available in NODE JS
|
||||||
|
|
||||||
|
console.log('KEY', key);
|
||||||
|
console.log('authenticator', authenticator);
|
||||||
|
console.log('secret', secret);
|
||||||
|
// 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.
|
||||||
|
*/
|
||||||
|
const salt = Buffer.concat([
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
Buffer.from((Number(makeid(1)) & 0b10000000).toString()), // ensure left most bit is set (1)
|
||||||
|
Buffer.from(makeid(1))
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log('salt', salt);
|
||||||
|
// ensure left most bit is set to 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
String
|
||||||
|
The plaintext String field consists of three logical sub-fields:
|
||||||
|
the Key-Length and Key sub-fields (both of which are required),
|
||||||
|
and the optional Padding sub-field. The Key-Length sub-field is
|
||||||
|
one octet in length and contains the length of the unencrypted Key
|
||||||
|
sub-field. The Key sub-field contains the actual encryption key.
|
||||||
|
If the combined length (in octets) of the unencrypted Key-Length
|
||||||
|
and Key sub-fields is not an even multiple of 16, then the Padding
|
||||||
|
sub-field MUST be present. If it is present, the length of the
|
||||||
|
Padding sub-field is variable, between 1 and 15 octets. The
|
||||||
|
String field MUST be encrypted as follows, prior to transmission:
|
||||||
|
|
||||||
|
Construct a plaintext version of the String field by concate-
|
||||||
|
nating the Key-Length and Key sub-fields. If necessary, pad
|
||||||
|
the resulting string until its length (in octets) is an even
|
||||||
|
multiple of 16. It is recommended that zero octets (0x00) be
|
||||||
|
used for padding. Call this plaintext P.
|
||||||
|
*/
|
||||||
|
|
||||||
|
console.log('key', key.length, key);
|
||||||
|
let P = Buffer.concat([new Uint8Array([key.length]), key]); // + key + padding;
|
||||||
|
|
||||||
|
// fill up with 0x00 till we have % 16
|
||||||
|
while (P.length % 16 !== 0) {
|
||||||
|
P = Buffer.concat([P, Buffer.from([0x00])]);
|
||||||
|
}
|
||||||
|
// console.log('PLAINTEXT', P.length, P);
|
||||||
|
/*
|
||||||
|
Call the shared secret S, the pseudo-random 128-bit Request
|
||||||
|
Authenticator (from the corresponding Access-Request packet) R,
|
||||||
|
and the contents of the Salt field A. Break P into 16 octet
|
||||||
|
chunks p(1), p(2)...p(i), where i = len(P)/16. Call the
|
||||||
|
ciphertext blocks c(1), c(2)...c(i) and the final ciphertext C.
|
||||||
|
Intermediate values b(1), b(2)...c(i) are required. Encryption
|
||||||
|
is performed in the following manner ('+' indicates
|
||||||
|
concatenation):
|
||||||
|
*/
|
||||||
|
|
||||||
|
const p: Buffer[] = [];
|
||||||
|
for (let i = 0; i < P.length; i += 16) {
|
||||||
|
p.push(P.slice(i, i + 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
const S = secret;
|
||||||
|
const R = authenticator;
|
||||||
|
const A = salt;
|
||||||
|
|
||||||
|
// console.log('S', S);
|
||||||
|
// console.log('R', R);
|
||||||
|
// console.log('A', A);
|
||||||
|
|
||||||
|
// const P = Buffer.alloc(16);
|
||||||
|
|
||||||
|
let C;
|
||||||
|
const c: { [key: number]: Buffer } = {};
|
||||||
|
const b: { [key: number]: Buffer } = {};
|
||||||
|
|
||||||
|
// console.log('S + R + A', S + R + A);
|
||||||
|
|
||||||
|
for (let i = 0; i < p.length; i++) {
|
||||||
|
// one octet is 8.. therefore +=2 means next 16
|
||||||
|
if (!i) {
|
||||||
|
b[i] = md5Hex(Buffer.concat([Buffer.from(S), R, A]));
|
||||||
|
} else {
|
||||||
|
b[i] = md5Hex(Buffer.concat([Buffer.from(S), c[i - 1]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
c[i] = Buffer.alloc(16); // ''; //p[i];
|
||||||
|
for (let n = 0; n < p[i].length; n++) {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
c[i][n] = p[i][n] ^ b[i][n];
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('c['+i+']', c[i]);
|
||||||
|
// console.log('b['+i+']', b[i]);
|
||||||
|
|
||||||
|
C = C ? Buffer.concat([C, c[i]]) : c[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const bufferC = Buffer.from(C);
|
||||||
|
console.log('BUFFER C', bufferC.length, bufferC);
|
||||||
|
return Buffer.concat([salt, bufferC]);
|
||||||
|
/*
|
||||||
|
Zorn Informational [Page 21]
|
||||||
|
|
||||||
|
RFC 2548 Microsoft Vendor-specific RADIUS Attributes March 1999
|
||||||
|
|
||||||
|
|
||||||
|
b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
|
||||||
|
b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
|
||||||
|
. .
|
||||||
|
. .
|
||||||
|
. .
|
||||||
|
b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
|
||||||
|
|
||||||
|
The resulting encrypted String field will contain
|
||||||
|
c(1)+c(2)+...+c(i).
|
||||||
|
*/
|
||||||
|
}
|
3
src/types/AuthChallenge.ts
Normal file
3
src/types/AuthChallenge.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface IAuthChallenge {
|
||||||
|
decode(data: Buffer): { username: string; password: string };
|
||||||
|
}
|
3
src/types/EAPType.ts
Normal file
3
src/types/EAPType.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface IEAPType {
|
||||||
|
handleMessage(msg: Buffer, state: string, handlers, identifier: number)
|
||||||
|
}
|
7
src/types/Handler.ts
Normal file
7
src/types/Handler.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export type ResponseHandler = (msg: Buffer) => void;
|
||||||
|
export type ResponseAuthHandler = (username: string, password: string, identifier: number) => void;
|
||||||
|
|
||||||
|
export interface IResponseHandlers {
|
||||||
|
response: ResponseHandler;
|
||||||
|
checkAuth: ResponseAuthHandler;
|
||||||
|
}
|
2
ssl/create.sh
Executable file
2
ssl/create.sh
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
openssl genrsa -out private-key.pem 1024
|
||||||
|
openssl req -new -key private-key.pem -out csr.pem
|
1
ssl/sign.sh
Normal file
1
ssl/sign.sh
Normal file
@ -0,0 +1 @@
|
|||||||
|
openssl x509 -req -in csr.pem -signkey private-key.pem -out public-cert.pem
|
11
tsconfig.eslint.json
Normal file
11
tsconfig.eslint.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"*.js"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true
|
||||||
|
}
|
||||||
|
}
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
|
||||||
|
// target settings for node 10
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2018",
|
||||||
|
"lib": ["es2018"],
|
||||||
|
|
||||||
|
// other best practice configs
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": false, // <-- get rid of this!
|
||||||
|
"removeComments": false, // <-- do not remove comments (needed for @deprecated notices etc)
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"composite": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"isolatedModules": false,
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "**/__tests__"],
|
||||||
|
"include": ["./src"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user