From c0da350c008324851cae2b1132a7b16ff72d56e3 Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Wed, 17 Jan 2024 18:20:14 +0000 Subject: [PATCH] fix(PrivateKeyStore): Make `retrieveUnboundSessionPublicKey` output `SessionKey` Because we actually need the key id too. --- src/index.ts | 6 +++- src/lib/keyStores/PrivateKeyStore.spec.ts | 7 ++-- src/lib/keyStores/PrivateKeyStore.ts | 40 ++++++++++++++++------- src/lib/keyStores/testMocks.ts | 20 ++++++++---- src/lib/nodes/Node.spec.ts | 4 +-- 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/index.ts b/src/index.ts index da36c126f..cca3e5aaa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,11 @@ export { CertificateError } from './lib/crypto/x509/CertificateError'; export * from './lib/pki/issuance'; // Key stores -export { PrivateKeyStore, SessionPrivateKeyData } from './lib/keyStores/PrivateKeyStore'; +export { + PrivateKeyStore, + SessionPrivateKeyData, + UnboundSessionPrivateKeyData, +} from './lib/keyStores/PrivateKeyStore'; export * from './lib/keyStores/PublicKeyStore'; export { KeyStoreSet } from './lib/keyStores/KeyStoreSet'; export { CertificateStore } from './lib/keyStores/CertificateStore'; diff --git a/src/lib/keyStores/PrivateKeyStore.spec.ts b/src/lib/keyStores/PrivateKeyStore.spec.ts index 2e9847cdb..fd4762041 100644 --- a/src/lib/keyStores/PrivateKeyStore.spec.ts +++ b/src/lib/keyStores/PrivateKeyStore.spec.ts @@ -133,10 +133,11 @@ describe('Session keys', () => { NODE_ID, ); - const publicKey = await MOCK_STORE.retrieveUnboundSessionPublicKey(NODE_ID); + const data = await MOCK_STORE.retrieveUnboundSessionPublicKey(NODE_ID); - expect(publicKey!.type).toBe('public'); - expect(await derSerializePublicKey(publicKey!!)).toEqual( + expect(data?.keyId).toMatchObject(sessionKeyPair.sessionKey.keyId); + expect(data!.publicKey.type).toBe('public'); + expect(await derSerializePublicKey(data!.publicKey)).toEqual( await derSerializePublicKey(sessionKeyPair.sessionKey.publicKey), ); }); diff --git a/src/lib/keyStores/PrivateKeyStore.ts b/src/lib/keyStores/PrivateKeyStore.ts index ab7d87e38..0f28644da 100644 --- a/src/lib/keyStores/PrivateKeyStore.ts +++ b/src/lib/keyStores/PrivateKeyStore.ts @@ -11,6 +11,7 @@ import { derSerializePublicKey, } from '../crypto/keys/serialisation'; import { getIdFromIdentityKey } from '../crypto/keys/digest'; +import { SessionKey } from '../SessionKey'; /** * Data for a private key of a session key pair. @@ -21,6 +22,14 @@ export interface SessionPrivateKeyData { readonly peerId?: string; } +/** + * Data for an unbound, private key of a session key pair. + */ +export interface UnboundSessionPrivateKeyData { + readonly keyId: string; + readonly keySerialized: Buffer; +} + export abstract class PrivateKeyStore { //region Identity keys @@ -54,7 +63,7 @@ export abstract class PrivateKeyStore { nodeId: string, peerId?: string, ): Promise { - const keyIdString = keyId.toString('hex'); + const keyIdString = encodeKeyId(keyId); const privateKeyDer = await derSerializePrivateKey(privateKey); try { await this.saveSessionKeySerialized(keyIdString, privateKeyDer, nodeId, peerId); @@ -70,26 +79,27 @@ export abstract class PrivateKeyStore { * @throws PrivateKeyStoreError when the look-up could not be done * @return The public key if it exists or `null` otherwise */ - public async retrieveUnboundSessionPublicKey(nodeId: string): Promise { - const privateKeySerialised = await this.retrieveLatestUnboundSessionKeySerialised(nodeId); + public async retrieveUnboundSessionPublicKey(nodeId: string): Promise { + const data = await this.retrieveLatestUnboundSessionKeyData(nodeId); - if (!privateKeySerialised) { + if (!data) { return null; } - const privateKey = await derDeserializeECDHPrivateKey(privateKeySerialised); + const privateKey = await derDeserializeECDHPrivateKey(data.keySerialized); const publicKeySerialised = await derSerializePublicKey(privateKey); - return derDeserializeECDHPublicKey(publicKeySerialised); + const publicKey = await derDeserializeECDHPublicKey(publicKeySerialised); + return { keyId: decodeKeyId(data.keyId), publicKey }; } /** - * Return the data of the latest, unbound session key for the specified `nodeId`. + * Return the latest unbound session key for the specified `nodeId`. * * @param nodeId The id of the node that owns the key */ - protected abstract retrieveLatestUnboundSessionKeySerialised( + protected abstract retrieveLatestUnboundSessionKeyData( nodeId: string, - ): Promise; + ): Promise; /** * Retrieve private session key, regardless of whether it's an initial key or not. @@ -107,7 +117,7 @@ export abstract class PrivateKeyStore { peerId: string, ): Promise { const keyData = await this.retrieveSessionKeyDataOrThrowError(keyId, nodeId); - const keyIdHex = keyId.toString('hex'); + const keyIdHex = encodeKeyId(keyId); if (keyData.peerId && peerId !== keyData.peerId) { throw new UnknownKeyError( @@ -135,7 +145,7 @@ export abstract class PrivateKeyStore { keyId: Buffer, nodeId: string, ): Promise { - const keyIdHex = keyId.toString('hex'); + const keyIdHex = encodeKeyId(keyId); let keyData: SessionPrivateKeyData | null; try { keyData = await this.retrieveSessionKeyData(keyIdHex); @@ -157,3 +167,11 @@ export abstract class PrivateKeyStore { return generateRSAKeyPair(keyOptions); } } + +function encodeKeyId(keyId: Buffer): string { + return keyId.toString('hex'); +} + +function decodeKeyId(keyIdHex: string): Buffer { + return Buffer.from(keyIdHex, 'hex'); +} diff --git a/src/lib/keyStores/testMocks.ts b/src/lib/keyStores/testMocks.ts index 5d156a44e..880773974 100644 --- a/src/lib/keyStores/testMocks.ts +++ b/src/lib/keyStores/testMocks.ts @@ -3,7 +3,11 @@ import { CertificationPath } from '../pki/CertificationPath'; import { CertificateStore } from './CertificateStore'; import { KeyStoreSet } from './KeyStoreSet'; -import { PrivateKeyStore, SessionPrivateKeyData } from './PrivateKeyStore'; +import { + PrivateKeyStore, + SessionPrivateKeyData, + UnboundSessionPrivateKeyData, +} from './PrivateKeyStore'; import { PublicKeyStore, SessionPublicKeyData } from './PublicKeyStore'; export class MockPrivateKeyStore extends PrivateKeyStore { @@ -55,13 +59,17 @@ export class MockPrivateKeyStore extends PrivateKeyStore { return this.sessionKeys[keyId] ?? null; } - protected override async retrieveLatestUnboundSessionKeySerialised( + protected override async retrieveLatestUnboundSessionKeyData( nodeId: string, - ): Promise { - const keyData = Object.values(this.sessionKeys).find( - (data) => data.nodeId === nodeId && !data.peerId, + ): Promise { + const entry = Object.entries(this.sessionKeys).find( + ([_, d]) => d.nodeId === nodeId && !d.peerId, ); - return keyData?.keySerialized ?? null; + if (!entry) { + return null; + } + const [keyId, data] = entry; + return { keyId, keySerialized: data.keySerialized }; } } diff --git a/src/lib/nodes/Node.spec.ts b/src/lib/nodes/Node.spec.ts index d8e72df33..7b26a65d3 100644 --- a/src/lib/nodes/Node.spec.ts +++ b/src/lib/nodes/Node.spec.ts @@ -111,8 +111,8 @@ describe('generateSessionKey', () => { const sessionKey = await node.generateSessionKey(); - const publicKey = await KEY_STORES.privateKeyStore.retrieveUnboundSessionPublicKey(node.id); - await expect(derSerializePublicKey(publicKey!!)).resolves.toEqual( + const key = await KEY_STORES.privateKeyStore.retrieveUnboundSessionPublicKey(node.id); + await expect(derSerializePublicKey(key!.publicKey)).resolves.toEqual( await derSerializePublicKey(sessionKey.publicKey), ); });