Skip to content

Commit

Permalink
chore: added replace pre created user tests
Browse files Browse the repository at this point in the history
  • Loading branch information
apsantiso committed Feb 4, 2025
1 parent a70fe50 commit 065bab4
Show file tree
Hide file tree
Showing 7 changed files with 608 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AsymmetricEncryptionService } from './asymmetric-encryption.service';
import { KyberBuilder, kyberProvider } from './providers/kyber.provider';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import * as openpgp from './openpgp';
import * as utils from './utils';

jest.mock('./openpgp');
jest.mock('./utils');

describe('AsymmetricEncryptionService', () => {
let service: AsymmetricEncryptionService;
let kyberKem: DeepMocked<KyberBuilder>;

beforeEach(async () => {
kyberKem = createMock();

const module: TestingModule = await Test.createTestingModule({
providers: [
AsymmetricEncryptionService,
{
provide: kyberProvider.provide,
useValue: kyberKem,
},
],
}).compile();

service = module.get<AsymmetricEncryptionService>(
AsymmetricEncryptionService,
);
});

it('When tests are created, then expected mocks should be created', () => {
expect(service).toBeDefined();
expect(kyberKem).toBeDefined();
});

describe('generateKyberKeys', () => {
it('When called, should generate a pair of keys', async () => {
const publicKey = 'publicKey';
const privateKey = 'privateKey';

kyberKem.keypair.mockResolvedValue({
publicKey: Buffer.from(publicKey),
privateKey: Buffer.from(privateKey),
});

const keys = await service.generateKyberKeys();

expect(keys).toEqual({
publicKey: Buffer.from(publicKey).toString('base64'),
privateKey: Buffer.from(privateKey).toString('base64'),
});
});
});

describe('encapsulateWithKyber', () => {
it('When called, then it should encapsulate secret with kyber keys', async () => {
const mockCiphertext = new Uint8Array([1, 2, 3]);
const mockSecret = new Uint8Array([4, 5, 6]);
kyberKem.encapsulate.mockResolvedValue({
ciphertext: mockCiphertext,
sharedSecret: mockSecret,
});

const result = await service.encapsulateWithKyber(
new Uint8Array([7, 8, 9]),
);

expect(result).toEqual({
ciphertext: mockCiphertext,
sharedSecret: mockSecret,
});
});
});

describe('decapsulateWithKyber', () => {
it('When called, then it should decapsulate secret with kyber keys', async () => {
const mockSecret = new Uint8Array([4, 5, 6]);
kyberKem.decapsulate.mockResolvedValue({ sharedSecret: mockSecret });

const result = await service.decapsulateWithKyber(
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6]),
);

expect(result).toEqual(mockSecret);
});
});

describe('hybridEncryptMessageWithPublicKey', () => {
it('When kyber key is passed, then it should encrypt with hybrid mode', async () => {
const mockCipherText = new Uint8Array([4, 5, 6]);
const mockSharedSecret = new Uint8Array([4, 5, 6]);

jest.spyOn(utils, 'extendSecret').mockResolvedValue('extendedSecret');
jest.spyOn(utils, 'XORhex').mockReturnValue('xoredMessage');
jest
.spyOn(openpgp, 'encryptMessageWithPublicKey')
.mockResolvedValue('encryptedECCMessage');

jest.spyOn(service, 'encapsulateWithKyber').mockResolvedValueOnce({
ciphertext: mockCipherText,
sharedSecret: mockSharedSecret,
});

const result = await service.hybridEncryptMessageWithPublicKey({
message: 'Hello',
publicKeyInBase64: 'ECCPublicKey',
publicKyberKeyBase64: 'KyberPublicKey',
});

expect(result).toContain('SHlicmlkTW9kZQ==');
expect(result).toContain('$'); // Hybrid format separator
});

it('When no Kyber key is passed, then it should encrypt using ECC only', async () => {
const encryptedMessage = Buffer.from('encryptedECCMessage').toString(
'binary',
);
const expectedBase64 = Buffer.from(encryptedMessage, 'binary').toString(
'base64',
);

jest
.spyOn(openpgp, 'encryptMessageWithPublicKey')
.mockResolvedValue(encryptedMessage);

const result = await service.hybridEncryptMessageWithPublicKey({
message: 'Hello',
publicKeyInBase64: 'ECCPublicKey',
});

expect(result).toContain(expectedBase64);
expect(result).not.toContain('$');
});
});

