PolicyLogic

PolicyLogic is the brain of SpherAAA. The PolicyLogic uses a JavaScript compiler to write your Radius authentication call flow. JavaScript was chosen for its simplicity.

Type and environments

To provide a sandbox for development and testing, SpherAAA offers four environments.

  • QA
  • DEV
  • STAGE
  • PROD

Each environment has two categories:

  • AUTH - Authentication flow
  • ACCT - Accounting flow

Secure page

PolicyFlow categories

PolicyLogic structure

When PolicyLogic retrieves a request for handling, we put all the available variables into a JSON Object, called "radius".

Variables groups - radius

radius contents the following groups:

For example:

{ "eap": {
    "EAP-Code": "EAP-RESPONSE",
    "EAP-Id": 125,
    "EAP-Lenght": 79,
    "EAP-Type": "EAP-TTLS"
  },
    "request": {
        "Service-Type": "Framed",
        "NAS-IP-Address": "192.168.200.1",
        "NAS-Identifier": "NAS-2",
        "EAP-Message": "0231004f158000000045170303004031e8575fdcf9804ec95333e48d75971a0958a08fd5a5fef63de82bdf27574380e7dea817843716b10c918456b46fed9d1117a9e4d1e4f53bebea1423d27e8ec0",
        "NAS-Port-Type": "19",
        "Framed-MTU": "1400",
        "User-Name": "user@mydomain.com",
        "Calling-Station-Id": "01-02-03-04-05-06",
        "State": "aaa.spheralogic.com;1661152509",
        "Unknown-0-187-0": "000fac04",
        "Unknown-0-186-0": "000fac04",
        "Acct-Session-Id": "42C4B84538D254BB",
        "Message-Authenticator": "f605f97dbd864a5c64716dd5002d58a3",
        "Called-Station-Id": "0C-60-DE-E4-BB-E6:SSID-SECURE",
        "NAS-Port": "1",
        "Connect-Info": "CONNECT 54Mbps 802.11g",
        "Unknown-0-188-0": "000fac01"
    },
  "code": "ACCESS-REQUEST",
  "session": {
        "Spheraaa-Session-Id": "S-1661733604348",
        "Packet-Identifier": 0,
        "Authenticator": "b122f07a22f77a6cba6872dd795d3741",
        "Packet-Length": 333,
        "Source-Host-Port": "192.168.200.1:45969"
    },
  "host": {
    "nasIpAddr": "192.168.200.1",
    "note": "openwrt",
    "type": "OPENWRT",
    "dynPort": "3799",
    "Source-Host-Port": "127.0.0.1:18812"
  },
  "reply": {},
  "config": [
        {"proxy": 
          {"partners": [
            {
                "realms": [
                    "aaa.com",
                    "bbb.com"
                ],
                "endpoint": ["1.1.1.1","aaa.proxyaaa.com"],
                "auth-port": "1812",
                "acct-port": "1813",
                "name": "PARTNER_1",
                "timeout": "5000",
                "timeout-unreachable": "30000",
                "balance-mode": "random"
            },
            {
                "realms": ["ccc.com"],
                "endpoint": "2.2.2.2",
                "auth-port": "1812",
                "acct-port": "1812",
                "name": "PARTNER_2",
                "timeout": "1000"
            },
            {
                "realms": ["ddd.com"],
                "endpoint": "3.3.3.3",
                "auth-port": "1812",
                "acct-port": "1813",
                "name": "PARTNER_3"
            },
            {
                "realms": [
                    "wlan.mnc001.mcc002.3gppnetwork.org",
                    "001_002.net"
                ],
                "endpoint": "4.4.4.4",
                "auth-port": "1812",
                "acct-port": "1813",
                "name": "PARTNER_MOBILE"
            }
        ]}},
        {
            "Provider-Name": "My Provider Name",
            "Authenticated-TTL": "2",
            "Domain": "mydomain.com",
            "Session-Timeout": "1800"
        }
    ],
  "user": {
    "Identity": "user",
    "Realm": "mydomain.com"
  }
}

EAP group

EAP group holds parsed information regarding current EAP process.

Request group

Request group holds attributes from the RADIUS Requests.

Code

Name of the Radius-Request/Response

Session

Session group holds information about RADIUS packet. For example, packet-id, authenticator and packet length.

Host group

Host group holds variables related to Requested-Host.

Reply group

Reply group holds variables which SpherAAA responding back.

Config group

The Config group holds entries from the config collection group, which are available by default. Consider this collection as settings for PolicyLogic.

Example:

{
  "Provider-Name": "My Provider Name",
  "Session-Timeout": "1800",
  "Authenticated-TTL": "1"
}

and it will be available in PolicyLogic in radius.config

...
 "config": [
    {
      "Provider-Name": "My Provider Name",
      "Authenticated-TTL": "1",
      "Session-Timeout": "1800"
    }
  ]

User group

