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
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"
},
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 beingnull
. To check if variable is defined, the basic conditionif (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 totrue
during comparison. - Loose typing requires understanding rules behind type coercion:
3 + 3 + "3"
evaluates to63
whereas"3" + 3 + 3
yields333
. 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
but1 !== true
. - For all operations involving numbers, a good habit is to use the built-in
parseInt
andparseFloat
functions or a shorter+variable
expression syntax to perform number conversion. - Just like Bash, conditional expressions could be simplified with the
&&
and||
operators so that1 == "1" && print("yes")
simulates the longerif (1 == "1") { print("yes"); }
syntax. - A function always expects an arbitrary number of positional arguments, rendering missing values as
undefined
. The predefined array-likearguments
variable exposes the index access with square brackets. The only ways to access this variable are througharguments[i]
andarguments.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"
}
]
}
}
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 |
|
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;
}
}
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 |
|
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 |
|
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
}
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:
-
url
: A property that represents the URL to which the HTTP/HTTPS request will be sent. -
disableSslVerification
: Turns off the verification or checking process for SSL certificates when making HTTPS connections. Default isfalse
-
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". -
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. -
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. -
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
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;
}
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"