-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: added replace pre created user tests
- Loading branch information
Showing
7 changed files
with
608 additions
and
3 deletions.
There are no files selected for viewing
200 changes: 200 additions & 0 deletions
200
src/externals/asymmetric-encryption/asymmetric-encryption.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(''); | ||
}); | ||
}); |
Oops, something went wrong.