The user group might hold User-Name related information. You can keep your variables here as well. If the username is in NAI format (email), then radius.user will hold the Identity and Realm variables.

For example, for User-Name = "user@domain.com"

  ...,
  "user": {
    "Identity": "user",
    "Realm": "domain.com"
  }

In the case of EAP-TLS authentication, the user group includes details of the user's provided certificate:

    "user": {
        "IssuerDN": "CN=aaa.spheralogic.com, OU=Wireless, O=SpheraLogic LLC-Root, L=NewYork, ST=NY, C=US",
        "SerialNumber": "10453860482036592703",
        "NotAfter": "Sun Aug 11 22:18:42 EDT 2024",
        "NotBefore": "Fri Aug 12 22:18:42 EDT 2022",
        "Identity": "eap",
        "SubjectDN": "CN=anonymous@company.com, OU=USERID_NAME, O=SpherAAA_LLC-Client, L=NewYork, ST=NY, C=US",
        "SessionID": "62f70ac8005f4821c7db5cd1b838f7bacb5eb567d8927ac32fd5b1a0616bbde1",
        "Realm": "local.com"
    },
In PolicyLogic, user authorization can be done using this information.

You can later use these variables as references in your PolicyLogic.

var realm = radius.user['Realm'];

PolicyLogic syntax

JavaScript 101

JavaScript has proven itself to be a very easy language to comprehend and immediately put into use. However, it comes with a set of basic principles that could be baffling to newcomers:

  • While the ECMAScript specification lists semicolons as optional, using them in all assignment statements and function declarations can protect against unintentional results.
  • Variables are undefined by default which is a different from being null. To check if variable is defined, the basic condition if (variable) is a sufficient check.
  • JavaScript introduces the concept of truthy and falsy (or falsey) values. All values except undefined, null, false, -0, +0, NaN, and '' are truthy and evaluate to true during comparison.
  • Loose typing requires understanding rules behind type coercion: 3 + 3 + "3" evaluates to 63 whereas "3" + 3 + 3 yields 333. This implicit conversion stems from left-to-right arithmetic and when a string object is found, the operation continues as a concatenation.
  • The same type of coercion applies to a comparison invoked with the == operator. To avoid coercing the type when comparing values, it's good to use the === operator, which compares both the value and the type, for example, 1 == true but 1 !== true.
  • For all operations involving numbers, a good habit is to use the built-in parseInt and parseFloat functions or a shorter +variable expression syntax to perform number conversion.
  • Just like Bash, conditional expressions could be simplified with the && and || operators so that 1 == "1" && print("yes") simulates the longer if (1 == "1") { print("yes"); } syntax.
  • A function always expects an arbitrary number of positional arguments, rendering missing values as undefined. The predefined array-like arguments variable exposes the index access with square brackets. The only ways to access this variable are through arguments[i] and arguments.length().

Functions

Response

- success

Depends on Radius request type, functions return success response to client. For instance:

Access-Request -> Access-Accept

Acct-Request -> Acct-Response

CoA-Request -> CoA-Ack

DM-Request -> DM-Ack

Status-Request -> Access-Accept

For example, this statement will return Access-Accept with Reply-Message = "Success Auth".

radius.reply['Reply-Message'] = "Success Auth";
success();

- fail

Depends on Radius request type, functions return failure response to client. For instance:

Access-Request -> Access-Reject

Acct-Request -> Acct-Response

CoA-Request -> CoA-Nack

DM-Request -> DM-Nack

Status-Request -> Access-Reject

For example, we will return Access-Reject with Reply-Message = "Invalid Username or Password".

radius.reply['Reply-Message'] = "Invalid Username or Password";
fail();

fail(replyMessage) - returns failure response with providede replyMessage

fail("Access denided");

Requests

- RadiusRequest

RadiusRequest(code, dest_ip, dest_port, timeout)

The RadiusRequest function generates a Radius-Request and returns the request result.

code - Radius Code. Check for supported codes

dest_ip - IP-Address of destination Radius or NAS Server

dest_port - 1812 or 1813 or 3799 or any

timeout - Timeout (in ms) for response. Default is 2000ms

- RadiusProxy

The RadiusProxy() function generates a Radius-Request and returns the request result. Parameters for the RadiusProxy can be configured either by setting them using radius.proxy['Parameter-Name'] or directly as instance variables. Below are the supported parameters for the function:

send() - This function sends the RADIUS request to the destination (dst) host, using the request attributes specified in the radius.request group. The response from the proxy AAA server will then be automatically added to the radius.reply group.

dst or radius.proxy['Proxy-Address'] - IP-Address of destination Radius or NAS Server

dst_port or radius.proxy['Proxy-Address-Port'] - (Integer) 1812 or 1813 or 3799 or any

timeout or radius.proxy['Proxy-Timeout'] - Timeout (in ms) for response. Default is 2000ms

