206 lines
5.9 KiB
TypeScript
206 lines
5.9 KiB
TypeScript
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();
|