Browse Source

refactor: major code cleanups :-)

first working version
master
simon 2 years ago
parent
commit
42a05b069f
  1. 8
      .gitignore
  2. 57
      README.md
  3. 11
      config.js
  4. 228
      package-lock.json
  5. 59
      package.json
  6. 210
      src/app.ts
  7. 4
      src/auth/google-ldap.ts
  8. 219
      src/eap.ts
  9. 233
      src/eap/eap-ttls.ts
  10. 2
      src/helpers.ts
  11. 95
      src/radius/RadiusService.ts
  12. 36
      src/radius/handler/DefaultPacketHandler.ts
  13. 193
      src/radius/handler/EAPPacketHandler.ts
  14. 460
      src/radius/handler/eapMethods/EAPTTLS.ts
  15. 2
      src/radius/handler/eapMethods/challenges/PAPChallenge.ts
  16. 80
      src/server/UDPServer.ts
  17. 12
      src/tls/crypt.ts
  18. 2
      src/types/Authentication.ts
  19. 15
      src/types/EAPMethod.ts
  20. 3
      src/types/EAPType.ts
  21. 19
      src/types/PacketHandler.ts
  22. 32
      src/types/Server.ts
  23. 12
      ssl/README.md
  24. 98
      ssl/cert/00.pem
  25. 30
      ssl/cert/ca.key
  26. 36
      ssl/cert/ca.pem
  27. 80
      ssl/cert/server.crt
  28. 24
      ssl/cert/server.csr
  29. 30
      ssl/cert/server.key
  30. 2
      ssl/db/index.txt
  31. 2
      tsconfig.json

8
.gitignore

@ -4,10 +4,8 @@ node_modules/*
# build files
dist
# certificates
ssl/*.pem
*.crt
*.key
# ts
tsconfig.tsbuildinfo
# custom certificates
/ssl-*/

57
README.md