timeout_unreachable or radius.proxy['Proxy-Timeout-Unreachable'] - It is marks the server as unreachable and avoids using it for the specified duration, in milliseconds.

name or radius.proxy['Proxy-Provider'] - The name of host-groups, which is required for SpherAAA's internal Load-Balancer.

balance_mode or radius.proxy['Proxy-Balance-Mode'] - This value specifies the load-balancing algorithm for proxied addresses. Supported values are random and round-robin. Session persistence is supported for EAP requests.

dpd or radius.proxy['Proxy-DPD'] - Enable Dynamic Peer Discovery (WBA OpenRoaming, eduroam) to find out the AAA server IP/FQDN by querying NAPTR, SRV, and A records from the DNS server, by using the realm value from the EAP-Identity.

To proxy or send RADIUS packets to another AAA server, we should define Partners AAA IP in the NAS configuration (as regular NAS).

The destination IP-Address can be dynamically retrieved from configuration (config collection) or manually assigned.

Example:

const proxyServer = new RadiusProxy();
proxyServer.code = radius.code;
proxyServer.dst = "1.2.3.4"; //radius.proxy['Proxy-Address'] = "1.2.3.4";
proxyServer.dst_port = "1812"; // radius.proxy['Proxy-Address-Port'] = "1812";
proxyServer.timeout = "2000";
proxyServer.name = "MY_PROXY_PROVIDER";
proxyServer.timeout_unreachable = "10000";
proxyServer.balance_mode = "round-robin";
proxyServer.dpd = "false";
const proxyResult = proxyServer.send();
//log(proxyResul);

and log result

{"reply":
    {
        "Class":"TYPE=X",
        "Class":"ID=USERID"
    },
    "code":"ACCESS-ACCEPT"
}
- getProxySettings

getProxySettings(realm) loops over the config.proxy group and retrieves proxy parameters using radius.user ['Realm'] parameter

As a result of getProxySettings(realm), radius.proxy group will be populated with the below parameters:

radius.proxy['Proxy-Address'] - Destination IP-Address of Partner AAA server

radius.proxy['Proxy-Address-Port'] - Port of Partner AAA Server

radius.proxy['Proxy-Timeout'] - Response timeout from Partner AAA Server, in milliseconds.

radius.proxy['Proxy-Timeout-Unreachable'] - The value of timeout-unreachable marks the server as unreachable and avoids using it for the specified duration, in milliseconds.

radius.proxy['Proxy-Provider'] - The value of the name, which is required for SpherAAA's internal Load-Balancer.

radius.proxy['Proxy-Balance-Mode'] - The value of balance-mode specifies the load-balancing algorithm for proxied addresses. Supported values are random and round-robin. Session persistence is supported for EAP requests.

radius.proxy['Proxy-DPD'] - Enable Dynamic Peer Discovery (WBA OpenRoaming, eduroam) to find out the AAA server IP/FQDN by querying NAPTR, SRV, and A records from the DNS server, using the realm value from the EAP-Identity.

Here's an example of obtaining a destination address dynamically based on the user realm (domain) using getProxySettings. Considering this entry on the config collection (created by default):

{
  "proxy": {
    "partners": [{
        "name": "PARTNER1",
        "realms": ["partner1.com","partner1.net"],
        "endpoint": ["1.1.1.1", "aaa.proxyaaa.com"],
        "auth-port": "1812",
        "acct-port": "1813",
        "name": "PARTNER_1",
        "timeout": "5000",
        "timeout-unreachable": "30000",
        "balance-mode": "random"
      },{
        "name": "DPD-PARTNER",
        "realms": [
          "partner-dpd.com"
        ],
        "endpoint": "someuniquename.com",
        "port": "2083",
        "timeout": "2000",
        "pdp": "true"
      },
      {
        "name": "PARTNER2",
        "realms": [
          "partner2.com"
        ],
        "endpoint": "2.2.2.2",
        "port": "1812",
        "timeout": "2000"
      },
      {
        "name": "PARTNER3",
        "realms": [
          "partner3.com"
        ],
        "endpoint": "3.3.3.3",
        "auth-port": "1812",
        "timeout": "2000"
      }
    ]
  }
}

NAS List

Config Proxy

We can run the below code to get a dynamically obtained destination address and send a Radius-Proxy request.

function proxyCheck() {
    var retVal = false;

    if (getProxySettings(radius.user ['Realm'])) {
        var proxyResult = RadiusProxy(radius.code, radius.proxy['Proxy-Address'], radius.proxy['Proxy-Address-Port'], radius.proxy['Proxy-Timeout']);
        consoleLog(JSON.stringify(proxyResult));
        if (proxyResult) {
            retVal = true;
        }
    }
    return retVal;
}

The result of the proxy will be populated into the radius.reply group for further processing.

- RadiusCodes

Auth: access-request, access-accept, access-reject

Acct: acct-request, acct-response

