Files
smartradius/test/server/test.chap.ts

210 lines
7.2 KiB
TypeScript
Raw Normal View History

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as crypto from 'node:crypto';
import { RadiusAuthenticator } from '../../ts_server/index.js';
tap.test('should calculate CHAP response per RFC', async () => {
// CHAP-Response = MD5(CHAP-ID + Password + Challenge)
const chapId = 42;
const password = 'mypassword';
const challenge = crypto.randomBytes(16);
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
// Verify manually
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.length).toEqual(16);
expect(response.equals(expected)).toBeTruthy();
});
tap.test('should verify valid CHAP response', async () => {
const chapId = 1;
const password = 'correctpassword';
const challenge = crypto.randomBytes(16);
// Calculate the expected response
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
// Build CHAP-Password attribute: CHAP Ident (1 byte) + Response (16 bytes)
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
// Should verify with correct password
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
});
tap.test('should reject CHAP response with wrong password', async () => {
const chapId = 1;
const correctPassword = 'correctpassword';
const wrongPassword = 'wrongpassword';
const challenge = crypto.randomBytes(16);
// Calculate response with correct password
const response = RadiusAuthenticator.calculateChapResponse(chapId, correctPassword, challenge);
// Build CHAP-Password attribute
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
// Should NOT verify with wrong password
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, wrongPassword);
expect(isValid).toBeFalsy();
});
tap.test('should reject CHAP response with wrong challenge', async () => {
const chapId = 1;
const password = 'mypassword';
const correctChallenge = crypto.randomBytes(16);
const wrongChallenge = crypto.randomBytes(16);
// Calculate response with correct challenge
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, correctChallenge);
// Build CHAP-Password attribute
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
// Should NOT verify with wrong challenge
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, wrongChallenge, password);
expect(isValid).toBeFalsy();
});
tap.test('should reject CHAP response with wrong identifier', async () => {
const correctChapId = 1;
const wrongChapId = 2;
const password = 'mypassword';
const challenge = crypto.randomBytes(16);
// Calculate response with correct CHAP ID
const response = RadiusAuthenticator.calculateChapResponse(correctChapId, password, challenge);
// Build CHAP-Password with WRONG CHAP ID
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(wrongChapId, 0); // Wrong ID
response.copy(chapPassword, 1);
// Should NOT verify
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeFalsy();
});
tap.test('should reject invalid CHAP-Password length', async () => {
const challenge = crypto.randomBytes(16);
const password = 'mypassword';
// CHAP-Password must be exactly 17 bytes (1 + 16)
const invalidChapPassword = Buffer.alloc(16); // Too short
const isValid = RadiusAuthenticator.verifyChapResponse(invalidChapPassword, challenge, password);
expect(isValid).toBeFalsy();
const tooLongChapPassword = Buffer.alloc(18); // Too long
const isValid2 = RadiusAuthenticator.verifyChapResponse(tooLongChapPassword, challenge, password);
expect(isValid2).toBeFalsy();
});
tap.test('should handle all CHAP ID values (0-255)', async () => {
const password = 'testpassword';
const challenge = crypto.randomBytes(16);
for (const chapId of [0, 1, 127, 128, 254, 255]) {
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
}
});
tap.test('should handle special characters in password', async () => {
const chapId = 1;
const password = '!@#$%^&*()_+-=[]{}|;:,.<>?~`';
const challenge = crypto.randomBytes(16);
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
});
tap.test('should handle unicode password', async () => {
const chapId = 1;
const password = '密码パスワード'; // Chinese + Japanese
const challenge = crypto.randomBytes(16);
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
});
tap.test('should handle empty password', async () => {
const chapId = 1;
const password = '';
const challenge = crypto.randomBytes(16);
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
});
tap.test('should handle very long password', async () => {
const chapId = 1;
const password = 'a'.repeat(1000); // Very long password
const challenge = crypto.randomBytes(16);
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
});
tap.test('should handle different challenge lengths', async () => {
const chapId = 1;
const password = 'testpassword';
// CHAP challenge can be various lengths
for (const length of [8, 16, 24, 32]) {
const challenge = crypto.randomBytes(length);
const response = RadiusAuthenticator.calculateChapResponse(chapId, password, challenge);
const chapPassword = Buffer.allocUnsafe(17);
chapPassword.writeUInt8(chapId, 0);
response.copy(chapPassword, 1);
const isValid = RadiusAuthenticator.verifyChapResponse(chapPassword, challenge, password);
expect(isValid).toBeTruthy();
}
});
export default tap.start();