Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(PrivateKeyStore): Make retrieveUnboundSessionPublicKey output SessionKey #642

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
7 changes: 4 additions & 3 deletions src/lib/keyStores/PrivateKeyStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);
});
Expand Down
40 changes: 29 additions & 11 deletions src/lib/keyStores/PrivateKeyStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -54,7 +63,7 @@ export abstract class PrivateKeyStore {
nodeId: string,
peerId?: string,
): Promise<void> {
const keyIdString = keyId.toString('hex');
const keyIdString = encodeKeyId(keyId);
const privateKeyDer = await derSerializePrivateKey(privateKey);
try {
await this.saveSessionKeySerialized(keyIdString, privateKeyDer, nodeId, peerId);
Expand All @@ -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<CryptoKey | null> {
const privateKeySerialised = await this.retrieveLatestUnboundSessionKeySerialised(nodeId);
public async retrieveUnboundSessionPublicKey(nodeId: string): Promise<SessionKey | null> {
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<Buffer | null>;
): Promise<UnboundSessionPrivateKeyData | null>;

/**
* Retrieve private session key, regardless of whether it's an initial key or not.
Expand All @@ -107,7 +117,7 @@ export abstract class PrivateKeyStore {
peerId: string,
): Promise<CryptoKey> {
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(
Expand Down Expand Up @@ -135,7 +145,7 @@ export abstract class PrivateKeyStore {
keyId: Buffer,
nodeId: string,
): Promise<SessionPrivateKeyData> {
const keyIdHex = keyId.toString('hex');
const keyIdHex = encodeKeyId(keyId);
let keyData: SessionPrivateKeyData | null;
try {
keyData = await this.retrieveSessionKeyData(keyIdHex);
Expand All @@ -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');
}
20 changes: 14 additions & 6 deletions src/lib/keyStores/testMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Buffer | null> {
const keyData = Object.values(this.sessionKeys).find(
(data) => data.nodeId === nodeId && !data.peerId,
): Promise<UnboundSessionPrivateKeyData | null> {
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 };
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib/nodes/Node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);
});
Expand Down