Disconnect-Message: dm-request, dm-ack, dm-nack

Change-of-Authorization: coa-request, coa-ack, coa-nack

Status: status-request, status-response

Cache Collection functions

- queryMany/queryAll

queryMany( queryKey, queryValue, collectionName )

queryAll( collectionName )

Query collection by specifying the query key and value. As a result, you will get all entries for specified key.

queryKey - The key to finding an existing entry.

queryValue - The value of a key when looking up an existing entry.

collectionName - The collection's name.

returns - The query result returns JSON ArrayObject ([{'key1':'val1'}, {'key2':'val2'}]). If query failed or entry doesn't exists, return value will false

For instance:

    // Check for the PPSK Authentication. 
    // Assign VLANs dynamically.
    //
    function checkPPSK() {

    if (radius.request['User-Name'] == radius.request['User-Password']) {
        var dbResult = queryMany("Called-Station-Id", radius.request['Called-Station-Id'], "ppsk");
        if (dbResult) {
            for (i = 0; i <= dbResult.length - 1; i++) {
                radius.reply['tag'][i] = {};
                radius.reply['tag'][i]['Tunnel-Password'] = dbResult[i]['Tunnel-Password'];
                radius.reply['tag'][i]['Tunnel-Private-Group-Id'] = dbResult[i]['Tunnel-Private-Group-Id'];
                radius.reply['tag'][i]['Tunnel-Type'] = dbResult[i]['Tunnel-Type'];
                radius.reply['tag'][i]['Tunnel-Medium-Type'] = dbResult[i]['Tunnel-Medium-Type'];              
            }

            radius.reply['Reply-Message'] = "Success Auth";

            //
            // Store request in the "Session" collection with TTL 1hr
            //
            // function: upsertEntry(collectionName, object, TTL, indexName, indexValue);
            //      
            upsertEntry("sessions", radius.request, 3600, "Calling-Station-Id", radius.request['Calling-Station-Id']);

            success();
            return true;
        }
        return false;
    }
}

- queryOne

queryOne( queryKey, queryValue, collectionName )

Query collection by specifying the query key and value. As a result, you will receive only one entry. To query all entries, please use queryAll

queryKey - The key to finding an existing entry.

queryKeyValue - The value of a key when looking up an existing entry.

collectionName - The collection's name.

returns The query result returns JSON Object. If query failed or entry doesn't exists, return value will be false

{
  "status":"failed"
}

For instance:

var dbResult = queryOne("User-Name", radius.request['User-Name'], "users");

if(dbResult['result'] != "failed" && dbResult['User-Password'] == radius.request['User-Password'] ){
       radius.reply['Reply-Message'] =  "Success Auth";

        //
        // Store request in the "Session" collection with TTL 1hr
        //
        // function: upsertEntry(collectionName, object, TTL, indexName, indexValue);
        //      
        upsertEntry("sessions", radius.request, 3600, "Calling-Station-Id", radius.request['Calling-Station-Id']);       
        success();
         }

   }

- insertEntry

insertEntry( collectionName, jsonObject, ttl )

Persist a JSON object in a collection using the Time-to-Live option. When the TTL expires, the entry will be removed.

collectionName - The collection's name.If the collection does not exist, it will be created automatically.

jsonObject - any valid JSON Object.

TTL - Time-To-Live in seconds. Valid numbers: 1 - seconds, 3600 - 1hr, 7200 - 2hrs and etc.

For instance, we will add a custom made JSON object to the collection for a period of 24 hours.

var sessionVar = {
    "AccountId": 123456, 
    "Calling-Station-Id": radius.request['Calling-Station-Id'], 
    "Called-Station-Id": radius.request['Called-Station-Id']
};
insertEntry( "auth_sessions", sessionVar, 86400 );

- upsertEntry

upsertEntry( collectionName, jsonObject, ttl, key, value )

If an entry with the key exists, update it; otherwise, use the Time-to-Live option to persist a JSON object in a collection. The entry will be removed after the TTL expires.

collectionName - The collection's name.If the collection does not exist, it will be created automatically.

jsonObject - any valid JSON Object. If key provided, jsonObject will be saved under key-Value

TTL - Time-To-Live in seconds. Valid numbers: 1 - seconds, 3600 - 1hr, 7200 - 2hrs and etc.

key - The key to finding an existing entry. If provided INDEX with this key will be created.

value - The value of a key when looking up an existing entry.

For instance, we will add a custom made JSON object to the collection for a period of 24 hours.

var sessionVar = {
    "AccountId": 123456, 
    "Calling-Station-Id": radius.request['Calling-Station-Id'], 
    "Called-Station-Id": radius.request['Called-Station-Id']
};
upsertEntry( "auth_sessions", sessionVar, 86400, "Calling-Station-Id",  radius.request['Calling-Station-Id'] );

- deleteEntry

deleteEntry( key, value, collectionName )

