fix: add MS-MPPE-Send-Key and MS-MPPE-Recv-Key

This commit is contained in:
simon 2020-02-16 19:17:03 +01:00
parent 13f10bae59
commit 7e28c60d81
4 changed files with 171 additions and 150 deletions

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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]);
}

View File

@ -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;