Files
comet/test/test_crypt.pas
Ken Johnson 46912a9798
Some checks failed
Build and Release / build-and-release (push) Has been cancelled
Version 1.1-1, standardize naming and move tests
- Adopt standard versioning: 1.0, 1.1, 1.1-1 (major.minor-revision)
- Archive naming: comet-1.1-1-linux-x64.tar.gz (professional format)
- Move test sources from src/ to test/ directory
- Update Makefile, release script, and CI workflow to match
2026-04-07 09:00:39 -07:00

248 lines
6.3 KiB
ObjectPascal

{
test_crypt.pas - Test X25519 key exchange and ChaCha20 encryption
}
program test_crypt;
{$mode objfpc}{$H+}
uses
SysUtils, cometcrypt, cometed25519;
var
TestCount, PassCount: Integer;
procedure Check(const Name: string; Condition: Boolean);
begin
Inc(TestCount);
if Condition then
begin
Inc(PassCount);
WriteLn(Name, ' OK');
end
else
WriteLn(Name, ' FAIL');
end;
procedure TestChaCha20;
var
S1, S2: TChacha20State;
Key: array[0..31] of Byte;
Original, Data: array[0..999] of Byte;
Cipher1: array[0..999] of Byte;
BigData: array[0..65535] of Byte;
BigOrig: array[0..65535] of Byte;
I: Integer;
AllMatch: Boolean;
begin
WriteLn('--- ChaCha20 ---');
{ Create a test key }
for I := 0 to 31 do
Key[I] := Byte(I * 7 + 3);
{ Create test data }
for I := 0 to 999 do
Original[I] := Byte(I * 13 + 42);
Move(Original, Data, 1000);
{ Encrypt }
ChaCha20Init(S1, Key);
ChaCha20Crypt(S1, @Data[0], 1000);
{ Verify ciphertext differs from plaintext }
AllMatch := True;
for I := 0 to 999 do
if Data[I] <> Original[I] then AllMatch := False;
Check('ChaCha20: ciphertext differs', not AllMatch);
{ Decrypt with same key }
ChaCha20Init(S2, Key);
ChaCha20Crypt(S2, @Data[0], 1000);
{ Verify plaintext recovered }
AllMatch := True;
for I := 0 to 999 do
if Data[I] <> Original[I] then AllMatch := False;
Check('ChaCha20: decrypt recovers plaintext', AllMatch);
{ Test incremental: encrypt in chunks matches encrypt all-at-once }
Move(Original, Data, 1000);
ChaCha20Init(S1, Key);
ChaCha20Crypt(S1, @Data[0], 1000);
Move(Data, Cipher1, 1000);
{ Encrypt same data in small chunks }
Move(Original, Data, 1000);
ChaCha20Init(S2, Key);
ChaCha20Crypt(S2, @Data[0], 100);
ChaCha20Crypt(S2, @Data[100], 300);
ChaCha20Crypt(S2, @Data[400], 600);
AllMatch := True;
for I := 0 to 999 do
if Data[I] <> Cipher1[I] then AllMatch := False;
Check('ChaCha20: chunked matches whole', AllMatch);
{ Large block test - 64KB }
for I := 0 to 65535 do
BigOrig[I] := Byte(I xor (I shr 8));
Move(BigOrig, BigData, 65536);
ChaCha20Init(S1, Key);
ChaCha20Crypt(S1, @BigData[0], 65536);
ChaCha20Init(S2, Key);
ChaCha20Crypt(S2, @BigData[0], 65536);
AllMatch := True;
for I := 0 to 65535 do
if BigData[I] <> BigOrig[I] then AllMatch := False;
Check('ChaCha20: 64KB round-trip', AllMatch);
end;
procedure TestX25519;
var
PrivA, PubA, PrivB, PubB: TX25519Key;
SecretA, SecretB: TX25519Key;
I: Integer;
Match, NonZeroA, NonZeroB, Differ, NonZeroS: Boolean;
begin
WriteLn('--- X25519 ---');
{ Generate two keypairs }
X25519Keypair(PrivA, PubA);
X25519Keypair(PrivB, PubB);
{ Keys should not be all zeros }
NonZeroA := False;
NonZeroB := False;
for I := 0 to 31 do
begin
if PubA[I] <> 0 then NonZeroA := True;
if PubB[I] <> 0 then NonZeroB := True;
end;
Check('X25519: pubkey A non-zero', NonZeroA);
Check('X25519: pubkey B non-zero', NonZeroB);
{ Keys should differ }
Differ := False;
for I := 0 to 31 do
if PubA[I] <> PubB[I] then Differ := True;
Check('X25519: pubkeys differ', Differ);
{ Diffie-Hellman: both sides compute same shared secret }
Check('X25519: DH A succeeds', X25519SharedSecret(PrivA, PubB, SecretA));
Check('X25519: DH B succeeds', X25519SharedSecret(PrivB, PubA, SecretB));
Match := True;
for I := 0 to 31 do
if SecretA[I] <> SecretB[I] then Match := False;
Check('X25519: shared secrets match', Match);
{ Shared secret should not be all zeros }
NonZeroS := False;
for I := 0 to 31 do
if SecretA[I] <> 0 then NonZeroS := True;
Check('X25519: shared secret non-zero', NonZeroS);
end;
procedure TestSessionCrypt;
var
PrivA, PubA, PrivB, PubB, SecretA, SecretB: TX25519Key;
CryptOrig, CryptAnsw: TCometCrypt;
Data, Original: array[0..511] of Byte;
I: Integer;
AllMatch: Boolean;
begin
WriteLn('--- Session Encryption ---');
{ Simulate key exchange }
X25519Keypair(PrivA, PubA);
X25519Keypair(PrivB, PubB);
X25519SharedSecret(PrivA, PubB, SecretA);
X25519SharedSecret(PrivB, PubA, SecretB);
{ Initialize both sides }
CometCryptInit(CryptOrig, SecretA, PubA, PubB, True); { originator }
CometCryptInit(CryptAnsw, SecretB, PubB, PubA, False); { answerer }
Check('Session: originator active', CryptOrig.Active);
Check('Session: answerer active', CryptAnsw.Active);
{ Originator sends frame to answerer }
for I := 0 to 511 do
Original[I] := Byte(I * 3 + 17);
Move(Original, Data, 512);
CometCryptEncrypt(CryptOrig, @Data[0], 512);
{ Verify encrypted }
AllMatch := True;
for I := 0 to 511 do
if Data[I] <> Original[I] then AllMatch := False;
Check('Session: originator->answerer encrypted', not AllMatch);
{ Answerer decrypts }
CometCryptDecrypt(CryptAnsw, @Data[0], 512);
AllMatch := True;
for I := 0 to 511 do
if Data[I] <> Original[I] then AllMatch := False;
Check('Session: answerer decrypts correctly', AllMatch);
{ Answerer sends frame to originator }
for I := 0 to 511 do
Original[I] := Byte(I * 11 + 99);
Move(Original, Data, 512);
CometCryptEncrypt(CryptAnsw, @Data[0], 512);
CometCryptDecrypt(CryptOrig, @Data[0], 512);
AllMatch := True;
for I := 0 to 511 do
if Data[I] <> Original[I] then AllMatch := False;
Check('Session: answerer->originator round-trip', AllMatch);
{ Multiple frames in sequence - counters advance correctly }
for I := 0 to 511 do
Original[I] := Byte(I);
Move(Original, Data, 512);
CometCryptEncrypt(CryptOrig, @Data[0], 512);
CometCryptDecrypt(CryptAnsw, @Data[0], 512);
AllMatch := True;
for I := 0 to 511 do
if Data[I] <> Original[I] then AllMatch := False;
Check('Session: second frame round-trip', AllMatch);
{ Third frame }
Move(Original, Data, 512);
CometCryptEncrypt(CryptOrig, @Data[0], 512);
CometCryptDecrypt(CryptAnsw, @Data[0], 512);
AllMatch := True;
for I := 0 to 511 do
if Data[I] <> Original[I] then AllMatch := False;
Check('Session: third frame round-trip', AllMatch);
end;
begin
TestCount := 0;
PassCount := 0;
TestChaCha20;
TestX25519;
TestSessionCrypt;
WriteLn;
WriteLn(PassCount, '/', TestCount, ' tests passed.');
if PassCount = TestCount then
WriteLn('All crypto tests passed.')
else
begin
WriteLn('SOME TESTS FAILED');
Halt(1);
end;
end.