describe('hybridDecryptMessageWithPrivateKey', () => {
it('When both ECC and Kyber keys are passed, then it should decrypt using hybrid mode', async () => {
const mockDecryptedMessage = 'decryptedMessage';
const mockSharedSecret = new Uint8Array([4, 5, 6]);
const mockXoredMessage =
Buffer.from(mockDecryptedMessage).toString('hex');
const mockDecryptedECCMessage = 'decryptedECCMessage';

jest
.spyOn(service, 'decapsulateWithKyber')
.mockResolvedValue(mockSharedSecret);
jest
.spyOn(openpgp, 'decryptMessageWithPrivateKey')
.mockResolvedValue(mockDecryptedECCMessage);

jest.spyOn(utils, 'extendSecret').mockResolvedValue('extendedSecret');
jest.spyOn(utils, 'XORhex').mockReturnValue(mockXoredMessage);

const encryptedMessageInBase64 =
'SHlicmlkTW9kZQ==$mockKyberCiphertext$mockEccCiphertext';

const result = await service.hybridDecryptMessageWithPrivateKey({
encryptedMessageInBase64,
privateKeyInBase64: 'ECCPrivateKey',
privateKyberKeyInBase64: 'KyberPrivateKey',
});

expect(result).toEqual(mockDecryptedMessage);
});

it('When no Kyber key is passed, then it should decrypt using ECC only', async () => {
const mockDecryptedECCMessage = 'Any decrypted text';

jest
.spyOn(openpgp, 'decryptMessageWithPrivateKey')
.mockResolvedValue(mockDecryptedECCMessage);

const encryptedMessageInBase64 = 'mockEccCiphertextBase64';

const result = await service.hybridDecryptMessageWithPrivateKey({
encryptedMessageInBase64,
privateKeyInBase64: 'ECCPrivateKey',
});

expect(result).toEqual(mockDecryptedECCMessage);
});

it('When attempting to decrypt a hybrid message without a Kyber private key, then it should throw error', async () => {
const encryptedMessageInBase64 =
'SHlicmlkTW9kZQ==$mockKyberCiphertext$mockEccCiphertext';

await expect(
service.hybridDecryptMessageWithPrivateKey({
encryptedMessageInBase64,
privateKeyInBase64: 'ECCPrivateKey',
}),
).rejects.toThrow(
'Attempted to decrypt hybrid ciphertex without Kyber key',
);
});
});
});
110 changes: 110 additions & 0 deletions src/externals/asymmetric-encryption/openpgp.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
generateNewKeys,
decryptMessageWithPrivateKey,
encryptMessageWithPublicKey,
} from './openpgp';
import * as openpgp from 'openpgp';

jest.mock('openpgp', () => ({
generateKey: jest.fn(),
readPrivateKey: jest.fn(),
readMessage: jest.fn(),
decrypt: jest.fn(),
readKey: jest.fn(),
encrypt: jest.fn(),
createMessage: jest.fn(),
}));