@ -1,17 +1,18 @@
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)
- supports LDAP Authentification Backend
- supports WPA2 Entprise (TTLS over PAP)
Protect your WIFI access with a username and password by a credential provider you already use!
## Known Issues / Disclaimer
This is a first implementation draft, which is currently only working with a nodejs fork (see https://github.com/nodejs/node/pull/31814).
* 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
- PAP / CHAP RFC not found to implement this correctly
- a lot of bugs
CONTRIBUTIONS WELCOME!
CONTRIBUTIONS WELCOME! If you are willing to help, just open a PR or contact me via bug system or simon.tretter@hokify.com.
## Installation
@ -21,17 +22,39 @@ CONTRIBUTIONS WELCOME!
## 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
you need:
1. Running LDAP Service (E.g. Google Suite Enterprise or Gloud Identity Premium)
2. Optional: Create your own SSL certificate (e.g. self signed via npm run create-certificate)
3. Check config.js and adapt to your needs
- configure authentication (passport config), e.g. for LDAP
```js
var config = {
// ....
authentication: 'ldap',
authenticationOptions: {
url: 'ldap://127.0.0.1:1636',
base: 'dc=hokify,dc=com'
}
};
```
- set radius secret
4. Install und build server: npm install && npm run build
5. Start server "npm run start"
## Authentications
right now only one simple ldap implementation is done,
the idea is though to use [passport.js](http://www.passportjs.org/) as authentication provider,
therefore it would be possible to use the radius server with your email provider authentication or any
other auth mechanismus you use (well everything with no 2factor or anything else that requries an extra step).
## 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
npm run start

11
config.js

@ -1,14 +1,19 @@
import * as fs from 'fs';
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const path = require('path');
const SSL_CERT_DIRECTORY = './ssl/cert';
module.exports = {
port: 1812,
// radius secret
secret: 'testing123',
certificate: {
cert: fs.readFileSync('./ssl/cert/server.crt'),
cert: fs.readFileSync(path.join(SSL_CERT_DIRECTORY, '/server.crt')),
key: [
{
pem: fs.readFileSync('./ssl/cert/server.key'),
pem: fs.readFileSync(path.join(SSL_CERT_DIRECTORY, '/server.key')),
passphrase: 'whatever2020'
}
]

228
package-lock.json

@ -34,13 +34,13 @@
}
},
"@hokify/eslint-config": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@hokify/eslint-config/-/eslint-config-0.2.2.tgz",
"integrity": "sha512-FvVZd8hruAriQ84UR0MqxRtXXUT9Eqe9LTGoU90sQ6TgHXTGPQpOfY+8DAICX0HtRc5ntB/ZEgL6L9ims4NLxA==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@hokify/eslint-config/-/eslint-config-0.2.4.tgz",
"integrity": "sha512-wUiJ1mBc+6TEqqB1qK0LsaMazg6h19MwA4jlMn4Qltgiql7S2EmWDSK0ID9wCmI4bgUB6WqZsNQqvkMxNG7hYw==",
"dev": true,
"requires": {
"@typescript-eslint/eslint-plugin": "^2.19.2",
"@typescript-eslint/parser": "^2.19.2",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",
"eslint-config-airbnb-typescript": "^7.0.0",
"eslint-config-prettier": "^6.10.0",
"eslint-import-resolver-alias": "^1.1.2",
@ -50,11 +50,20 @@
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-mocha": "^6.2.2",
"eslint-plugin-mocha": "^6.3.0",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.18.3",
"eslint-plugin-unicorn": "^16.0.0"
"eslint-plugin-unicorn": "^16.1.1"
}
},
"@types/body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/color-name": {
@ -62,12 +71,39 @@
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
},
"@types/connect": {
"version": "3.4.33",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
"requires": {
"@types/node": "*"
}
},
"@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
"dev": true
},
"@types/express": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
"integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz",
"integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==",
"requires": {
"@types/node": "*",
"@types/range-parser": "*"
}
},
"@types/ioredis": {
"version": "4.14.7",
"resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.14.7.tgz",
@ -92,11 +128,15 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-1.0.5.tgz",
"integrity": "sha512-hrtMZjVfWNPxkwFkYhLAU0ITZ3/reDft4jDzLBvrGDSKCbEvW+GeZb4PgM3jlSwSsv0cXqnDeWcupFLIgW9E0Q==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/mime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
},
"@types/node": {
"version": "13.7.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz",
@ -116,6 +156,14 @@
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
"dev": true
},
"@types/passport": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
"integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
"requires": {
"@types/express": "*"
}
},
"@types/radius": {
"version": "0.0.28",
"resolved": "https://registry.npmjs.org/@types/radius/-/radius-0.0.28.tgz",
@ -125,6 +173,11 @@
"@types/node": "*"
}
},
"@types/range-parser": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
},
"@types/redis": {
"version": "2.8.15",
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.15.tgz",
@ -133,13 +186,22 @@
"@types/node": "*"
}
},
"@types/serve-static": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
"requires": {
"@types/express-serve-static-core": "*",
"@types/mime": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.19.2.tgz",
"integrity": "sha512-HX2qOq2GOV04HNrmKnTpSIpHjfl7iwdXe3u/Nvt+/cpmdvzYvY0NHSiTkYN257jHnq4OM/yo+OsFgati+7LqJA==",
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.20.0.tgz",
"integrity": "sha512-cimIdVDV3MakiGJqMXw51Xci6oEDEoPkvh8ggJe2IIzcc0fYqAxOXN6Vbeanahz6dLZq64W+40iUEc9g32FLDQ==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "2.19.2",
"@typescript-eslint/experimental-utils": "2.20.0",
"eslint-utils": "^1.4.3",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
@ -147,32 +209,32 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.2.tgz",
"integrity": "sha512-B88QuwT1wMJR750YvTJBNjMZwmiPpbmKYLm1yI7PCc3x0NariqPwqaPsoJRwU9DmUi0cd9dkhz1IqEnwfD+P1A==",
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.20.0.tgz",
"integrity": "sha512-fEBy9xYrwG9hfBLFEwGW2lKwDRTmYzH3DwTmYbT+SMycmxAoPl0eGretnBFj/s+NfYBG63w/5c3lsvqqz5mYag==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "2.19.2",
"@typescript-eslint/typescript-estree": "2.20.0",
"eslint-scope": "^5.0.0"
}
},
"@typescript-eslint/parser": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.19.2.tgz",
"integrity": "sha512-8uwnYGKqX9wWHGPGdLB9sk9+12sjcdqEEYKGgbS8A0IvYX59h01o8os5qXUHMq2na8vpDRaV0suTLM7S8wraTA==",
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.20.0.tgz",
"integrity": "sha512-o8qsKaosLh2qhMZiHNtaHKTHyCHc3Triq6aMnwnWj7budm3xAY9owSZzV1uon5T9cWmJRJGzTFa90aex4m77Lw==",
"dev": true,
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "2.19.2",
"@typescript-eslint/typescript-estree": "2.19.2",
"@typescript-eslint/experimental-utils": "2.20.0",
"@typescript-eslint/typescript-estree": "2.20.0",
"eslint-visitor-keys": "^1.1.0"
}
},
"@typescript-eslint/typescript-estree": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.2.tgz",
"integrity": "sha512-Xu/qa0MDk6upQWqE4Qy2X16Xg8Vi32tQS2PR0AvnT/ZYS4YGDvtn2MStOh5y8Zy2mg4NuL06KUHlvCh95j9C6Q==",
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.20.0.tgz",
"integrity": "sha512-WlFk8QtI8pPaE7JGQGxU7nGcnk1ccKAJkhbVookv94ZcAef3m6oCE/jEDL6dGte3JcD7reKrA0o55XhBRiVT3A==",
"dev": true,
"requires": {
"debug": "^4.1.1",
@ -389,6 +451,11 @@
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
"dev": true
},
"bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@ -896,9 +963,9 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
"enhanced-resolve": {
@ -1361,12 +1428,24 @@
}
},
"eslint-plugin-mocha": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz",
"integrity": "sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz",
"integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==",
"dev": true,
"requires": {
"ramda": "^0.26.1"
"eslint-utils": "^2.0.0",
"ramda": "^0.27.0"
},
"dependencies": {
"eslint-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
"integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
}
}
},
"eslint-plugin-node": {
@ -2159,6 +2238,25 @@
}
}
},
"ldapauth-fork": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-4.3.1.tgz",
"integrity": "sha512-IJUnkEDQg6D45jUKW3FFfMWZhUjZoGkN97WaMXF1cBod0gJq74d+iwRavPqiE3o/KNRgqwFesrdE4Ym4Fc1GIQ==",
"requires": {
"@types/ldapjs": "^1.0.0",
"@types/node": "^10.12.12",
"bcryptjs": "^2.4.0",
"ldapjs": "^1.0.2",
"lru-cache": "^5.1.1"
},
"dependencies": {
"@types/node": {
"version": "10.17.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.16.tgz",
"integrity": "sha512-A4283YSA1OmnIivcpy/4nN86YlnKRiQp8PYwI2KdPCONEBN093QTb0gCtERtkLyVNGKKIGazTZ2nAmVzQU51zA=="
}
}
},
"ldapjs": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-1.0.2.tgz",
@ -2215,13 +2313,13 @@
}
},
"loader-utils": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
"integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
"integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^2.0.0",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
}
},
@ -2290,6 +2388,14 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": {
"yallist": "^3.0.2"
}
},
"make-error": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
@ -2509,9 +2615,9 @@
"dev": true
},
"object-hash": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.2.tgz",
"integrity": "sha512-b+2AKjAf6uQlxxv8ChHdM+VT4eeX+ZSwv+pk2xIXZWbo+yxn4/En1iC+GHe/OFYa9on0AhFF2PvuAcFHoiiHaA==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz",
"integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==",
"dev": true
},
"object-inspect": {
@ -2676,6 +2782,29 @@
"error-ex": "^1.2.0"
}
},
"passport-ldapauth": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-2.1.3.tgz",
"integrity": "sha512-23n425UTasN6XhcXG0qQ0h0YrS/zfo8kNIEhSLfPsNpglhYhhQFfB1pmDc5RrH+Kiz5fKLkki5BpvkKHCwkixg==",
"requires": {
"@types/node": "^10.12.26",
"@types/passport": "^1.0.0",
"ldapauth-fork": "^4.2.0",
"passport-strategy": "^1.0.0"
},
"dependencies": {
"@types/node": {
"version": "10.17.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.16.tgz",
"integrity": "sha512-A4283YSA1OmnIivcpy/4nN86YlnKRiQp8PYwI2KdPCONEBN093QTb0gCtERtkLyVNGKKIGazTZ2nAmVzQU51zA=="
}
}
},
"passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
},
"path-browserify": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
@ -2870,9 +2999,9 @@
"integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw=="
},
"ramda": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
"integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz",
"integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==",
"dev": true
},
"randombytes": {
@ -3011,9 +3140,9 @@
"dev": true
},
"regexp-tree": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.19.tgz",
"integrity": "sha512-mVeVLF/qg5qFbZSW0f7pXbuVX73dKIjuhOyC2JLKxzmpya75O4qLcvI9j0jp31Iz7IAkEVHa1UErDCAqaLKo5A==",
"version": "0.1.20",
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.20.tgz",
"integrity": "sha512-gSiH74kc00oTbQkN7tZogZe0ttKwyxyDVLAnU20aWoarbLE9AypbJHRlZ567h4Zi19q3cPVRWDYbfECElrHgsQ==",
"dev": true
},
"regexp.prototype.flags": {
@ -3587,9 +3716,9 @@
"dev": true
},
"typescript": {
"version": "3.7.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz",
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.2.tgz",
"integrity": "sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==",
"dev": true
},
"universalify": {
@ -3761,6 +3890,11 @@
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"yargs": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz",

59
package.json

@ -1,31 +1,32 @@
{
"name": "radius-server",
"description": "radius server for google LDAP and TTLT",
"version": "0.0.1",
"scripts": {
"start": "/home/simon/Dev/others/node/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",
"passport-ldapauth": "^2.1.3"
},
"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"
}
"name": "radius-server",
"description": "radius server for google LDAP and TTLT",
"version": "0.0.1",
"scripts": {
"start": "/home/simon/Dev/others/node/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",
"passport-ldapauth": "^2.1.3"
},
"license": "GPLv3",
"devDependencies": {
"@types/ldapjs": "^1.0.5",
"@types/radius": "0.0.28",
"@hokify/eslint-config": "^0.2.4",
"eslint": "^6.8.0",
"prettier": "^1.19.1",
"typescript": "^3.8.2"
}
}

