fix: a lot of bug fixes, first running version for windows and android :)
code is super ugly right now.. please don't judge
This commit is contained in:
parent
7e28c60d81
commit
4989c2b6bc
33
config.js
Normal file
33
config.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// radius secret
|
||||||
|
secret: 'testing123',
|
||||||
|
|
||||||
|
certificate: {
|
||||||
|
cert: fs.readFileSync('./ssl/cert/server.crt'),
|
||||||
|
key: [
|
||||||
|
{
|
||||||
|
pem: fs.readFileSync('./ssl/cert/server.key') as Buffer,
|
||||||
|
passphrase: 'whatever2020'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// authentication
|
||||||
|
authentication: 'ldap',
|
||||||
|
authenticationOptions: {
|
||||||
|
url: 'ldap://127.0.0.1:1636',
|
||||||
|
base: 'dc=hokify,dc=com',
|
||||||
|
tlsOptions2: {
|
||||||
|
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')]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -3,7 +3,7 @@
|
|||||||
"description": "radius server for google LDAP and TTLT",
|
"description": "radius server for google LDAP and TTLT",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node dist/app.js",
|
"start": "/home/simon/Dev/others/node/node dist/app.js",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"dev": "ts-node src/app.ts",
|
"dev": "ts-node src/app.ts",
|
||||||
"test-ttls-pap": "eapol_test -c ./ttls-pap.conf -s testing123",
|
"test-ttls-pap": "eapol_test -c ./ttls-pap.conf -s testing123",
|
||||||
@ -17,7 +17,8 @@
|
|||||||
"ts-node": "^8.6.2",
|
"ts-node": "^8.6.2",
|
||||||
"type-cacheable": "^4.0.0",
|
"type-cacheable": "^4.0.0",
|
||||||
"yargs": "~15.1.0",
|
"yargs": "~15.1.0",
|
||||||
"md5": "^2.2.1"
|
"md5": "^2.2.1",
|
||||||
|
"passport-ldapauth": "^2.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/ldapjs": "^1.0.5",
|
"@types/ldapjs": "^1.0.5",
|
||||||
|
94
src/app.ts
94
src/app.ts
@ -3,27 +3,15 @@ import * as radius from 'radius';
|
|||||||
// import * as dgram from "dgram";
|
// import * as dgram from "dgram";
|
||||||
// import * as fs from 'fs';
|
// import * as fs from 'fs';
|
||||||
import { EAPHandler } from './eap';
|
import { EAPHandler } from './eap';
|
||||||
import { makeid } from './helpers';
|
import { IDeferredPromise, makeid, MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from './helpers';
|
||||||
|
|
||||||
import { LDAPAuth } from './ldap';
|
import { GoogleLDAPAuth } from './auth/google-ldap';
|
||||||
import { AdditionalAuthHandler } from './types/Handler';
|
import { AdditionalAuthHandler } from './types/Handler';
|
||||||
|
|
||||||
const server = dgram.createSocket('udp4');
|
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')
|
const { argv } = require('yargs')
|
||||||
.usage('Simple Google LDAP <> RADIUS Server\nUsage: $0')
|
.usage('RADIUS Server\nUsage: $0')
|
||||||
.example('$0 --port 1812 -s radiussecret')
|
.example('$0 --port 1812 -s radiussecret')
|
||||||
.default({
|
.default({
|
||||||
port: 1812,
|
port: 1812,
|
||||||
@ -46,9 +34,39 @@ 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({url: 'ldap://ldap.google.com', base: 'dc=hokify,dc=com', uid: 'uid', tlsOptions});
|
||||||
|
|
||||||
const ldap = new LDAPAuth(argv.ldapServer, argv.baseDN);
|
const ldap = new GoogleLDAPAuth(argv.ldapServer, argv.baseDN);
|
||||||
|
|
||||||
const eapHandler = new EAPHandler();
|
const eapHandler = new EAPHandler();
|
||||||
|
const timeout: { [key: string]: NodeJS.Timeout } = {};
|
||||||
|
const waitForNextMsg: { [key: string]: IDeferredPromise } = {};
|
||||||
|
|
||||||
|
function sendToClient(
|
||||||
|
msg: string | Uint8Array,
|
||||||
|
offset: number,
|
||||||
|
length: number,
|
||||||
|
port?: number,
|
||||||
|
address?: string,
|
||||||
|
callback?: (error: Error | null, bytes: number) => void,
|
||||||
|
stateForRetry?: string
|
||||||
|
): void {
|
||||||
|
let retried = 0;
|
||||||
|
|
||||||
|
function sendResponse() {
|
||||||
|
console.log(`sending response... (try: ${retried})`);
|
||||||
|
server.send(msg, offset, length, port, address, (error: Error | null, bytes: number) => {
|
||||||
|
// all good
|
||||||
|
|
||||||
|
if (callback) callback(error, bytes);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (stateForRetry && retried < 3) {
|
||||||
|
// timeout[stateForRetry] = setTimeout(sendResponse, 600 * (retried+1));
|
||||||
|
}
|
||||||
|
retried++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResponse();
|
||||||
|
}
|
||||||
|
|
||||||
server.on('message', async function(msg, rinfo) {
|
server.on('message', async function(msg, rinfo) {
|
||||||
const packet = radius.decode({ packet: msg, secret: argv.secret });
|
const packet = radius.decode({ packet: msg, secret: argv.secret });
|
||||||
@ -57,7 +75,6 @@ server.on('message', async function(msg, rinfo) {
|
|||||||
console.log('unknown packet type: ', packet.code);
|
console.log('unknown packet type: ', packet.code);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('packet.attributes', packet.attributes);
|
// console.log('packet.attributes', packet.attributes);
|
||||||
|
|
||||||
// console.log('rinfo', rinfo);
|
// console.log('rinfo', rinfo);
|
||||||
@ -90,7 +107,7 @@ server.on('message', async function(msg, rinfo) {
|
|||||||
});
|
});
|
||||||
console.log(`Sending ${success ? 'accept' : 'reject'} 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) {
|
sendToClient(response, 0, response.length, rinfo.port, rinfo.address, function(err, _bytes) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('Error sending response to ', rinfo);
|
console.log('Error sending response to ', rinfo);
|
||||||
}
|
}
|
||||||
@ -99,15 +116,22 @@ server.on('message', async function(msg, rinfo) {
|
|||||||
|
|
||||||
if (packet.attributes['EAP-Message']) {
|
if (packet.attributes['EAP-Message']) {
|
||||||
const state = (packet.attributes.State && packet.attributes.State.toString()) || makeid(16);
|
const state = (packet.attributes.State && packet.attributes.State.toString()) || makeid(16);
|
||||||
// EAP MESSAGE
|
|
||||||
eapHandler.handleEAPMessage(packet.attributes['EAP-Message'], state, {
|
if (timeout[state]) {
|
||||||
|
clearTimeout(timeout[state]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
response: (EAPMessage: Buffer) => {
|
response: (EAPMessage: Buffer) => {
|
||||||
const attributes: any = [['State', Buffer.from(state)]];
|
const attributes: any = [['State', Buffer.from(state)]];
|
||||||
let sentDataSize = 0;
|
let sentDataSize = 0;
|
||||||
do {
|
do {
|
||||||
if (EAPMessage.length > 0) {
|
if (EAPMessage.length > 0) {
|
||||||
attributes.push(['EAP-Message', EAPMessage.slice(sentDataSize, sentDataSize + 253)]);
|
attributes.push([
|
||||||
sentDataSize += 253;
|
'EAP-Message',
|
||||||
|
EAPMessage.slice(sentDataSize, sentDataSize + MAX_RADIUS_ATTRIBUTE_SIZE)
|
||||||
|
]);
|
||||||
|
sentDataSize += MAX_RADIUS_ATTRIBUTE_SIZE;
|
||||||
}
|
}
|
||||||
} while (sentDataSize < EAPMessage.length);
|
} while (sentDataSize < EAPMessage.length);
|
||||||
|
|
||||||
@ -118,14 +142,34 @@ server.on('message', async function(msg, rinfo) {
|
|||||||
attributes
|
attributes
|
||||||
});
|
});
|
||||||
|
|
||||||
server.send(response, 0, response.length, rinfo.port, rinfo.address, function(err, _bytes) {
|
waitForNextMsg[state] = newDeferredPromise();
|
||||||
|
|
||||||
|
sendToClient(
|
||||||
|
response,
|
||||||
|
0,
|
||||||
|
response.length,
|
||||||
|
rinfo.port,
|
||||||
|
rinfo.address,
|
||||||
|
function(err, _bytes) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('Error sending response to ', rinfo);
|
console.log('Error sending response to ', rinfo);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
state
|
||||||
|
);
|
||||||
|
|
||||||
|
return waitForNextMsg[state].promise;
|
||||||
},
|
},
|
||||||
checkAuth
|
checkAuth
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (waitForNextMsg[state]) {
|
||||||
|
const identifier = packet.attributes['EAP-Message'].slice(1, 2).readUInt8(0); // .toString('hex');
|
||||||
|
waitForNextMsg[state].resolve({ response: handlers.response, identifier });
|
||||||
|
}
|
||||||
|
|
||||||
|
// EAP MESSAGE
|
||||||
|
eapHandler.handleEAPMessage(packet.attributes['EAP-Message'], state, handlers);
|
||||||
} else {
|
} else {
|
||||||
const username = packet.attributes['User-Name'];
|
const username = packet.attributes['User-Name'];
|
||||||
const password = packet.attributes['User-Password'];
|
const password = packet.attributes['User-Password'];
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import * as NodeCache from 'node-cache';
|
import * as NodeCache from 'node-cache';
|
||||||
|
|
||||||
import { createClient, Client } from 'ldapjs';
|
import { createClient, Client } from 'ldapjs';
|
||||||
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
|
||||||
const usernameFields = ['posixUid', 'mail'];
|
const usernameFields = ['posixUid', 'mail'];
|
||||||
|
|
||||||
// TLS:
|
// TLS:
|
||||||
// https://github.com/ldapjs/node-ldapjs/issues/307
|
// https://github.com/ldapjs/node-ldapjs/issues/307
|
||||||
|
|
||||||
export class LDAPAuth {
|
export class GoogleLDAPAuth implements IAuthentication {
|
||||||
cache = new NodeCache();
|
cache = new NodeCache();
|
||||||
|
|
||||||
ldap: Client;
|
ldap: Client;
|
||||||
@ -24,7 +25,7 @@ export class LDAPAuth {
|
|||||||
this.fetchDNs();
|
this.fetchDNs();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchDNs() {
|
private async fetchDNs() {
|
||||||
const dns: { [key: string]: string } = {};
|
const dns: { [key: string]: string } = {};
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
@ -119,6 +120,6 @@ export class LDAPAuth {
|
|||||||
|
|
||||||
this.cache.set(cacheKey, true, 86400);
|
this.cache.set(cacheKey, true, 86400);
|
||||||
|
|
||||||
return true;
|
return username;
|
||||||
}
|
}
|
||||||
}
|
}
|
80
src/eap.ts
80
src/eap.ts
@ -4,6 +4,7 @@
|
|||||||
// 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)
|
||||||
import { IResponseHandlers, ResponseHandler } from './types/Handler';
|
import { IResponseHandlers, ResponseHandler } from './types/Handler';
|
||||||
import { EAPTTLS } from './eap/eap-ttls';
|
import { EAPTTLS } from './eap/eap-ttls';
|
||||||
|
import { MAX_RADIUS_ATTRIBUTE_SIZE } from './helpers';
|
||||||
|
|
||||||
export class EAPHandler {
|
export class EAPHandler {
|
||||||
eapTTLS: EAPTTLS;
|
eapTTLS: EAPTTLS;
|
||||||
@ -17,28 +18,35 @@ export class EAPHandler {
|
|||||||
* @param data
|
* @param data
|
||||||
* @param type 1 = identity, 21 = EAP-TTLS, 2 = notification, 4 = md5-challenge, 3 = NAK
|
* @param type 1 = identity, 21 = EAP-TTLS, 2 = notification, 4 = md5-challenge, 3 = NAK
|
||||||
*/
|
*/
|
||||||
private sendEAPResponse(
|
private async sendEAPResponse(
|
||||||
response: ResponseHandler,
|
response: ResponseHandler,
|
||||||
identifier: number,
|
identifier: number,
|
||||||
data?: Buffer,
|
data?: Buffer,
|
||||||
msgType = 21,
|
msgType = 21,
|
||||||
msgFlags = 0x00
|
msgFlags = 0x00
|
||||||
) {
|
) {
|
||||||
const maxFragmentSize = 1400; // @todo .. take framed-mtu into account from AVPs
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
|
const maxSize = (MAX_RADIUS_ATTRIBUTE_SIZE - 5) * 4;
|
||||||
|
|
||||||
|
let sentDataSize = 0;
|
||||||
|
|
||||||
|
let currentIdentifier = identifier;
|
||||||
|
let currentResponse = response;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const fragmentMaxPart =
|
// SLICE
|
||||||
data && (i + 1) * maxFragmentSize > data.length ? undefined : (i + 1) * maxFragmentSize;
|
|
||||||
const sslPart = data && data.slice(i * maxFragmentSize, fragmentMaxPart);
|
|
||||||
console.log('sslPart', sslPart, i, maxFragmentSize, i * maxFragmentSize, fragmentMaxPart);
|
|
||||||
|
|
||||||
const includeLength =
|
// const fragmentMaxPart =
|
||||||
data &&
|
// data && (i + 1) * maxFragmentSize > data.length ? (i + 1) * maxFragmentSize : undefined;
|
||||||
i === 0 &&
|
|
||||||
fragmentMaxPart !== undefined; /* firsrt one and we have more, therefore include length */
|
const dataPart = data && data.length > 0 && data.slice(sentDataSize, sentDataSize + maxSize);
|
||||||
|
|
||||||
|
/* it's the first one and we have more, therefore include length */
|
||||||
|
const includeLength = data && i === 0 && sentDataSize < data.length;
|
||||||
|
|
||||||
|
sentDataSize += maxSize;
|
||||||
|
|
||||||
// console.log('includeLength', includeLength, fragmentMaxPart, i)
|
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -62,11 +70,13 @@ export class EAPHandler {
|
|||||||
const flags =
|
const flags =
|
||||||
msgFlags +
|
msgFlags +
|
||||||
(includeLength ? 0b10000000 : 0) + // set L bit
|
(includeLength ? 0b10000000 : 0) + // set L bit
|
||||||
(fragmentMaxPart /* we have more */ ? 0b01000000 : 0); // set M bit
|
(data && sentDataSize < data.length /* we have more */ ? 0b01000000 : 0); // set M bit
|
||||||
|
|
||||||
|
currentIdentifier++;
|
||||||
|
|
||||||
let buffer = Buffer.from([
|
let buffer = Buffer.from([
|
||||||
1, // request
|
1, // request
|
||||||
identifier + 1,
|
currentIdentifier,
|
||||||
0, // length (1/2)
|
0, // length (1/2)
|
||||||
0, // length (2/2)
|
0, // length (2/2)
|
||||||
msgType, // 1 = identity, 21 = EAP-TTLS, 2 = notificaiton, 4 = md5-challenge, 3 = NAK
|
msgType, // 1 = identity, 21 = EAP-TTLS, 2 = notificaiton, 4 = md5-challenge, 3 = NAK
|
||||||
@ -81,15 +91,16 @@ export class EAPHandler {
|
|||||||
buffer = Buffer.concat([buffer, length]);
|
buffer = Buffer.concat([buffer, length]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resBuffer = sslPart ? Buffer.concat([buffer, sslPart]) : buffer;
|
const resBuffer = dataPart ? Buffer.concat([buffer, dataPart]) : buffer;
|
||||||
resBuffer.writeUInt16BE(resBuffer.byteLength, 2);
|
resBuffer.writeUInt16BE(resBuffer.byteLength, 2);
|
||||||
|
|
||||||
console.log('EAP RESPONSE', {
|
console.log('<<<<<<<<<<<< EAP RESPONSE TO CLIENT', {
|
||||||
code: 1,
|
code: 1,
|
||||||
identifier: identifier + 1,
|
currentIdentifier,
|
||||||
length: (includeLength && data && data.byteLength) || 0,
|
includeLength,
|
||||||
msgType,
|
length: (data && data.byteLength) || 0,
|
||||||
flags,
|
msgType: msgType.toString(10),
|
||||||
|
flags: `00000000${flags.toString(2)}`.substr(-8),
|
||||||
data
|
data
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -98,13 +109,14 @@ export class EAPHandler {
|
|||||||
// buffer.writeInt8(21, 4); // eap-ttls
|
// buffer.writeInt8(21, 4); // eap-ttls
|
||||||
// buffer.writeInt8(0, 5); // flags
|
// buffer.writeInt8(0, 5); // flags
|
||||||
|
|
||||||
/*
|
console.log('sending message with identifier', currentIdentifier);
|
||||||
@todo: this is wrong,
|
({ identifier: currentIdentifier, response: currentResponse } = await currentResponse(
|
||||||
if there are more messages, add them to a queue
|
resBuffer
|
||||||
and process the next one when client has ack. (message without data)
|
));
|
||||||
*/
|
console.log('next message got identifier', currentIdentifier);
|
||||||
response(resBuffer);
|
} while (data && sentDataSize < data.length);
|
||||||
} while (data && i * maxFragmentSize < data.length);
|
|
||||||
|
console.log('DONE', sentDataSize, data && data.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEAPMessage(msg: Buffer, state: string, handlers: IResponseHandlers) {
|
handleEAPMessage(msg: Buffer, state: string, handlers: IResponseHandlers) {
|
||||||
@ -148,8 +160,7 @@ export class EAPHandler {
|
|||||||
Message Length | Data...
|
Message Length | Data...
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
*/
|
*/
|
||||||
|
console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', {});
|
||||||
console.log('RESPONDING WITH IDENTIDY / START');
|
|
||||||
|
|
||||||
this.sendEAPResponse(handlers.response, identifier, undefined, 21, 0x20);
|
this.sendEAPResponse(handlers.response, identifier, undefined, 21, 0x20);
|
||||||
|
|
||||||
@ -167,14 +178,17 @@ export class EAPHandler {
|
|||||||
break;
|
break;
|
||||||
case 21: // EAP TTLS
|
case 21: // EAP TTLS
|
||||||
this.eapTTLS.handleMessage(msg, state, handlers, identifier);
|
this.eapTTLS.handleMessage(msg, state, handlers, identifier);
|
||||||
return;
|
break;
|
||||||
case 3: // nak
|
case 3: // nak
|
||||||
this.sendEAPResponse(handlers.response, identifier, undefined, 3);
|
// this.sendEAPResponse(handlers.response, identifier, undefined, 3);
|
||||||
break;
|
break;
|
||||||
case 2: // notification
|
case 2: // notification
|
||||||
|
console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
|
||||||
console.info('notification');
|
console.info('notification');
|
||||||
break;
|
break;
|
||||||
case 4: // md5-challenge
|
case 4: // md5-challenge
|
||||||
|
console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {});
|
||||||
|
|
||||||
console.info('md5-challenge');
|
console.info('md5-challenge');
|
||||||
break;
|
break;
|
||||||
case 254: // expanded type
|
case 254: // expanded type
|
||||||
@ -183,7 +197,11 @@ export class EAPHandler {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error('unsupported type', type);
|
// we do not support this auth type, ask for TTLS
|
||||||
|
console.error('unsupported type', type, 'requesting TTLS (21)');
|
||||||
|
|
||||||
|
this.sendEAPResponse(handlers.response, identifier, Buffer.from([21]), 3);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IAuthChallenge } from '../../types/AuthChallenge';
|
import { IEAPChallenge } from '../../types/EAPChallenge';
|
||||||
|
|
||||||
export class PAPChallenge implements IAuthChallenge {
|
export class PAPChallenge implements IEAPChallenge {
|
||||||
// i couldn't find any documentation about it, therefore best guess how this is processed...
|
// i couldn't find any documentation about it, therefore best guess how this is processed...
|
||||||
// http://www.networksorcery.com/enp/rfc/rfc1334.txt ?
|
// http://www.networksorcery.com/enp/rfc/rfc1334.txt ?
|
||||||
|
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
|
/* eslint-disable no-bitwise */
|
||||||
import * as events from 'events';
|
import * as events from 'events';
|
||||||
import * as tls from 'tls';
|
import * as tls from 'tls';
|
||||||
import { encodeTunnelPW, openTLSSockets, startTLSServer } from '../tls/crypt';
|
import { encodeTunnelPW, openTLSSockets, startTLSServer } from '../tls/crypt';
|
||||||
import { AdditionalAuthHandler, IResponseHandlers } from '../types/Handler';
|
import { AdditionalAuthHandler, ResponseAuthHandler } from '../types/Handler';
|
||||||
import { PAPChallenge } from './challenges/pap';
|
import { PAPChallenge } from './challenges/pap';
|
||||||
import { IEAPType } from '../types/EAPType';
|
import { IEAPType } from '../types/EAPType';
|
||||||
|
|
||||||
|
interface IEAPResponseHandlers {
|
||||||
|
response: (respData?: Buffer, msgType?: number) => void;
|
||||||
|
checkAuth: ResponseAuthHandler;
|
||||||
|
}
|
||||||
|
|
||||||
export class EAPTTLS implements IEAPType {
|
export class EAPTTLS implements IEAPType {
|
||||||
papChallenge: PAPChallenge;
|
papChallenge: PAPChallenge;
|
||||||
|
|
||||||
@ -12,23 +18,48 @@ export class EAPTTLS implements IEAPType {
|
|||||||
this.papChallenge = new PAPChallenge();
|
this.papChallenge = new PAPChallenge();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage(msg: Buffer, state: string, handlers, identifier: number) {
|
decode(msg: Buffer) {
|
||||||
const flags = msg.slice(5, 6); // .toString('hex');
|
const flags = msg.slice(5, 6).readUInt8(0); // .toString('hex');
|
||||||
|
|
||||||
// if (flags)
|
// if (flags)
|
||||||
// @todo check if "L" flag is set in flags
|
// @todo check if "L" flag is set in flags
|
||||||
const msglength = msg.slice(6, 10).readInt32BE(0); // .toString('hex');
|
const decodedFlags = {
|
||||||
const data = msg.slice(6, msg.length); // 10); //.toString('hex');
|
lengthIncluded: flags & 0b010000000,
|
||||||
|
moreFragments: flags & 0b001000000,
|
||||||
|
start: flags & 0b000100000,
|
||||||
|
reserved: flags & 0b000011000,
|
||||||
|
version: flags & 0b010000111
|
||||||
|
};
|
||||||
|
let msglength;
|
||||||
|
if (decodedFlags.lengthIncluded) {
|
||||||
|
msglength = msg.slice(6, 10).readInt32BE(0); // .readDoubleLE(0); // .toString('hex');
|
||||||
|
}
|
||||||
|
const data = msg.slice(decodedFlags.lengthIncluded ? 10 : 6, msg.length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
decodedFlags,
|
||||||
|
msglength,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage(msg: Buffer, state: string, handlers, identifier: number) {
|
||||||
|
const { decodedFlags, msglength, data } = this.decode(msg);
|
||||||
|
|
||||||
// check if no data package is there and we have something in the queue, if so.. empty the queue first
|
// check if no data package is there and we have something in the queue, if so.. empty the queue first
|
||||||
if (!data) {
|
if (!data || data.length === 0) {
|
||||||
// @todo: queue processing
|
// @todo: queue processing
|
||||||
console.warn('no data, just a confirmation!');
|
console.warn(
|
||||||
|
`>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS, ACK / NACK (no data, just a confirmation, ID: ${identifier})`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('incoming EAP TTLS', {
|
console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS', {
|
||||||
flags /*
|
// flags: `00000000${flags.toString(2)}`.substr(-8),
|
||||||
|
decodedFlags,
|
||||||
|
identifier,
|
||||||
|
/*
|
||||||
0 1 2 3 4 5 6 7
|
0 1 2 3 4 5 6 7
|
||||||
+---+---+---+---+---+---+---+---+
|
+---+---+---+---+---+---+---+---+
|
||||||
| L | M | S | R | R | V |
|
| L | M | S | R | R | V |
|
||||||
@ -39,14 +70,15 @@ export class EAPTTLS implements IEAPType {
|
|||||||
S = Start
|
S = Start
|
||||||
R = Reserved
|
R = Reserved
|
||||||
V = Version (000 for EAP-TTLSv0)
|
V = Version (000 for EAP-TTLSv0)
|
||||||
*/,
|
*/
|
||||||
|
|
||||||
msglength,
|
msglength,
|
||||||
data,
|
data,
|
||||||
dataStr: data.toString()
|
dataStr: data.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
let currentConnection = openTLSSockets.get(state) as
|
let currentConnection = openTLSSockets.get(state) as
|
||||||
| { events: events.EventEmitter; tls: tls.TLSSocket; currentHandlers: IResponseHandlers }
|
| { events: events.EventEmitter; tls: tls.TLSSocket; currentHandlers: IEAPResponseHandlers }
|
||||||
| undefined;
|
| undefined;
|
||||||
if (!currentConnection) {
|
if (!currentConnection) {
|
||||||
const connection = startTLSServer();
|
const connection = startTLSServer();
|
||||||
@ -71,12 +103,15 @@ export class EAPTTLS implements IEAPType {
|
|||||||
// pwd not found..
|
// pwd not found..
|
||||||
console.error('pwd not found', err);
|
console.error('pwd not found', err);
|
||||||
// NAK
|
// NAK
|
||||||
|
currentConnection!.currentHandlers.response(undefined, 3);
|
||||||
|
|
||||||
|
/*
|
||||||
this.sendEAPResponse(
|
this.sendEAPResponse(
|
||||||
currentConnection!.currentHandlers.response,
|
currentConnection!.currentHandlers.response,
|
||||||
identifier,
|
identifier,
|
||||||
undefined,
|
undefined,
|
||||||
3
|
3
|
||||||
);
|
); */
|
||||||
currentConnection!.events.emit('end');
|
currentConnection!.events.emit('end');
|
||||||
throw new Error(`pwd not found`);
|
throw new Error(`pwd not found`);
|
||||||
}
|
}
|
||||||
@ -85,16 +120,28 @@ export class EAPTTLS implements IEAPType {
|
|||||||
console.log('data', incomingData);
|
console.log('data', incomingData);
|
||||||
console.log('data str', incomingData.toString());
|
console.log('data str', incomingData.toString());
|
||||||
|
|
||||||
currentConnection!.events.emit('end');
|
// currentConnection!.events.emit('end');
|
||||||
throw new Error(`unsupported auth type${type}`);
|
|
||||||
|
console.log('UNSUPPORTED AUTH TYPE, requesting PAP');
|
||||||
|
// throw new Error(`unsupported auth type${type}`);
|
||||||
|
currentConnection!.currentHandlers.response(Buffer.from([1]), 3);
|
||||||
|
|
||||||
|
/*
|
||||||
|
this.sendEAPResponse(
|
||||||
|
currentConnection!.currentHandlers.response,
|
||||||
|
identifier,
|
||||||
|
Buffer.from([1]),
|
||||||
|
3
|
||||||
|
); */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
currentConnection.events.on('response', (responseData: Buffer) => {
|
currentConnection.events.on('response', (responseData: Buffer) => {
|
||||||
console.log('sending encrypted data back to client', responseData);
|
// console.log('sending encrypted data back to client', responseData);
|
||||||
|
|
||||||
// send back...
|
// send back...
|
||||||
this.sendEAPResponse(currentConnection!.currentHandlers.response, identifier, responseData);
|
currentConnection!.currentHandlers.response(responseData);
|
||||||
|
// this.sendEAPResponse(currentConnection!.currentHandlers.response, identifier, responseData);
|
||||||
// this.sendMessage(TYPE.PRELOGIN, data, false);
|
// this.sendMessage(TYPE.PRELOGIN, data, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -103,13 +150,14 @@ export class EAPTTLS implements IEAPType {
|
|||||||
console.log('ENDING SOCKET');
|
console.log('ENDING SOCKET');
|
||||||
openTLSSockets.del(state);
|
openTLSSockets.del(state);
|
||||||
});
|
});
|
||||||
} else {
|
} /* else {
|
||||||
console.log('using existing socket');
|
console.log('using existing socket');
|
||||||
}
|
} */
|
||||||
|
|
||||||
// update handlers
|
// update handlers
|
||||||
currentConnection.currentHandlers = {
|
currentConnection.currentHandlers = {
|
||||||
...handlers,
|
response: (respData?: Buffer, msgType?: number) =>
|
||||||
|
this.sendEAPResponse(handlers.response, identifier, respData, msgType),
|
||||||
checkAuth: (username: string, password: string) => {
|
checkAuth: (username: string, password: string) => {
|
||||||
const additionalAuthHandler: AdditionalAuthHandler = (success, params) => {
|
const additionalAuthHandler: AdditionalAuthHandler = (success, params) => {
|
||||||
const buffer = Buffer.from([
|
const buffer = Buffer.from([
|
||||||
@ -138,7 +186,7 @@ export class EAPTTLS implements IEAPType {
|
|||||||
'ttls keying material'
|
'ttls keying material'
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('keyingMaterial', keyingMaterial);
|
// console.log('keyingMaterial', keyingMaterial);
|
||||||
|
|
||||||
// eapKeyData + len
|
// eapKeyData + len
|
||||||
params.attributes.push([
|
params.attributes.push([
|
||||||
|
@ -7,3 +7,33 @@ export function makeid(length) {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MAX_RADIUS_ATTRIBUTE_SIZE = 253;
|
||||||
|
|
||||||
|
export interface IDeferredPromise {
|
||||||
|
promise: Promise<any>;
|
||||||
|
resolve: (value?: unknown) => Promise<void>;
|
||||||
|
reject: (reason?: any) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const newDeferredPromise = (): IDeferredPromise => {
|
||||||
|
if (Promise && !('deferred' in Promise)) {
|
||||||
|
let fResolve;
|
||||||
|
let fReject;
|
||||||
|
|
||||||
|
const P = new Promise((resolve, reject) => {
|
||||||
|
fResolve = resolve;
|
||||||
|
fReject = reject;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
promise: P,
|
||||||
|
resolve: fResolve,
|
||||||
|
reject: fReject
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Promise as any).deferred;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const delay = (timeout: number) =>
|
||||||
|
new Promise(resolve => setTimeout(() => resolve(), timeout));
|
||||||
|
@ -5,14 +5,21 @@ import { createSecureContext } from 'tls';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import * as DuplexPair from 'native-duplexpair';
|
import * as DuplexPair from 'native-duplexpair';
|
||||||
|
import * as constants from 'constants';
|
||||||
import { makeid } from '../helpers';
|
import { makeid } from '../helpers';
|
||||||
|
import * as config from '../../config';
|
||||||
|
|
||||||
// https://nodejs.org/api/tls.html
|
// https://nodejs.org/api/tls.html
|
||||||
const tlsOptions = {
|
const tlsOptions: tls.SecureContextOptions = {
|
||||||
cert: fs.readFileSync('./ssl/public-cert.pem'),
|
...config.certificate,
|
||||||
key: fs.readFileSync('./ssl/private-key.pem'),
|
// ca: fs.readFileSync('./ssl/server.pem'),
|
||||||
ecdhCurve: 'auto'
|
// eslint-disable-next-line no-bitwise
|
||||||
|
secureOptions: constants.SSL_OP_NO_TICKET // : constants.SSL_OP_NO_TLSv1_2 | constants.SSL_OP_NO_TLSv1_1,
|
||||||
|
// honorCipherOrder: true
|
||||||
|
// secureOptions:
|
||||||
|
// ecdhCurve: 'auto'
|
||||||
};
|
};
|
||||||
|
console.log('tlsOptions', tlsOptions);
|
||||||
const secureContext = createSecureContext(tlsOptions);
|
const secureContext = createSecureContext(tlsOptions);
|
||||||
export const openTLSSockets = new NodeCache({ useClones: false, stdTTL: 3600 }); // keep sockets for about one hour
|
export const openTLSSockets = new NodeCache({ useClones: false, stdTTL: 3600 }); // keep sockets for about one hour
|
||||||
|
|
||||||
@ -22,7 +29,11 @@ export function startTLSServer(): { events: events.EventEmitter; tls: tls.TLSSoc
|
|||||||
|
|
||||||
const cleartext = new tls.TLSSocket(duplexpair.socket1, {
|
const cleartext = new tls.TLSSocket(duplexpair.socket1, {
|
||||||
secureContext,
|
secureContext,
|
||||||
isServer: true
|
isServer: true,
|
||||||
|
enableTrace: true,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
// handshakeTimeout: 10,
|
||||||
|
requestCert: false
|
||||||
});
|
});
|
||||||
const encrypted = duplexpair.socket2;
|
const encrypted = duplexpair.socket2;
|
||||||
|
|
||||||
@ -71,6 +82,7 @@ export function startTLSServer(): { events: events.EventEmitter; tls: tls.TLSSoc
|
|||||||
console.log('*********** new client connection established / secured ********');
|
console.log('*********** new client connection established / secured ********');
|
||||||
// this.emit('secure', securePair.cleartext);
|
// this.emit('secure', securePair.cleartext);
|
||||||
// this.encryptAllFutureTraffic();
|
// this.encryptAllFutureTraffic();
|
||||||
|
console.log('GET FINSIHED', cleartext.getFinished());
|
||||||
});
|
});
|
||||||
|
|
||||||
cleartext.on('error', (err?: Error) => {
|
cleartext.on('error', (err?: Error) => {
|
||||||
|
3
src/types/Authentication.ts
Normal file
3
src/types/Authentication.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface IAuthentication {
|
||||||
|
authenticate(username: string, password: string): Promise<string>;
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
export interface IAuthChallenge {
|
export interface IEAPChallenge {
|
||||||
decode(data: Buffer): { username: string; password: string };
|
decode(data: Buffer): { username: string; password: string };
|
||||||
}
|
}
|
@ -1,3 +1,3 @@
|
|||||||
export interface IEAPType {
|
export interface IEAPType {
|
||||||
handleMessage(msg: Buffer, state: string, handlers, identifier: number)
|
handleMessage(msg: Buffer, state: string, handlers, identifier: number);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { RadiusPacket } from 'radius';
|
import { RadiusPacket } from 'radius';
|
||||||
|
|
||||||
export type ResponseHandler = (msg: Buffer) => void;
|
export type ResponseHandler = (
|
||||||
|
msg: Buffer
|
||||||
|
) => Promise<{ identifier: number; response: ResponseHandler }>;
|
||||||
export type ResponseAuthHandler = (
|
export type ResponseAuthHandler = (
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
8
ssl/README.md
Normal file
8
ssl/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
this is based on freeradius cert directory :)
|
||||||
|
|
||||||
|
1.) edit ca.cnf
|
||||||
|
2.) edit server.cnf
|
||||||
|
3.) replace your choosen pwd (deafult is whatever2020) in create.sh
|
||||||
|
4.) run ./create.sh
|
||||||
|
5.) set your choosen pwd in ~/config.js
|
||||||
|
|
61
ssl/ca.cnf
Normal file
61
ssl/ca.cnf
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
[ ca ]
|
||||||
|
default_ca = CA_default
|
||||||
|
|
||||||
|
[ CA_default ]
|
||||||
|
dir = ./cert/
|
||||||
|
certs = $dir
|
||||||
|
crl_dir = $dir/crl
|
||||||
|
database = $dir/index.txt
|
||||||
|
new_certs_dir = $dir
|
||||||
|
certificate = $dir/ca.pem
|
||||||
|
serial = $dir/serial
|
||||||
|
crl = $dir/crl.pem
|
||||||
|
private_key = $dir/ca.key
|
||||||
|
RANDFILE = $dir/.rand
|
||||||
|
name_opt = ca_default
|
||||||
|
cert_opt = ca_default
|
||||||
|
default_days = 60
|
||||||
|
default_crl_days = 30
|
||||||
|
default_md = sha256
|
||||||
|
preserve = no
|
||||||
|
policy = policy_match
|
||||||
|
crlDistributionPoints = URI:http://www.example.org/example_ca.crl
|
||||||
|
|
||||||
|
[ policy_match ]
|
||||||
|
countryName = match
|
||||||
|
stateOrProvinceName = match
|
||||||
|
organizationName = match
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
|
||||||
|
[ policy_anything ]
|
||||||
|
countryName = optional
|
||||||
|
stateOrProvinceName = optional
|
||||||
|
localityName = optional
|
||||||
|
organizationName = optional
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
|
||||||
|
[ req ]
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = certificate_authority
|
||||||
|
default_bits = 2048
|
||||||
|
input_password = whatever2020
|
||||||
|
output_password = whatever2020
|
||||||
|
x509_extensions = v3_ca
|
||||||
|
|
||||||
|
[certificate_authority]
|
||||||
|
countryName = AT
|
||||||
|
stateOrProvinceName = Vienna
|
||||||
|
localityName = Vienna
|
||||||
|
organizationName = hokify.com
|
||||||
|
emailAddress = info@hokify.com
|
||||||
|
commonName = "hokify GmbH"
|
||||||
|
|
||||||
|
[v3_ca]
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always,issuer:always
|
||||||
|
basicConstraints = critical,CA:true
|
||||||
|
crlDistributionPoints = URI:http://www.example.org/example_ca.crl
|
0
ssl/cert/index.txt
Normal file
0
ssl/cert/index.txt
Normal file
1
ssl/cert/serial
Normal file
1
ssl/cert/serial
Normal file
@ -0,0 +1 @@
|
|||||||
|
00
|
@ -1,2 +1,15 @@
|
|||||||
openssl genrsa -out private-key.pem 1024
|
# generate private key
|
||||||
openssl req -new -key private-key.pem -out csr.pem
|
# openssl genrsa -out csr.key 2048
|
||||||
|
|
||||||
|
# CA
|
||||||
|
openssl req -new -x509 -keyout ca.key -out ca.pem -days 3600 -config ./ca.cnf
|
||||||
|
|
||||||
|
# server
|
||||||
|
openssl req -new -out server.csr -keyout server.key -config ./server.cnf
|
||||||
|
|
||||||
|
# sign it
|
||||||
|
# -key $(PASSWORD_CA) (default pwd is whatever2020)
|
||||||
|
openssl ca -batch -keyfile ca.key -cert ca.pem -in server.csr -key whatever2020 -out server.crt -extensions xpserver_ext -extfile xpextensions -config ./server.cnf
|
||||||
|
|
||||||
|
# sign it
|
||||||
|
# openssl x509 -req -in csr.pem -signkey private-key.pem -out public-cert.pem
|
||||||
|
54
ssl/server.cnf
Normal file
54
ssl/server.cnf
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
[ ca ]
|
||||||
|
default_ca = CA_default
|
||||||
|
|
||||||
|
[ CA_default ]
|
||||||
|
dir = ./cert/
|
||||||
|
certs = $dir
|
||||||
|
crl_dir = $dir/crl
|
||||||
|
database = $dir/index.txt
|
||||||
|
new_certs_dir = $dir
|
||||||
|
certificate = $dir/server.pem
|
||||||
|
serial = $dir/serial
|
||||||
|
crl = $dir/crl.pem
|
||||||
|
private_key = $dir/server.key
|
||||||
|
RANDFILE = $dir/.rand
|
||||||
|
name_opt = ca_default
|
||||||
|
cert_opt = ca_default
|
||||||
|
default_days = 6000
|
||||||
|
default_crl_days = 30
|
||||||
|
default_md = sha256
|
||||||
|
preserve = no
|
||||||
|
policy = policy_match
|
||||||
|
|
||||||
|
[ policy_match ]
|
||||||
|
countryName = match
|
||||||
|
stateOrProvinceName = match
|
||||||
|
organizationName = match
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
|
||||||
|
[ policy_anything ]
|
||||||
|
countryName = optional
|
||||||
|
stateOrProvinceName = optional
|
||||||
|
localityName = optional
|
||||||
|
organizationName = optional
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
|
||||||
|
[ req ]
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = server
|
||||||
|
default_bits = 2048
|
||||||
|
input_password = whatever2020
|
||||||
|
output_password = whatever2020
|
||||||
|
|
||||||
|
[server]
|
||||||
|
countryName = AT
|
||||||
|
stateOrProvinceName = Vienna
|
||||||
|
localityName = Vienna
|
||||||
|
organizationName = hokify.com
|
||||||
|
emailAddress = info@hokify.com
|
||||||
|
commonName = "hokify GmbH"
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
openssl x509 -req -in csr.pem -signkey private-key.pem -out public-cert.pem
|
|
24
ssl/xpextensions
Normal file
24
ssl/xpextensions
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#
|
||||||
|
# File containing the OIDs required for Windows.
|
||||||
|
#
|
||||||
|
# http://support.microsoft.com/kb/814394/en-us
|
||||||
|
#
|
||||||
|
[ xpclient_ext]
|
||||||
|
extendedKeyUsage = 1.3.6.1.5.5.7.3.2
|
||||||
|
crlDistributionPoints = URI:http://www.example.com/example_ca.crl
|
||||||
|
|
||||||
|
[ xpserver_ext]
|
||||||
|
extendedKeyUsage = 1.3.6.1.5.5.7.3.1
|
||||||
|
crlDistributionPoints = URI:http://www.example.com/example_ca.crl
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add this to the PKCS#7 keybag attributes holding the client's private key
|
||||||
|
# for machine authentication.
|
||||||
|
#
|
||||||
|
# the presence of this OID tells Windows XP that the cert is intended
|
||||||
|
# for use by the computer itself, and not by an end-user.
|
||||||
|
#
|
||||||
|
# The other solution is to use Microsoft's web certificate server
|
||||||
|
# to generate these certs.
|
||||||
|
#
|
||||||
|
# 1.3.6.1.4.1.311.17.2
|
Loading…
Reference in New Issue
Block a user