If the key already exists in an entry, it will be removed, and 1 will be returned as a result; otherwise, 0 will be returned.

key - The key to finding an existing entry.

value - The value of a key when looking up an existing entry.

collectionName - The collection's name.

deteleEntry( "Calling-Station-Id",  radius.request['Calling-Station-Id'], "sessions" );

EAP

- EAP-TTLS

EAP-TTLS (Extensible Authentication Protocol-Tunneled Transport Layer Security) is another authentication method that uses a tunneled TLS connection. Unlike EAP-TLS, EAP-TTLS does not require digital certificates on the client side, which can make it easier to deploy in some environments. During the authentication process, the client sends its credentials to the authentication server in an encrypted TLS tunnel. The server then verifies the credentials and sends an authentication response back to the client. If the authentication is successful, the server and client establish a secure TLS tunnel, and the client is granted access to the network resources.

SpherAAA supports EAP-TTLS both with PAP and EAP-GTC inner-tunnel authentication methods. Before starting with EAP-TTLS, please import or generate a server certificate using this instruction.

Usage: var eapttls = new EapTTLS();

Parameters:

eapttls.cert_id : Specify the certificate ID from "Secure > EAP Server Certificates". By default, SpheraAAA will use the default specified certificate for that environment.

eapttls.start() : Starts EAP-TTLS Offer and authentication process.

Returns EAP states: Starting, Handshaking, ApplicationData.

To start the EAP-TTLS process, we can call the internal function EapTTLS() and while the function returns EAP-State values, the process needs to continue.

Until we reach the ApplicationData state, in which SpherAAA and the client exchange credentials within a TLS tunnel. In this state, SpherAAA adds a new parameter Inner-Auth-Method to request group with the value of the inner auth method: EAP-GTC, PAP or MSCHAPv2.

1
2
3
4
5
6
{..
  "Calling-Station-Id": "44:55:66:77:88:99",
  "Inner-Auth-Method": "PAP",
  "State": "aaa-01.spheralogic.com;547ad4f372e27b0f",
  "Framed-IP-Address": "10.10.244.177"
..}

Below you can find example on how EapTTLS is implemented (included to default auth file):

    // Check for EAP-TTLS Authentication. If an "eap" group is available
    // and if we are not in Inner-Tunnel Auth state, then just return back
    // to EAP-TTLS until handshake process is done and we get Inner-Tunnel
    // credentials.
    //
    if (radius.eap && !radius.request['Inner-Auth-Method']) {
        // Create a new object for handling EAP-TTLS Authentication.        
        var eapttls = new EapTTLS();

        // Use an alternate certificate when a request originates from the realm "otherdomain.com".
        // if (radius.user.Realm == "otherdomain.com" )
        //     eapttls.cert_id = "6461706ef4befabdc81e6d1a";

        // Start EAP-TTLS authentication
        var ttlsReturn = eapttls.start();

        // Check if EapTTLS process returned any result
        // Usually it will return EAP Exchange status
        // like: Starting, Handshaking, ApplicationData
        //
        if (ttlsReturn) {
            return;
        }
    }
Here we are calling return function to exist PolicyFlow and continune with EAP Handshake process.

- EAP-TLS

EAP-TLS (Extensible Authentication Protocol-Transport Layer Security) is a strong authentication method that uses digital certificates for mutual authentication between the client and the authentication server. During the authentication process, the client presents its digital certificate to the server for verification, and the server presents its own certificate to the client. If the certificates are valid and trusted, the client and server establish a secure TLS connection, and the client is granted access to the network resources.

Usage: var eaptls = new EapTLS();

Parameters:

eaptls.cert_id : Specify the certificate ID from "Secure > EAP Server Certificates". By default, SpheraAAA will use the default specified certificate for that environment.

eaptls.ocsp_enabled: It is possible to verify a client certificate with an OCSP server (as specified in RFC 2560 and RFC 6960) and retrieve the result as a variable. By default, OCSP is false, but you can enable it by setting the value to true. This will prompt SpherAAA to send the request to the OCSP server. It's important to note that SpherAAA will not automatically decline a request based on the OCSP responder response. Instead, it provides you with the flexibility to make that decision. For more details, check OCSP Response

eaptls.start() : Starts EAP-TLS Offer and authentication process.

Returns EAP states: Starting, Handshaking, ApplicationData.

To start the EAP-TTLS process, we can call the internal function EapTLS() and while the function returns EAP-State values, the process needs to continue.