210
src/app.ts

@ -1,186 +1,42 @@
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 { IDeferredPromise, makeid, MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from './helpers';
import { GoogleLDAPAuth } from './auth/google-ldap';
import { AdditionalAuthHandler } from './types/Handler';
const server = dgram.createSocket('udp4');
import { UDPServer } from './server/UDPServer';
import { RadiusService } from './radius/RadiusService';
const { argv } = require('yargs')
.usage('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');
import * as config from '../config';
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}`);
console.log(`Listener Port: ${config.port || 1812}`);
console.log(`RADIUS Secret: ${config.secret}`);
console.log(`Auth Mode: ${config.authentication}`);
// const ldap = new LDAPAuth({url: 'ldap://ldap.google.com', base: 'dc=hokify,dc=com', uid: 'uid', tlsOptions});
const ldap = new GoogleLDAPAuth(argv.ldapServer, argv.baseDN);
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) {
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,
additionalAuthHandler?: AdditionalAuthHandler
) {
console.log(`Access-Request for ${username}`);
let success = false;
try {
await ldap.authenticate(username, password);
success = true;
} catch (err) {
console.error(err);
}
const attributes: any[] = [];
if (additionalAuthHandler) {
await additionalAuthHandler(success, { packet, attributes, secret: argv.secret });
}
const response = radius.encode_response({
packet,
code: success ? 'Access-Accept' : 'Access-Reject',
secret: argv.secret,
attributes
});
console.log(`Sending ${success ? 'accept' : 'reject'} for user ${username}`);
sendToClient(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);
if (timeout[state]) {
clearTimeout(timeout[state]);
}
const handlers = {
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 + MAX_RADIUS_ATTRIBUTE_SIZE)
]);
sentDataSize += MAX_RADIUS_ATTRIBUTE_SIZE;
const ldap = new GoogleLDAPAuth(
config.authenticationOptions.url,
config.authenticationOptions.base
);
const server = new UDPServer(config.port);
const radiusService = new RadiusService(config.secret, ldap);
(async () => {
server.on('message', async (msg, rinfo) => {
const response = await radiusService.handleMessage(msg);
if (response) {
server.sendToClient(
response.data,
rinfo.port,
rinfo.address,
(err, _bytes) => {
if (err) {
console.log('Error sending response to ', rinfo);
}
} while (sentDataSize < EAPMessage.length);
const response = radius.encode_response({
packet,
code: 'Access-Challenge',
secret: argv.secret,
attributes
});
waitForNextMsg[state] = newDeferredPromise();
sendToClient(
response,
0,
response.length,
rinfo.port,
rinfo.address,
function(err, _bytes) {
if (err) {
console.log('Error sending response to ', rinfo);
}
},
state
);
return waitForNextMsg[state].promise;
},
checkAuth
};
if (waitForNextMsg[state]) {
const identifier = packet.attributes['EAP-Message'].slice(1, 2).readUInt8(0); // .toString('hex');
waitForNextMsg[state].resolve({ response: handlers.response, identifier });
},
response.expectAcknowledgment
);
}
});
// EAP MESSAGE
eapHandler.handleEAPMessage(packet.attributes['EAP-Message'], state, handlers);
} 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);
// start server
await server.start();
})();

4
src/auth/google-ldap.ts

@ -1,6 +1,6 @@
import * as NodeCache from 'node-cache';
import { createClient, Client } from 'ldapjs';
import { Client, createClient } from 'ldapjs';
import { IAuthentication } from '../types/Authentication';
const usernameFields = ['posixUid', 'mail'];
@ -58,7 +58,7 @@ export class GoogleLDAPAuth implements IAuthentication {
});
res.on('end', result => {
console.log(`status: ${result?.status}`);
console.log(`ldap status: ${result?.status}`);
// replace with new dns
this.allValidDNsCache = dns;

219
src/eap.ts

@ -1,219 +0,0 @@
// 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';
import { MAX_RADIUS_ATTRIBUTE_SIZE } from './helpers';
export class EAPHandler {
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 async sendEAPResponse(
response: ResponseHandler,
identifier: number,
data?: Buffer,
msgType = 21,
msgFlags = 0x00
) {
let i = 0;
const maxSize = (MAX_RADIUS_ATTRIBUTE_SIZE - 5) * 4;
let sentDataSize = 0;
let currentIdentifier = identifier;
let currentResponse = response;
do {
// SLICE
// const fragmentMaxPart =
// data && (i + 1) * maxFragmentSize > data.length ? (i + 1) * maxFragmentSize : undefined;
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;
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
(data && sentDataSize < data.length /* we have more */ ? 0b01000000 : 0); // set M bit
currentIdentifier++;
let buffer = Buffer.from([
1, // request
currentIdentifier,
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 = dataPart ? Buffer.concat([buffer, dataPart]) : buffer;
resBuffer.writeUInt16BE(resBuffer.byteLength, 2);
console.log('<<<<<<<<<<<< EAP RESPONSE TO CLIENT', {
code: 1,
currentIdentifier,
includeLength,
length: (data && data.byteLength) || 0,
msgType: msgType.toString(10),
flags: `00000000${flags.toString(2)}`.substr(-8),
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
console.log('sending message with identifier', currentIdentifier);
({ identifier: currentIdentifier, response: currentResponse } = await currentResponse(
resBuffer
));
console.log('next message got identifier', currentIdentifier);
} while (data && sentDataSize < data.length);
console.log('DONE', sentDataSize, data && 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('>>>>>>>>>>>> REQUEST FROM CLIENT: IDENTIFY', {});
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);
break;
case 3: // nak
// this.sendEAPResponse(handlers.response, identifier, undefined, 3);
break;
case 2: // notification
console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
console.info('notification');
break;
case 4: // md5-challenge
console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {});
console.info('md5-challenge');
break;
case 254: // expanded type
console.error('not implemented type', type);
break;
default:
// 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;
case 3:
console.log('Client Auth Success');
break;
case 4:
console.log('Client Auth FAILURE');
break;
default:
break;
// silently ignor;
}
}
}

233
src/eap/eap-ttls.ts

@ -1,233 +0,0 @@
/* eslint-disable no-bitwise */
import * as events from 'events';
import * as tls from 'tls';
import { encodeTunnelPW, openTLSSockets, startTLSServer } from '../tls/crypt';
import { AdditionalAuthHandler, ResponseAuthHandler } from '../types/Handler';
import { PAPChallenge } from './challenges/pap';
import { IEAPType } from '../types/EAPType';
interface IEAPResponseHandlers {
response: (respData?: Buffer, msgType?: number) => void;
checkAuth: ResponseAuthHandler;
}
export class EAPTTLS implements IEAPType {
papChallenge: PAPChallenge;
constructor(private sendEAPResponse) {
this.papChallenge = new PAPChallenge();
}
decode(msg: Buffer) {
const flags = msg.slice(5, 6).readUInt8(0); // .toString('hex');
// if (flags)
// @todo check if "L" flag is set in flags
const decodedFlags = {
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
if (!data || data.length === 0) {
// @todo: queue processing
console.warn(
`>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS, ACK / NACK (no data, just a confirmation, ID: ${identifier})`
);
return;
}
console.log('>>>>>>>>>>>> REQUEST FROM CLIENT: EAP TTLS', {
// flags: `00000000${flags.toString(2)}`.substr(-8),
decodedFlags,
identifier,
/*
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 currentConnection = openTLSSockets.get(state) as
| { events: events.EventEmitter; tls: tls.TLSSocket; currentHandlers: IEAPResponseHandlers }
| undefined;
if (!currentConnection) {
const connection = startTLSServer();
currentConnection = {
events: connection.events,
tls: connection.tls,
currentHandlers: handlers
};
openTLSSockets.set(state, currentConnection);
// register event listeners
currentConnection.events.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);
currentConnection!.currentHandlers.checkAuth(username, password);
} catch (err) {
// pwd not found..
console.error('pwd not found', err);
// NAK
currentConnection!.currentHandlers.response(undefined, 3);
/*
this.sendEAPResponse(
currentConnection!.currentHandlers.response,
identifier,
undefined,
3
); */
currentConnection!.events.emit('end');
throw new Error(`pwd not found`);
}
break;
default:
console.log('data', incomingData);
console.log('data str', incomingData.toString());
// currentConnection!.events.emit('end');
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) => {
// console.log('sending encrypted data back to client', responseData);
// send back...
currentConnection!.currentHandlers.response(responseData);
// this.sendEAPResponse(currentConnection!.currentHandlers.response, identifier, responseData);
// this.sendMessage(TYPE.PRELOGIN, data, false);
});
currentConnection.events.on('end', () => {
// cleanup socket
console.log('ENDING SOCKET');
openTLSSockets.del(state);
});
} /* else {
console.log('using existing socket');
} */
// update handlers
currentConnection.currentHandlers = {
response: (respData?: Buffer, msgType?: number) =>
this.sendEAPResponse(handlers.response, identifier, respData, msgType),
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
currentConnection.events.emit('send', data);
}
}

2
src/helpers.ts

@ -8,6 +8,8 @@ export function makeid(length) {
return result;
}
// by RFC Radius attributes have a max length
// https://tools.ietf.org/html/rfc6929#section-1.2
export const MAX_RADIUS_ATTRIBUTE_SIZE = 253;
export interface IDeferredPromise {

95
src/radius/RadiusService.ts

@ -0,0 +1,95 @@
import * as radius from 'radius';
import { IAuthentication } from '../types/Authentication';
import { EAPPacketHandler } from './handler/EAPPacketHandler';
import { DefaultPacketHandler } from './handler/DefaultPacketHandler';
import { IPacketHandler, IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler';
export class RadiusService {
radiusPacketHandlers: IPacketHandler[] = [];
constructor(private secret: string, private authentication: IAuthentication) {
this.radiusPacketHandlers.push(new EAPPacketHandler(authentication));
this.radiusPacketHandlers.push(new DefaultPacketHandler(authentication));
}
async handleMessage(
msg: Buffer
): Promise<{ data: Buffer; expectAcknowledgment?: boolean } | undefined> {
const packet = radius.decode({ packet: msg, secret: this.secret });
if (packet.code !== 'Access-Request') {
console.log('unknown packet type: ', packet.code);
return undefined;
}
// console.log('packet.attributes', packet.attributes);
// console.log('rinfo', rinfo);
/*
const checkAuth = async (
username: string,
password: string,
additionalAuthHandler?: AdditionalAuthHandler
) => {
console.log(`Access-Request for ${username}`);
let success = false;
try {
await this.authentication.authenticate(username, password);
success = true;
} catch (err) {
console.error(err);
}
const attributes: any[] = [];
if (additionalAuthHandler) {
await additionalAuthHandler(success, { packet, attributes, secret: this.secret });
}
const response = radius.encode_response({
packet,
code: success ? 'Access-Accept' : 'Access-Reject',
secret: this.secret,
attributes
});
console.log(`Sending ${success ? 'accept' : 'reject'} for user ${username}`);
this.server.sendToClient(response, rinfo.port, rinfo.address, function(err, _bytes) {
if (err) {
console.log('Error sending response to ', rinfo);
}
});
}; */
let response: IPacketHandlerResult;
let i = 0;
if (!this.radiusPacketHandlers[i]) {
throw new Error('no packet handlers registered');
}
// process packet handlers until we get a response
do {
/* response is of type IPacketHandlerResult */
response = await this.radiusPacketHandlers[i].handlePacket(packet.attributes, packet);
i++;
} while (this.radiusPacketHandlers[i] && (!response || !response.code));
// still no response, we are done here
if (!response || !response.code) {
return undefined;
}
// all fine, return radius encoded response
return {
data: radius.encode_response({
packet,
code: response.code,
secret: this.secret,
attributes: response.attributes
}),
// if message is accept or reject, we conside this as final message
// this means we do not expect a reponse from the client again (acknowledgement for package)
expectAcknowledgment: response.code === PacketResponseCode.AccessChallenge
};
}
}

36
src/radius/handler/DefaultPacketHandler.ts

@ -0,0 +1,36 @@
import { IAuthentication } from '../../types/Authentication';
import {
IPacketHandler,
IPacketHandlerResult,
PacketResponseCode
} from '../../types/PacketHandler';
export class DefaultPacketHandler implements IPacketHandler {
constructor(private authentication: IAuthentication) {}
async handlePacket(attributes: { [key: string]: Buffer }): Promise<IPacketHandlerResult> {
const username = attributes['User-Name'];
const password = attributes['User-Password'];
if (!username || !password) {
// params missing, this handler cannot continue...
return {};
}
const authenticated = await this.authentication.authenticate(
username.toString(),
password.toString()
);
if (authenticated) {
// success
return {
code: PacketResponseCode.AccessAccept
};
}
// Failed
return {
code: PacketResponseCode.AccessReject
};
}
}

193
src/radius/handler/EAPPacketHandler.ts

@ -0,0 +1,193 @@
// https://tools.ietf.org/html/rfc3748#section-4.1
import * as NodeCache from 'node-cache';
import { RadiusPacket } from 'radius';
import { EAPTTLS } from './eapMethods/EAPTTLS';
import { makeid } from '../../helpers';
import {
IPacketHandler,
IPacketHandlerResult,
PacketResponseCode
} from '../../types/PacketHandler';
import { IAuthentication } from '../../types/Authentication';
import { IEAPMethod } from '../../types/EAPMethod';
export class EAPPacketHandler implements IPacketHandler {
private eapMethods: IEAPMethod[] = [];
// private eapConnectionStates: { [key: string]: { validMethods: IEAPMethod[] } } = {};
private eapConnectionStates = new NodeCache({ useClones: false, stdTTL: 3600 }); // max for one hour
constructor(authentication: IAuthentication) {
this.eapMethods.push(new EAPTTLS(authentication));
}
/**
*
* @param data
* @param msgType 1 = identity, 21 = EAP-TTLS, 2 = notification, 4 = md5-challenge, 3 = NAK
*/
private async buildEAPResponse(
identifier: number,
msgType: number,
data?: Buffer
): Promise<IPacketHandlerResult> {
/** build a package according to this:
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 | Type-Data ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
*/
const buffer = Buffer.from([
1, // request
identifier,
0, // length (1/2)
0, // length (2/2)
msgType // 1 = identity, 21 = EAP-TTLS, 2 = notificaiton, 4 = md5-challenge, 3 = NAK
]);
const resBuffer = data ? Buffer.concat([buffer, data]) : buffer;
// set EAP length header
resBuffer.writeUInt16BE(resBuffer.byteLength, 2);
return {
code: PacketResponseCode.AccessChallenge,
attributes: [['EAP-Message', buffer]]
};
}
private decodeEAPHeader(msg: Buffer) {
/**
* parse msg according to this:
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 | Type-Data ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
*/
/*
code:
1 Request
2 Response
3 Success
4 Failure
*/
const code = msg.slice(0, 1).readUInt8(0);
/* identifier is a number */
const identifier = msg.slice(1, 2).readUInt8(0);
const length = msg.slice(2, 4).readInt16BE(0);
/* EAP type */