describe('PGP Utils', () => {
describe('generateNewKeys', () => {
it('When generating new keys, then it should return keys in base64 format', async () => {
const mockPrivateKey = 'mockPrivateKey';
const mockPublicKey = 'mockPublicKey';
const mockRevocationCert = 'mockRevocationCert';

(openpgp.generateKey as jest.Mock).mockResolvedValue({
privateKey: mockPrivateKey,
publicKey: mockPublicKey,
revocationCertificate: mockRevocationCert,
});

const result = await generateNewKeys();

expect(openpgp.generateKey).toHaveBeenCalledWith({
userIDs: [{ email: '[email protected]' }],
curve: 'ed25519',
date: undefined,
});
expect(result.privateKeyArmored).toBe(mockPrivateKey);
expect(result.publicKeyArmored).toBe(
Buffer.from(mockPublicKey).toString('base64'),
);
expect(result.revocationCertificate).toBe(
Buffer.from(mockRevocationCert).toString('base64'),
);
});
});

describe('decryptMessageWithPrivateKey', () => {
it('When a valid encrypted message and private key are provided, then it should decrypt the message successfully', async () => {
const mockEncryptedMessage = 'mockEncryptedMessage';
const mockPrivateKey = 'mockPrivateKeyBase64';
const mockDecryptedMessage = 'Decrypted message';

(openpgp.readPrivateKey as jest.Mock).mockResolvedValue(mockPrivateKey);
(openpgp.readMessage as jest.Mock).mockResolvedValue(
mockEncryptedMessage,
);
(openpgp.decrypt as jest.Mock).mockResolvedValue({
data: mockDecryptedMessage,
});

const result = await decryptMessageWithPrivateKey({
encryptedMessage: mockEncryptedMessage,
privateKeyInBase64: mockPrivateKey,
});

expect(openpgp.readPrivateKey).toHaveBeenCalledWith({
armoredKey: mockPrivateKey,
});
expect(openpgp.readMessage).toHaveBeenCalledWith({
armoredMessage: mockEncryptedMessage,
});
expect(openpgp.decrypt).toHaveBeenCalledWith({
message: mockEncryptedMessage,
decryptionKeys: mockPrivateKey,
});
expect(result).toBe(mockDecryptedMessage);
});
});

describe('encryptMessageWithPublicKey', () => {
it('When a valid message and public key are provided, then it should encrypt the message successfully', async () => {
const mockMessage = 'Decrypted message';
const mockPublicKey = 'mockPublicKey';
const mockPublicKeyBase64 = Buffer.from(mockPublicKey).toString('base64');
const mockEncryptedMessage = 'mockEncryptedMessage';

(openpgp.readKey as jest.Mock).mockResolvedValue(mockPublicKey);
(openpgp.createMessage as jest.Mock).mockResolvedValue({
text: mockMessage,
});
(openpgp.encrypt as jest.Mock).mockResolvedValue(mockEncryptedMessage);

const result = await encryptMessageWithPublicKey({
message: mockMessage,
publicKeyInBase64: mockPublicKeyBase64,
});

expect(openpgp.readKey).toHaveBeenCalledWith({
armoredKey: mockPublicKey,
});
expect(openpgp.createMessage).toHaveBeenCalledWith({ text: mockMessage });
expect(openpgp.encrypt).toHaveBeenCalledWith({
message: { text: mockMessage },
encryptionKeys: mockPublicKey,
});
expect(result).toBe(mockEncryptedMessage);
});
});
});
73 changes: 73 additions & 0 deletions src/externals/asymmetric-encryption/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { extendSecret, XORhex } from './utils';

describe('extendSecret', () => {
it('When given a secret, then it should return a hashed value with the desired bit-length', async () => {
const secret = new Uint8Array([1, 2, 3, 4]);
const bitLength = 256;

const result = await extendSecret(secret, bitLength);

expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});

it('When given different inputs, then it should produce different hashes', async () => {
const secret1 = new Uint8Array([1, 2, 3, 4]);
const secret2 = new Uint8Array([5, 6, 7, 8]);
const bitLength = 256;

const hash1 = await extendSecret(secret1, bitLength);
const hash2 = await extendSecret(secret2, bitLength);

expect(hash1).not.toEqual(hash2);
});

it('When given the same input, then it should return the same hash', async () => {
const secret = new Uint8Array([1, 2, 3, 4]);
const bitLength = 256;

const hash1 = await extendSecret(secret, bitLength);
const hash2 = await extendSecret(secret, bitLength);

expect(hash1).toEqual(hash2);
});
});

describe('XORhex', () => {
it('When identical hex strings are XORed, then it should return a string of zeros', () => {
const hexString = 'deadbeef';
const expectedResult = '00000000';

expect(XORhex(hexString, hexString)).toBe(expectedResult);
});

it('When a fixed example is provided, it should return the expected result', async () => {
const firstHex = '74686973206973207468652074657374206d657373616765';
const secondHex = '7468697320697320746865207365636f6e64206d65737361';
const resultHex = '0000000000000000000000000700101b4e09451e16121404';

const xoredMessage = XORhex(firstHex, secondHex);

expect(xoredMessage).toEqual(resultHex);
});

it('When XORing with a zero-filled hex string, then it should return the original string', () => {
const a = '12345678';
const zeroString = '00000000';

expect(XORhex(a, zeroString)).toBe(a);
});

it('When input strings have different lengths, then it should throw an error', () => {
const a = '1234';
const b = 'abcd12';

expect(() => XORhex(a, b)).toThrow(
'Can XOR only strings with identical length',
);
});

it('When both input strings are empty, then it should return an empty string', () => {
expect(XORhex('', '')).toBe('');
});
});
Loading

0 comments on commit 065bab4

Please sign in to comment.