Until we reach the ApplicationData state, in which SpherAAA and the client authenticating each others. In this state, SpherAAA adds a new parameter Inner-Auth-Method to request group with the value X509:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"request": {
        "Service-Type": "1",
        "NAS-IP-Address": "192.168.200.144",
        "EAP-Message": "020900060d00",
        "NAS-Port-Type": "19",
        "Normalized-Mac": "44:55:66:77:88:99",
        "Tunnel-User-Password": "none",
        "User-Name": "none",
        "Calling-Station-Id": "44:55:66:77:88:99",
        "Inner-Auth-Method": "X509",
        "User-Password": "none",
        "State": "aaa-01.spheralogic.com;57680b8c06dc99bd",
        "Framed-IP-Address": "10.253.244.177",
        "Message-Authenticator": "2f7192b39c5bb5b4a9fb42d2553f8c81",
        "NAS-Port": "0",
        "Tunnel-User-Name": "none"
    },
    "user": {
      "X509": {
        "IssuerDN": "CN=aaa.spheralogic.com, OU=Wireless, O=SpheraLogic LLC-Root, L=NewYork, ST=NY, C=US",
        "SerialNumber": "10453860482036592703",
        "NotAfter": "Sun Aug 11 22:18:42 EDT 2024",
        "NotBefore": "Fri Aug 12 22:18:42 EDT 2022",
        "Identity": "eap",
        "SubjectDN": "CN=anonymous@company.com, OU=USERID_NAME, O=SpherAAA_LLC-Client, L=NewYork, ST=NY, C=US",
        "SessionID": "62f70ac8005f4821c7db5cd1b838f7bacb5eb567d8927ac32fd5b1a0616bbde1",
        "Realm": "local.com"
      }
    },
    "config": [...

You can verify the client certificate by calling checkTLSCert()

- OCSP Response

The OCSP response will be available in the user.X509.OCSP_Response variable and can have one of the following values:

  • good - the certificate is valid.
  • revoked - the certificate has been revoked.
  • unknown - the certificate's status is unknown to the OCSP.
  • error - SpherAAA couldn't connect to the certificate authority's OCSP server.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
"user": {
      "X509": {
        "IssuerDN": "CN=aaa.spheralogic.com, OU=Wireless, O=SpheraLogic LLC-Root, L=NewYork, ST=NY, C=US",
        "SerialNumber": "10453860482036592703",
        "NotAfter": "Sun Aug 11 22:18:42 EDT 2024",
        "NotBefore": "Fri Aug 12 22:18:42 EDT 2022",
        "Identity": "eap",
        "SubjectDN": "CN=anonymous@company.com, OU=USERID_NAME, O=SpherAAA_LLC-Client, L=NewYork, ST=NY, C=US",
        "SessionID": "62f70ac8005f4821c7db5cd1b838f7bacb5eb567d8927ac32fd5b1a0616bbde1",
        "Realm": "local.com",
        "OCSP_Response": "good",
        "OCSP_URL": "http://ocsp.company.com",
      }

You can refer to the example below, which includes a function for verifying the certificate status.

// This function checks for EAP-TLS authentication using OCSP Responder result and returns a boolean
// more info https://spheralogic.com/wiki/policy/#-ocsp-client

function verifyEapTls(ocsp_enabled) {
    if (radius.request['Inner-Auth-Method'] === "X509") {
        // Check if OCSP is enabled
        if (ocsp_enabled) {
            let certStatus = radius.user.X509.OCSP_Response;

            // Verify the certificate status using OCSP response
            if (certStatus === "good") {
                return true; // Certificate is valid
            } else {
                log("Certificate with Serial Number " + radius.user.X509.SerialNumber + " has been rejected due to the status " + certStatus);
                return false; // Certificate is rejected
            }
        } else {
            // OCSP is disabled, perform internal verification
            if (checkTLSCert()) {
                return true; // Certificate is valid
            }
        }
    } else {
        radius.reply['Reply-Message'] = "Certificate not found";
        return false; // Certificate is not found
    }
}
- checkTLSCert

checkTLSCert()

This function uses the certificate serial number to retrieve client EAP-TLS certificate information from the SpherAAA's internal PKI. When a certificate exists and not revoked/disabled , the return value is certificate data as a JSON:

{
  "comment": "Generated by SpherAAA. Root Cert SN 14520661811939377238",
  "created": {
    "$date": 1662143034000
  },
  "expires": {
    "$date": 1725215034000
  },
  "subject": "CN=anonymous@company.com,OU=USERID_NAME,O=SpherAAA-Client,L=City,ST=NY,C=US",
  "issuer": "CN=aaa.spheralogic.com,OU=1,O=SpheraLogic LLC-Root,L=NewYork,ST=NY,C=US",
  "serial_number": "17588163288774580330",
  "revoke_status": false
}
The function returns false if the certificate has been revoked or not found.

- EAP-AKA Privacy

The EAP-AKA Privacy is permanent identity of an EAP client using RSA-OAEP encryption scheme with a 2048-bit RSA key and SHA-256 hashing. The encrypted permanent identity is sent to the authentication server with an optional key identifier field, CertificateSerialNumber=1234567890 to help the server locate the private key to decrypt the identity. The client uses the RSA public key of the server to encrypt the identity and Base64 encoding of the encrypted data provides a string of 344 ASCII characters. The 2048-bit key size is necessary to avoid exceeding the maximum transfer unit of the AVP value.

The EapAkaIdentity() function initiates an Access-Challenge request by setting the EAP-Type to "request" and the EAP-SubType to AT_ANY_ID_REQ. The AAA server expects to receive an Access-Request containing an encrypted AKA_IDENTITY encoded using base64. Upon successful decryption, the function returns AKA-Identity-Done which provides information about the decrypted permanent identity and certificate serial number from the request in the user group:

"user": {
        "3GPP-Identity": {
            "Permanent-Identity": "0123456789012345@wlan.mnc456.mcc123.3gppnetwork.org",
            "Certificate-Serial-Number": "1234567890"
        },
        "Identity": "anonymous",
        "Realm": "wlan.mnc456.mcc123.3gppnetwork.org",
        "Timestamp": "2023/01/01 00:00:00"
    },

HTTPClient

This function designed to create an HTTP client for making HTTP requests. It's a constructor function that can be used to create instances of an HTTP client.

Here's a breakdown of the key properties and methods defined within the HTTPClient function:

  1. url: A property that represents the URL to which the HTTP/HTTPS request will be sent.

  2. disableSslVerification: Turns off the verification or checking process for SSL certificates when making HTTPS connections. Default is false

  3. method: A property that specifies the HTTP method for the request. The default method is set to "GET," but it can be changed to other HTTP methods like "POST", "PUT" and "DELETE".

  4. headers: An object property that holds HTTP headers to be included in the request. It allows you to set custom headers for your HTTP request.

  5. data: An object property that holds the data to be sent in the HTTP request's body. This is typically used for methods like "POST" or "PUT" to send data to the server.

  6. send method: This method is used to actually send the HTTP request.

Here's an example of how you might use the HTTPClient constructor to make an HTTP GET request:

// Create an instance of HTTPClient
var client = new HTTPClient();

// Set the URL to which the request will be sent
client.url = "http://time.jsontest.com/";

// Send the GET request
try {
  var response = client.send();
  // Log the response
  log(response);
} 
 // Catch exceptions (4XX-5XX and all other errors) 
 catch (e) {
  // Log the received exception.
  log("Error: " + e.toString());
}

Result of response

{
  "response_code": 200,
  "response_data": {
    "date": "07-30-2022",
    "milliseconds_since_epoch": 1659147162342,
    "time": "02:12:42 AM"
  }
}

To use the HTTPClient for making a POST request, you can follow a similar pattern as before, but this time you'll set the method property to "POST" and provide data in the data property. Here's an example:

// Create an instance of HTTPClient
var client = new HTTPClient();

// Set the URL to which the POST request will be sent
client.url = "https://example.com/api/post-endpoint";

// Set the HTTP method to POST
client.method = "POST";

// Set custom headers if needed (optional)
client.headers = {
  "Content-Type": "application/json", // Assuming you're sending JSON data
  "Authorization": "Bearer YOUR_ACCESS_TOKEN" // Add any authentication headers if required
};

// Disable SSL Verification
client.disableSslVerification = true;

// Define the data to be sent in the request body
client.data = {
  username: "exampleUser",
  password: "examplePassword"
};

// Send the POST request
try {
  var post_response = client.send();
  // Log the response
  log(post_response);
} 
 // Catch exceptions (4XX-5XX and all other errors) 
 catch (e) {
  // Log the received exception.
  log("Error: " + e.toString());
}

Result of post_response

{
  "response_code":200,
  "response_data":"Thank you for this dump. I hope you have a lovely day!"
}

In this example, we've configured the HTTPClient instance to make a POST request by setting the method property to "POST." We've also specified custom headers (like Content-Type and Authorization) if they are required for your API. Finally, we've provided the data to be sent in the request body as an object in the data property. Adjust the URL, headers, and data to match your specific use case and API requirements.

Result in Error / Debug logs

Secure page

Error / Debug logs

Utilites

MAC lookup

getVendor(mac)

MAC OUI stands for "Media Access Control Organizationally Unique Identifier." It is a 24-bit number that is used to uniquely identify the manufacturer of a network interface controller (NIC) in a device. The first three octets (24 bits) of a MAC address represent the OUI, which is assigned by the Institute of Electrical and Electronics Engineers (IEEE). The remaining three octets represent the device identifier, which is assigned by the manufacturer.

MAC OUI can be useful for network administrators in several ways:

  • Device identification: The MAC OUI allows network administrators to identify the manufacturer of a network device, which can be useful for determining the device's capabilities, compatibility with other devices, and support options.

  • Network troubleshooting: Knowing the manufacturer of a device can also help network administrators to troubleshoot issues with the device, as they can look up the device specifications and known issues to diagnose and resolve problems.

  • Inventory management: By knowing the manufacturer of a device, network administrators can keep track of their network inventory, which can be useful for managing and maintaining the network.

  • Security management: The MAC OUI can also be used to identify and track devices on the network, which can be useful for network security management. For example, if an unknown device is detected on the network, the MAC OUI can be used to identify the device and determine if it should be allowed on the network.

The MAC OUI can provide valuable information for network administrators in managing and maintaining their networks, as well as for ensuring the security and privacy of their network users.

SpherAAA has an internal function that allows the lookup of a device NIC vendor based on its MAC address.

getVendor(mac) if a mac address was identified, returns the manufacturer name; otherwise returns private.

Example

var vendor = getVendor(radius.request['Normalized-Mac']);

//Reject all devices manufactured by Apple.
if (vendor == "Apple, Inc.") {
  fail("The use of Apple devices is prohibited.");
  return;
}
For more information, including for vendor names, please visit IEEE website

Parallel processing

A function can be called in a new thread without delaying the current execution or response. 

runParallel('function_name', delay_ms)

here:

  • function_name: is a name of calling function

  • delay_ms: Delays execution of function in milliseconds. Default is 500ms

Example: This example was taken from Accounting PolicyLogic:

//
// Starting point
//

function start() {

    //
    // To avoid delaying the Accounting-Response, execute the Session update function in parallel.
    // So first return Acct-Response
    success();
    //
    // and then insert / update "sessions" collection
    //
    runParallel('acct()', 500);
  }

  //
  // Update sessions collection.
  //
  function acct() {
    // function: upsertEntry(collectionName, object, TTL, indexName, indexValue);
    upsertEntry("sessions", radius.request, 86400, "Calling-Station-Id", radius.request['Calling-Station-Id']);
  }

Logging

log(message)

For debugging, you can call the internal function log(message). To check the result of log(), please use the Logs > Error/Debug logs menu on the SpherAAA Dashboard.

Crypto

Encryption: The encryption method takes in a secret key and a string of data to encrypt. It uses the Advanced Encryption Standard (AES) algorithm in Electronic Codebook (ECB) mode with PKCS5Padding to encrypt the data ("AES/ECB/PKCS5Padding"). The method returns a Base64-encoded string representation of the encrypted data.

Crypto.encrypt(plain_text, key)

plain_text - Input text

key - key with the size of 16, 24 or 32 bytes:

Example

var clear_text = "my private data";
var key = "gSp42mSu75EZaEgZ";

var encryted_text = Crypto.encrypt(clear_text,key);

Output: "jpiGHk7IU21tAoduyXFf7A=="

Decryption: The decryption method takes in a secret key and a Base64-encoded string of data to decrypt. It uses the same AES algorithm in ECB mode with PKCS5Padding to decrypt the data ("AES/ECB/PKCS5Padding"). The method returns the decrypted data as a string.

Crypto.decrypt(encryted_text, key)

encryted_text - Encrypted input text

key - key with the size of 16, 24 or 32 bytes:

Example

var encryted_text = "jpiGHk7IU21tAoduyXFf7A==";
var key = "gSp42mSu75EZaEgZ";

var plain_text = Crypto.decrypt(encryted_text,key);

Output: "my private data"

Base64

Base64 works by converting groups of three 8-bit binary bytes into four 6-bit characters, which are then represented using the standard ASCII character set. This process results in a larger output size compared to the input size, but the encoded data can be easily transmitted over communication channels that may not support binary data.

For example, if we take the string "Hello, World!" and convert it to Base64 encoding, the resulting output would be "SGVsbG8sIFdvcmxkIQ==". This encoding can then be sent as a text message or used in a URL without any issues.

Base64.encode(sting)

var b64 = Base64.encode("test");

output: b64="dGVzdA=="

Base64 encoding is reversible, meaning that the original binary data can be retrieved by decoding the Base64 string using a Base64 decoder. This makes it a useful tool for encoding and decoding data for various purposes, such as data transmission and data storage.

Base64.decode(base64_string)

var text = Base64.decode("dGVzdA==");

output: text="test";

HMAC

The HMAC algorithm takes a message and a secret key as input and produces a fixed-size hash value as output.

hmac(algorithm, message, secret_key)

The HMAC algorithm is commonly used to verify the integrity and authenticity of a message. To create an HMAC, the message is first combined with the secret key using a specific algorithm, such as SHA-256 or MD5. The resulting hash is then combined with the secret key again using the same algorithm. This process creates a unique signature for the message that can only be verified by someone who knows the secret key.

Supported alogorithims:

  • HmacMD5
  • HmacSHA1
  • HmacSHA224
  • HmacSHA256
  • HmacSHA384
  • HmacSHA512
  var algo = "HmacSHA256";
  var message  = "users private data";
  var secret_key = "secret";

  var hashed_value = hmac(algo, message, secret_key);

  output: hashed_value="10e80bacf0225e6b3defe6664a7f026f54d021a16a64a38d6ad29a848113e51e"