fix: add MS-MPPE-Send-Key and MS-MPPE-Recv-Key
This commit is contained in:
parent
13f10bae59
commit
7e28c60d81
78
src/app.ts
78
src/app.ts
@ -1,17 +1,17 @@
|
||||
import * as dgram from 'dgram';
|
||||
import * as radius from 'radius';
|
||||
// import * as dgram from "dgram";
|
||||
import * as fs from 'fs';
|
||||
// import * as fs from 'fs';
|
||||
import { EAPHandler } from './eap';
|
||||
import { encodeTunnelPW } from './tls/crypt';
|
||||
import { makeid } from './helpers';
|
||||
|
||||
import { LDAPAuth } from './ldap';
|
||||
import { AdditionalAuthHandler } from './types/Handler';
|
||||
|
||||
const server = dgram.createSocket('udp4');
|
||||
|
||||
// not used right now, using stunnel to connect to ldap
|
||||
const tlsOptions = {
|
||||
/* const tlsOptions = {
|
||||
key: fs.readFileSync('ldap.gsuite.hokify.com.40567.key'),
|
||||
cert: fs.readFileSync('ldap.gsuite.hokify.com.40567.crt'),
|
||||
|
||||
@ -20,7 +20,7 @@ const tlsOptions = {
|
||||
|
||||
// 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')
|
||||
@ -62,81 +62,33 @@ server.on('message', async function(msg, rinfo) {
|
||||
|
||||
// console.log('rinfo', rinfo);
|
||||
|
||||
async function checkAuth(username: string, password: string, EAPMessageIdentifier?: number) {
|
||||
async function checkAuth(
|
||||
username: string,
|
||||
password: string,
|
||||
additionalAuthHandler?: AdditionalAuthHandler
|
||||
) {
|
||||
console.log(`Access-Request for ${username}`);
|
||||
let code: 'Access-Accept' | 'Access-Reject';
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
await ldap.authenticate(username, password);
|
||||
code = 'Access-Accept';
|
||||
success = true;
|
||||
} 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 (additionalAuthHandler) {
|
||||
await additionalAuthHandler(success, { packet, attributes, secret: argv.secret });
|
||||
}
|
||||
|
||||
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,
|
||||
code: success ? 'Access-Accept' : 'Access-Reject',
|
||||
secret: argv.secret,
|
||||
attributes
|
||||
});
|
||||
console.log(`Sending ${code} for user ${username}`);
|
||||
console.log(`Sending ${success ? 'accept' : 'reject'} for user ${username}`);
|
||||
|
||||
server.send(response, 0, response.length, rinfo.port, rinfo.address, function(err, _bytes) {
|
||||
if (err) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as events from 'events';
|
||||
import { openTLSSockets, startTLSServer } from '../tls/crypt';
|
||||
import { IResponseHandlers } from '../types/Handler';
|
||||
import * as tls from 'tls';
|
||||
import { encodeTunnelPW, openTLSSockets, startTLSServer } from '../tls/crypt';
|
||||
import { AdditionalAuthHandler, IResponseHandlers } from '../types/Handler';
|
||||
import { PAPChallenge } from './challenges/pap';
|
||||
import { IEAPType } from '../types/EAPType';
|
||||
|
||||
@ -44,16 +45,20 @@ export class EAPTTLS implements IEAPType {
|
||||
dataStr: data.toString()
|
||||
});
|
||||
|
||||
let sslLayer = openTLSSockets.get(state) as
|
||||
| { socket: events.EventEmitter; currentHandlers: IResponseHandlers }
|
||||
let currentConnection = openTLSSockets.get(state) as
|
||||
| { events: events.EventEmitter; tls: tls.TLSSocket; currentHandlers: IResponseHandlers }
|
||||
| undefined;
|
||||
if (!sslLayer) {
|
||||
const newSocket = startTLSServer();
|
||||
sslLayer = { socket: newSocket, currentHandlers: handlers };
|
||||
openTLSSockets.set(state, sslLayer);
|
||||
if (!currentConnection) {
|
||||
const connection = startTLSServer();
|
||||
currentConnection = {
|
||||
events: connection.events,
|
||||
tls: connection.tls,
|
||||
currentHandlers: handlers
|
||||
};
|
||||
openTLSSockets.set(state, currentConnection);
|
||||
|
||||
// register event listeners
|
||||
newSocket.on('incoming', (incomingData: Buffer) => {
|
||||
currentConnection.events.on('incoming', (incomingData: Buffer) => {
|
||||
const type = incomingData.slice(3, 4).readUInt8(0);
|
||||
// const code = data.slice(4, 5).readUInt8(0);
|
||||
|
||||
@ -61,13 +66,18 @@ export class EAPTTLS implements IEAPType {
|
||||
case 1: // PAP / CHAP
|
||||
try {
|
||||
const { username, password } = this.papChallenge.decode(incomingData);
|
||||
sslLayer!.currentHandlers.checkAuth(username, password, identifier);
|
||||
currentConnection!.currentHandlers.checkAuth(username, password);
|
||||
} catch (err) {
|
||||
// pwd not found..
|
||||
console.error('pwd not found', err);
|
||||
// NAK
|
||||
this.sendEAPResponse(sslLayer!.currentHandlers.response, identifier, undefined, 3);
|
||||
newSocket.emit('end');
|
||||
this.sendEAPResponse(
|
||||
currentConnection!.currentHandlers.response,
|
||||
identifier,
|
||||
undefined,
|
||||
3
|
||||
);
|
||||
currentConnection!.events.emit('end');
|
||||
throw new Error(`pwd not found`);
|
||||
}
|
||||
break;
|
||||
@ -75,20 +85,20 @@ export class EAPTTLS implements IEAPType {
|
||||
console.log('data', incomingData);
|
||||
console.log('data str', incomingData.toString());
|
||||
|
||||
newSocket.emit('end');
|
||||
currentConnection!.events.emit('end');
|
||||
throw new Error(`unsupported auth type${type}`);
|
||||
}
|
||||
});
|
||||
|
||||
newSocket.on('response', (responseData: Buffer) => {
|
||||
currentConnection.events.on('response', (responseData: Buffer) => {
|
||||
console.log('sending encrypted data back to client', responseData);
|
||||
|
||||
// send back...
|
||||
this.sendEAPResponse(sslLayer!.currentHandlers.response, identifier, responseData);
|
||||
this.sendEAPResponse(currentConnection!.currentHandlers.response, identifier, responseData);
|
||||
// this.sendMessage(TYPE.PRELOGIN, data, false);
|
||||
});
|
||||
|
||||
newSocket.on('end', () => {
|
||||
currentConnection.events.on('end', () => {
|
||||
// cleanup socket
|
||||
console.log('ENDING SOCKET');
|
||||
openTLSSockets.del(state);
|
||||
@ -98,13 +108,78 @@ export class EAPTTLS implements IEAPType {
|
||||
}
|
||||
|
||||
// update handlers
|
||||
sslLayer.currentHandlers = {
|
||||
currentConnection.currentHandlers = {
|
||||
...handlers,
|
||||
checkAuth: (username: string, password: string) =>
|
||||
handlers.checkAuth(username, password, identifier)
|
||||
checkAuth: (username: string, password: string) => {
|
||||
const additionalAuthHandler: AdditionalAuthHandler = (success, params) => {
|
||||
const buffer = Buffer.from([
|
||||
success ? 3 : 4, // 3.. success, 4... failure
|
||||
identifier,
|
||||
0, // length (1/2)
|
||||
4 // length (2/2)
|
||||
]);
|
||||
|
||||
params.attributes.push(['EAP-Message', buffer]);
|
||||
|
||||
if (params.packet.attributes && params.packet.attributes['User-Name']) {
|
||||
// reappend username to response
|
||||
params.attributes.push(['User-Name', params.packet.attributes['User-Name']]);
|
||||
}
|
||||
|
||||
/*
|
||||
if (sess->eap_if->eapKeyDataLen > 64) {
|
||||
len = 32;
|
||||
} else {
|
||||
len = sess->eap_if->eapKeyDataLen / 2;
|
||||
}
|
||||
*/
|
||||
const keyingMaterial = (currentConnection?.tls as any).exportKeyingMaterial(
|
||||
128,
|
||||
'ttls keying material'
|
||||
);
|
||||
|
||||
console.log('keyingMaterial', keyingMaterial);
|
||||
|
||||
// eapKeyData + len
|
||||
params.attributes.push([
|
||||
'Vendor-Specific',
|
||||
311,
|
||||
[
|
||||
[
|
||||
16,
|
||||
encodeTunnelPW(
|
||||
keyingMaterial.slice(64),
|
||||
(params.packet as any).authenticator,
|
||||
// params.packet.attributes['Message-Authenticator'],
|
||||
params.secret
|
||||
)
|
||||
]
|
||||
]
|
||||
]); // MS-MPPE-Send-Key
|
||||
|
||||
// eapKeyData
|
||||
params.attributes.push([
|
||||
'Vendor-Specific',
|
||||
311,
|
||||
[
|
||||
[
|
||||
17,
|
||||
encodeTunnelPW(
|
||||
keyingMaterial.slice(0, 64),
|
||||
(params.packet as any).authenticator,
|
||||
// params.packet.attributes['Message-Authenticator'],
|
||||
params.secret
|
||||
)
|
||||
]
|
||||
]
|
||||
]); // MS-MPPE-Recv-Key
|
||||
};
|
||||
|
||||
return handlers.checkAuth(username, password, additionalAuthHandler);
|
||||
}
|
||||
};
|
||||
|
||||
// emit data to tls server
|
||||
sslLayer.socket.emit('send', data);
|
||||
currentConnection.events.emit('send', data);
|
||||
}
|
||||
}
|
||||
|
111
src/tls/crypt.ts
111
src/tls/crypt.ts
@ -16,7 +16,7 @@ const tlsOptions = {
|
||||
const secureContext = createSecureContext(tlsOptions);
|
||||
export const openTLSSockets = new NodeCache({ useClones: false, stdTTL: 3600 }); // keep sockets for about one hour
|
||||
|
||||
export function startTLSServer(): events.EventEmitter {
|
||||
export function startTLSServer(): { events: events.EventEmitter; tls: tls.TLSSocket } {
|
||||
const duplexpair = new DuplexPair();
|
||||
const emitter = new events.EventEmitter();
|
||||
|
||||
@ -58,7 +58,7 @@ export function startTLSServer(): events.EventEmitter {
|
||||
emitter.emit('incoming', data);
|
||||
});
|
||||
|
||||
cleartext.once('close', (data: Buffer) => {
|
||||
cleartext.once('close', (_data: Buffer) => {
|
||||
console.log('cleartext close');
|
||||
emitter.emit('end');
|
||||
});
|
||||
@ -82,7 +82,10 @@ export function startTLSServer(): events.EventEmitter {
|
||||
emitter.emit('end');
|
||||
});
|
||||
|
||||
return emitter;
|
||||
return {
|
||||
events: emitter,
|
||||
tls: cleartext
|
||||
};
|
||||
}
|
||||
|
||||
function md5Hex(buffer: Buffer): Buffer {
|
||||
@ -91,18 +94,15 @@ function md5Hex(buffer: Buffer): Buffer {
|
||||
return hasher.digest(); // new Buffer(hasher.digest("binary"), "binary");
|
||||
}
|
||||
|
||||
// alloc_size
|
||||
|
||||
// 0,
|
||||
// EAP_TLS_KEY_LEN 64
|
||||
// EAP_EMSK_LEN 64
|
||||
// const buffer = tlsSocket.exportKeyingMaterial(128, 'ttls keying material');
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
@ -120,9 +120,6 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
|
||||
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:
|
||||
@ -143,14 +140,13 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
|
||||
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,
|
||||
@ -160,53 +156,7 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
|
||||
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
|
||||
@ -222,4 +172,37 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
|
||||
The resulting encrypted String field will contain
|
||||
c(1)+c(2)+...+c(i).
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
let C;
|
||||
const c: { [key: number]: Buffer } = {};
|
||||
const b: { [key: number]: Buffer } = {};
|
||||
|
||||
for (let i = 0; i < p.length; i++) {
|
||||
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];
|
||||
}
|
||||
|
||||
C = C ? Buffer.concat([C, c[i]]) : c[i];
|
||||
}
|
||||
|
||||
const bufferC = Buffer.from(C);
|
||||
|
||||
return Buffer.concat([salt, bufferC]);
|
||||
}
|
||||
|
@ -1,7 +1,18 @@
|
||||
import { RadiusPacket } from 'radius';
|
||||
|
||||
export type ResponseHandler = (msg: Buffer) => void;
|
||||
export type ResponseAuthHandler = (username: string, password: string, identifier: number) => void;
|
||||
export type ResponseAuthHandler = (
|
||||
username: string,
|
||||
password: string,
|
||||
additionalAuthHandler?: AdditionalAuthHandler
|
||||
) => void;
|
||||
|
||||
export interface IResponseHandlers {
|
||||
response: ResponseHandler;
|
||||
checkAuth: ResponseAuthHandler;
|
||||
}
|
||||
|
||||
export type AdditionalAuthHandler = (
|
||||
success: boolean,
|
||||
params: { packet: RadiusPacket; attributes: any[]; secret: string }
|
||||
) => void;
|
||||
|
Loading…
Reference in New Issue
Block a user