Skip to content

Commit

Permalink
fix: Include key id in retrieveUnboundSessionPublicKey() result (#294)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarea authored Jan 17, 2024
1 parent 1a81899 commit 0f49b4a
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 24 deletions.
11 changes: 6 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"dependencies": {
"@google-cloud/kms": "^4.0.1",
"@relaycorp/relaynet-core": ">=1.88.0, < 2.0",
"@relaycorp/relaynet-core": ">=1.88.1, < 2.0",
"@typegoose/typegoose": "< 12.0",
"axios": "^1.6.5",
"env-var": "^7.4.1",
Expand All @@ -48,7 +48,7 @@
"webcrypto-core": "< 2.0"
},
"peerDependencies": {
"@relaycorp/relaynet-core": ">=1.88.0, < 2.0",
"@relaycorp/relaynet-core": ">=1.88.1, < 2.0",
"@typegoose/typegoose": "< 12.0",
"mongoose": "< 8.0",
"webcrypto-core": "< 2.0"
Expand Down
3 changes: 2 additions & 1 deletion src/lib/gcp/GCPPrivateKeyStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,8 @@ describe('Session keys', () => {

const key = await store.retrieveUnboundSessionPublicKey(nodeId);

await expect(derSerializePublicKey(key!)).resolves.toStrictEqual(
expect(key?.keyId).toMatchObject(latestKey.sessionKey.keyId);
await expect(derSerializePublicKey(key!.publicKey)).resolves.toStrictEqual(
await derSerializePublicKey(latestKey.sessionKey.publicKey),
);
});
Expand Down
14 changes: 10 additions & 4 deletions src/lib/gcp/GCPPrivateKeyStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IdentityKeyPair,
RSAKeyGenOptions,
SessionPrivateKeyData,
UnboundSessionPrivateKeyData,
} from '@relaycorp/relaynet-core';
import { getModelForClass, ReturnModelType } from '@typegoose/typegoose';
import { calculate as calculateCRC32C } from 'fast-crc32c';
Expand Down Expand Up @@ -128,20 +129,25 @@ export class GCPPrivateKeyStore extends CloudPrivateKeystore {
return { keySerialized, peerId, nodeId };
}

protected override async retrieveLatestUnboundSessionKeySerialised(
protected override async retrieveLatestUnboundSessionKeyData(
nodeId: string,
): Promise<Buffer | null> {
): Promise<UnboundSessionPrivateKeyData | null> {
const document = await this.sessionKeyModel
.findOne(
{ nodeId, peerId: undefined },
{ privateKeyCiphertext: 1 },
{ privateKeyCiphertext: 1, keyId: 1 },
{ sort: { creationDate: -1 } },
)
.exec();
if (!document) {
return null;
}
return this.decryptSessionPrivateKey(document.privateKeyCiphertext, nodeId);

const keySerialized = await this.decryptSessionPrivateKey(
document.privateKeyCiphertext,
nodeId,
);
return { keyId: document.keyId, keySerialized };
}

//region Identity key utilities
Expand Down
7 changes: 5 additions & 2 deletions src/lib/vault/VaultPrivateKeyStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ describe('VaultPrivateKeyStore', () => {
});
expect(mockAxiosClient.post).toHaveBeenCalledWith(`/s-node-${nodeId}`, {
data: {
keyId: sessionKeyIdHex,
privateKey: base64Encode(await derSerializePrivateKey(sessionKeyPair.privateKey)),
},
});
Expand Down Expand Up @@ -416,17 +417,19 @@ describe('VaultPrivateKeyStore', () => {
mockAxiosClient.get.mockResolvedValue(
makeVaultGETResponse(
{
keyId: sessionKeyPair.sessionKey.keyId.toString('hex'),
privateKey: base64Encode(await derSerializePrivateKey(sessionKeyPair.privateKey)),
},
200,
),
);
const store = new VaultPrivateKeyStore(stubVaultUrl, stubVaultToken, stubKvPath);

const publicKey = await store.retrieveUnboundSessionPublicKey(nodeId);
const key = await store.retrieveUnboundSessionPublicKey(nodeId);

expect(mockAxiosClient.get).toHaveBeenCalledWith(`/s-node-${nodeId}`);
await expect(derSerializePublicKey(publicKey!)).resolves.toEqual(
expect(key?.keyId).toMatchObject(sessionKeyPair.sessionKey.keyId);
await expect(derSerializePublicKey(key!.publicKey)).resolves.toEqual(
await derSerializePublicKey(sessionKeyPair.sessionKey.publicKey),
);
});
Expand Down
23 changes: 16 additions & 7 deletions src/lib/vault/VaultPrivateKeyStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
derDeserializeRSAPrivateKey,
derSerializePrivateKey,
SessionPrivateKeyData,
UnboundSessionPrivateKeyData,
} from '@relaycorp/relaynet-core';
import axios, { AxiosInstance } from 'axios';
import { Agent as HttpAgent } from 'http';
Expand All @@ -15,6 +16,8 @@ import {
KeyDataEncoded,
SessionKeyDataDecoded,
SessionKeyDataEncoded,
UnboundSessionKeyDataDecoded,
UnboundSessionKeyDataEncoded,
} from './keyData';
import { VaultStoreError } from './VaultStoreError';
import { CloudPrivateKeystore } from '../CloudPrivateKeystore';
Expand Down Expand Up @@ -69,7 +72,7 @@ export class VaultPrivateKeyStore extends CloudPrivateKeystore {

if (!peerId) {
// The key is unbound, so upsert it as the unbound key for the node
await this.saveData(keySerialized, `s-node-${nodeId}`);
await this.saveData(keySerialized, `s-node-${nodeId}`, { keyId });
}
}

Expand All @@ -88,10 +91,10 @@ export class VaultPrivateKeyStore extends CloudPrivateKeystore {
private async saveData(
keySerialized: Buffer,
keyId: string,
metadata: Omit<KeyDataEncoded, 'privateKey'> | null = null,
metadata: Omit<KeyDataEncoded, 'privateKey'> = {},
): Promise<void> {
const keyBase64 = base64Encode(keySerialized);
const data: KeyDataEncoded = { privateKey: keyBase64, ...(metadata ?? {}) };
const data: KeyDataEncoded = { privateKey: keyBase64, ...metadata };
const response = await this.axiosClient.post(`/${keyId}`, { data });
if (response.status !== 200 && response.status !== 204) {
throw new VaultStoreError(
Expand All @@ -101,11 +104,16 @@ export class VaultPrivateKeyStore extends CloudPrivateKeystore {
}
}

protected override async retrieveLatestUnboundSessionKeySerialised(
protected override async retrieveLatestUnboundSessionKeyData(
nodeId: string,
): Promise<Buffer | null> {
const data = await this.retrieveData(`s-node-${nodeId}`);
return data?.privateKey ?? null;
): Promise<UnboundSessionPrivateKeyData | null> {
const data = (await this.retrieveData(
`s-node-${nodeId}`,
)) as UnboundSessionKeyDataDecoded | null;
if (!data) {
return null;
}
return { keyId: data.keyId, keySerialized: data.privateKey };
}

private async retrieveData(keyId: string): Promise<KeyDataDecoded | null> {
Expand All @@ -123,6 +131,7 @@ export class VaultPrivateKeyStore extends CloudPrivateKeystore {

const vaultData = response.data.data.data as KeyDataEncoded;
return {
keyId: (vaultData as UnboundSessionKeyDataEncoded).keyId,
peerId: (vaultData as SessionKeyDataEncoded).peerId,
nodeId: (vaultData as SessionKeyDataEncoded).nodeId,
privateKey: base64Decode(vaultData.privateKey),
Expand Down
15 changes: 12 additions & 3 deletions src/lib/vault/keyData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ export interface SessionKeyDataEncoded extends BaseKeyDataEncoded {
readonly peerId?: string;
}

export type KeyDataEncoded = IdentityKeyDataEncoded | SessionKeyDataEncoded;
export interface UnboundSessionKeyDataEncoded extends BaseKeyDataEncoded {
readonly keyId: string;
}

export type KeyDataEncoded =
| IdentityKeyDataEncoded
| SessionKeyDataEncoded
| UnboundSessionKeyDataEncoded;

interface BaseKeyDataDecoded {
readonly privateKey: Buffer;
Expand All @@ -22,9 +29,11 @@ export interface SessionKeyDataDecoded extends BaseKeyDataDecoded {
readonly nodeId: string;
}

interface InitialSessionKeyDataDecoded extends BaseKeyDataDecoded {}
export interface UnboundSessionKeyDataDecoded extends BaseKeyDataDecoded {
readonly keyId: string;
}

export type KeyDataDecoded =
| IdentityKeyDataDecoded
| SessionKeyDataDecoded
| InitialSessionKeyDataDecoded;
| UnboundSessionKeyDataDecoded;

0 comments on commit 0f49b4a

Please sign in to comment.