retailnext / node-radius

Node.js RADIUS library for packet encoding and decoding.
Other
200 stars 60 forks source link

node-radius Build Status - A RADIUS library for node.js

node-radius is a RADIUS packet encoding/decoding library for node.js written in Javascript. With node-radius you can easily decode received packets, encode packets to send, and prepare responses to received packets. node-radius supports both RADIUS authentication and RADIUS accounting packets.

node-radius requires node.js v0.8.0. To install node-radius, simply run npm install radius in your project directory.

Let's look at some examples of how to use node-radius:

var radius = require('radius');

// ... receive raw_packet from UDP socket

var decoded = radius.decode({ packet: raw_packet, secret: "shared_secret" });

"decoded" might look something like this:

{
  code: 'Access-Request',
  identifer: 123,
  length: 250,
  attributes: {
    'NAS-IP-Address': '10.1.2.3',
    'User-Name': 'jlpicard',
    'User-Password': 'beverly123',
    'Vendor-Specific': {
      'Federation-Starship': 'Enterprise'
    }
  }
}

To prepare your response packet, use the encode_response function:

var response = radius.encode_response({
  packet: decoded,
  code: "Access-Accept",
  secret: "section31"
});

To prepare a stand-alone packet, try this:

var packet = radius.encode({
  code: "Access-Request",
  secret: "obsidian order",
  attributes: [
    ['NAS-IP-Address', '10.5.5.5'],
    ['User-Name', 'egarak'],
    ['User-Password', 'tailoredfit'],
    ['Vendor-Specific', 555, [['Real-Name', 'arobinson']]]
  ]
});

Method descriptions:

radius.decode(\)

decode takes as input an object with the following fields:

Using the dictionaries available, decode parses the raw packet and yields an object representation of the packet. The object has the following fields:

When decoding requests (e.g. "Access-Request", "Accounting-Request"), decode will automatically verify the request authenticator and the Message-Authenticator attribute, if present. If the request doesn't check out, decode will raise an error. The error, an instance of Radius.InvalidSecretError, has a "decoded" field you can use to inspect the decoded but invalid message. The most common reason for an incorrect authenticator is using the wrong shared secret.

radius.decode_without_secret(\)

Identical to decode, but does not need the secret. This can be useful to "pre-decode" a message, in order to look-up (or calculate) the secret to be used to properly decode the message later.

A message decoded without a secret will contain null values for encrypted fields (typically passwords, etc.).

radius.encode(\)

encode takes an object for arguments and returns a Buffer ready to be sent over the wire. The accepted arguments are:

encode will automatically add the Message-Authenticator when:

encode will not add the Message-Authenticator when:

The attributes will typically be like the following (see above example):

attributes: [
  [<attribute name>, <attribute value>],
  ...
]

If you don't care about attribute ordering, you can use a hash for the attributes:

attributes: {
  <attribute name>: <attribute value>,
  ...
}

If you want to send attributes that you haven't loaded a dictionary for, you can do:

attributes: [
  [<attribute id>, <Buffer>],
  ...
]

Where the first item is the numeric attribute id and the second item is just a Buffer containing the value of the attribute (not including length).

You can specify Vendor-Specific attributes like so:

attributes: [
  ['Vendor-Specific', <vendor id>, [
    [<attribute name>, <attribute value>],
    [<attribute name>, <attribute value>]
  ],
  ...
]

Or if you want each vendor attribute as a separate attribute, try this:

attributes: [
  ['Vendor-Specific', <vendor id>, [[<attribute name>, <attribute value>]]],
  ['Vendor-Specific', <vendor id>, [[<attribute name>, <attribute value>]]]
  ...
]

Like regular attributes, you can also specify the attribute id and a raw Buffer value for VSAs. If your dictionary specifies vendor attributes using the BEGIN-VENDOR/END-VENDOR format, you can use the symbolic vendor name as defined in the dictionary in place of the numeric \.

You can specify the tag field-attribute like so (see RFC2868):

attributes: [
  [<attribute name>, <tag number>, <attribute value>],
  ...
]

If the attribute has an optional tag and you don't want to send it, then only specify the \ and the \.

radius.encode_response(\)

encode_response prepares a response packet based on previously received and decoded packet. "args" is an object with the following properties:

encode_response does a few things for you to prepare the response:

  1. sets the response packet's message identifier to the identifer of the previously received packet
  2. copies any "Proxy-State" attributes from the previously received packet into the response packet
  3. calculates the appropriate response authenticator based on the request's authenticator
  4. calculates and adds a Message-Authenticator attribute if the request contained one

radius.verify_response(\)

verify_response checks the authenticator and Message-Authenticator attribute, if applicable, of a response packet you receive. It returns true if the packet checks out, and false otherwise (likely because the other side's shared secret is wrong). "args" is an object with the following properties:

This method is useful if you are acting as the NAS. For example, if you send an "Access-Request", you can use this method to verify the response you get ("Reject" or "Accept") is legitimate.

Note that if the request contained a Message-Authenticator, the response must also contain a Message-Authenticator.

Dictionaries

node-radius supports reading freeradius-style RADIUS dictionary files. node-radius comes with a slew of RFC dictionary files, so you should only worry about adding any vendor-specific dictionary files you have. node-radius will load all the dictionaries it knows about (the default RFC ones and any you added) automatically the first time it needs to, so you should add your dictionaries before you start to use the module.

radius.add_dictionary(\)

To add a dictionary to be loaded, use the add_dictionary function:

var radius = require('radius');

radius.add_dictionary('/path/to/my/dictionary');

add_dictionary takes either a file or a directory (given a directory, it assumes everything in the directory is a dictionary file). add_dictionary does not block or perform any IO. It simply adds the given path to a list which is used to load dictionaries later.

node-radius supports reading both the VENDORATTR and the BEGIN-VENDOR/END-VENDOR style for defining VSAs. node-radius also supports reading the following attribute modifiers: has_tag, encrypt=1.

node-radius will also follow "$INCLUDE" directives inside of dictionary files (to load other dictionary files).

Example usage

The following is an example of a simple radius authentication server:

var radius = require('radius');
var dgram = require("dgram");

var secret = 'radius_secret';
var server = dgram.createSocket("udp4");

server.on("message", function (msg, rinfo) {
  var code, username, password, packet;
  packet = radius.decode({packet: msg, secret: secret});

  if (packet.code != 'Access-Request') {
    console.log('unknown packet type: ', packet.code);
    return;
  }

  username = packet.attributes['User-Name'];
  password = packet.attributes['User-Password'];

  console.log('Access-Request for ' + username);

  if (username == 'jlpicard' && password == 'beverly123') {
    code = 'Access-Accept';
  } else {
    code = 'Access-Reject';
  }

  var response = radius.encode_response({
    packet: packet,
    code: code,
    secret: secret
  });

  console.log('Sending ' + code + ' for user ' + username);
  server.send(response, 0, response.length, rinfo.port, rinfo.address, function(err, bytes) {
    if (err) {
      console.log('Error sending response to ', rinfo);
    }
  });
});

server.on("listening", function () {
  var address = server.address();
  console.log("radius server listening " +
      address.address + ":" + address.port);
});

server.bind(1812);

Client and server examples can be found in the examples directory.

Important notes:

But, on the plus-side, unlike many other RADIUS libraries node-radius supports encrypting/decrypting passwords longer than 16 bytes!