Files
smartradius/test/server/test.authenticator.ts

206 lines
5.9 KiB
TypeScript
Raw Normal View History

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as crypto from 'crypto';
import {
RadiusAuthenticator,
RadiusPacket,
ERadiusCode,
ERadiusAttributeType,
} from '../../ts_server/index.js';
tap.test('should generate 16-byte random request authenticator', async () => {
const auth1 = RadiusAuthenticator.generateRequestAuthenticator();
const auth2 = RadiusAuthenticator.generateRequestAuthenticator();
expect(auth1.length).toEqual(16);
expect(auth2.length).toEqual(16);
// Should be random (different each time)
expect(auth1.equals(auth2)).toBeFalsy();
});
tap.test('should calculate response authenticator correctly', async () => {
const code = ERadiusCode.AccessAccept;
const identifier = 1;
const requestAuthenticator = crypto.randomBytes(16);
const attributes = Buffer.from([]);
const secret = 'testing123';
const responseAuth = RadiusAuthenticator.calculateResponseAuthenticator(
code,
identifier,
requestAuthenticator,
attributes,
secret
);
expect(responseAuth.length).toEqual(16);
// Verify by recalculating
const verified = RadiusAuthenticator.calculateResponseAuthenticator(
code,
identifier,
requestAuthenticator,
attributes,
secret
);
expect(responseAuth.equals(verified)).toBeTruthy();
});
tap.test('should calculate accounting request authenticator', async () => {
const code = ERadiusCode.AccountingRequest;
const identifier = 1;
const attributes = Buffer.from([]);
const secret = 'testing123';
const acctAuth = RadiusAuthenticator.calculateAccountingRequestAuthenticator(
code,
identifier,
attributes,
secret
);
expect(acctAuth.length).toEqual(16);
});
tap.test('should verify accounting request authenticator', async () => {
const secret = 'testing123';
// Create an accounting request packet
const packet = RadiusPacket.createAccountingRequest(1, secret, [
{ type: ERadiusAttributeType.AcctStatusType, value: 1 }, // Start
{ type: ERadiusAttributeType.AcctSessionId, value: 'session-123' },
]);
// Verify the authenticator
const isValid = RadiusAuthenticator.verifyAccountingRequestAuthenticator(packet, secret);
expect(isValid).toBeTruthy();
// Should fail with wrong secret
const isInvalid = RadiusAuthenticator.verifyAccountingRequestAuthenticator(packet, 'wrongsecret');
expect(isInvalid).toBeFalsy();
});
tap.test('should verify response authenticator', async () => {
const identifier = 1;
const requestAuthenticator = crypto.randomBytes(16);
const secret = 'testing123';
// Create a response packet
const responsePacket = RadiusPacket.createAccessAccept(
identifier,
requestAuthenticator,
secret,
[{ type: ERadiusAttributeType.ReplyMessage, value: 'Welcome' }]
);
// Verify the authenticator
const isValid = RadiusAuthenticator.verifyResponseAuthenticator(
responsePacket,
requestAuthenticator,
secret
);
expect(isValid).toBeTruthy();
// Should fail with wrong request authenticator
const wrongRequestAuth = crypto.randomBytes(16);
const isInvalid = RadiusAuthenticator.verifyResponseAuthenticator(
responsePacket,
wrongRequestAuth,
secret
);
expect(isInvalid).toBeFalsy();
// Should fail with wrong secret
const isInvalid2 = RadiusAuthenticator.verifyResponseAuthenticator(
responsePacket,
requestAuthenticator,
'wrongsecret'
);
expect(isInvalid2).toBeFalsy();
});
tap.test('should create packet header', async () => {
const code = ERadiusCode.AccessRequest;
const identifier = 42;
const authenticator = crypto.randomBytes(16);
const attributesLength = 50;
const header = RadiusAuthenticator.createPacketHeader(
code,
identifier,
authenticator,
attributesLength
);
expect(header.length).toEqual(20);
expect(header[0]).toEqual(code);
expect(header[1]).toEqual(identifier);
expect(header.readUInt16BE(2)).toEqual(20 + attributesLength);
expect(header.subarray(4, 20).equals(authenticator)).toBeTruthy();
});
tap.test('should calculate CHAP response', async () => {
const chapId = 1;
const password = 'testpassword';
const challenge = crypto.randomBytes(16);
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
expect(response.length).toEqual(16);
// Verify the calculation: MD5(CHAP-ID + Password + Challenge)
const md5 = crypto.createHash('md5');
md5.update(Buffer.from([chapId]));
md5.update(Buffer.from(password, 'utf8'));
md5.update(challenge);
const expected = md5.digest();
expect(response.equals(expected)).toBeTruthy();
});
tap.test('should verify CHAP response', async () => {
const chapId = 1;
const password = 'testpassword';
const challenge = crypto.randomBytes(16);
// Calculate the response
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
// Create CHAP-Password attribute format: CHAP-ID (1 byte) + Response (16 bytes)
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
// Verify with correct password
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
// Verify with wrong password
const isInvalid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, 'wrongpassword');
expect(isInvalid).toBeFalsy();
});
tap.test('should calculate Message-Authenticator (HMAC-MD5)', async () => {
const packet = Buffer.alloc(50);
packet[0] = ERadiusCode.AccessRequest;
packet[1] = 1;
packet.writeUInt16BE(50, 2);
crypto.randomBytes(16).copy(packet, 4);
const secret = 'testing123';
const msgAuth = RadiusAuthenticator.calculateMessageAuthenticator(packet, secret);
expect(msgAuth.length).toEqual(16);
// Verify it's HMAC-MD5
const hmac = crypto.createHmac('md5', Buffer.from(secret, 'utf8'));
hmac.update(packet);
const expected = hmac.digest();
expect(msgAuth.equals(expected)).toBeTruthy();
});
export default tap.start();