Skip to content

Commit

Permalink
fix: Represent private key ids as buffer values
Browse files Browse the repository at this point in the history
Since serial numbers are now 64-bit values.

Needed for relaycorp/awala-gateway-internet#17
  • Loading branch information
gnarea committed Feb 25, 2020
1 parent ade4bc6 commit a1b5441
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 60 deletions.
82 changes: 42 additions & 40 deletions src/lib/privateKeyStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class StubPrivateKeyStore extends PrivateKeyStore {
super();
}

public registerStubKey(keyId: Buffer, privateKeyData: PrivateKeyData): void {
this.keys[keyId.toString('base64')] = privateKeyData;
}

protected async fetchKey(keyId: string): Promise<PrivateKeyData> {
if (keyId in this.keys) {
return this.keys[keyId];
Expand All @@ -27,7 +31,8 @@ class StubPrivateKeyStore extends PrivateKeyStore {
}

describe('PrivateKeyStore', () => {
const stubKeyId = '123';
const stubKeyId = Buffer.from([1, 3, 5, 7, 9]);
const stubKeyIdBase64 = stubKeyId.toString('base64');
let stubPrivateKey: CryptoKey;

const stubPrivateKeyDer = Buffer.from('DER-encoded private key');
Expand Down Expand Up @@ -63,7 +68,7 @@ describe('PrivateKeyStore', () => {

test('Existing key should be returned', async () => {
const store = new StubPrivateKeyStore();
store.keys[stubKeyId] = stubPrivateKeyData;
store.registerStubKey(stubKeyId, stubPrivateKeyData);

const privateKeyData = await store.fetchNodeKey(stubKeyId);

Expand All @@ -76,19 +81,18 @@ describe('PrivateKeyStore', () => {
});
});

test('Numeric key ids should be converted to string', async () => {
const numericKeyId = 12345;
test('Key ids should be base64-encoded', async () => {
const store = new StubPrivateKeyStore();
store.keys[numericKeyId.toString()] = stubPrivateKeyData;
store.registerStubKey(stubKeyId, stubPrivateKeyData);

const privateKeyData = await store.fetchNodeKey(numericKeyId);
const privateKeyData = await store.fetchNodeKey(stubKeyId);

expect(privateKeyData).toBe(stubPrivateKey);
});

test('Session keys should not be returned', async () => {
const store = new StubPrivateKeyStore();
store.keys[stubKeyId] = { ...stubPrivateKeyData, type: 'session' as const };
store.registerStubKey(stubKeyId, { ...stubPrivateKeyData, type: 'session' as const });

await expectPromiseToReject(
store.fetchNodeKey(stubKeyId),
Expand All @@ -101,7 +105,7 @@ describe('PrivateKeyStore', () => {

await expectPromiseToReject(
store.fetchNodeKey(stubKeyId),
new PrivateKeyStoreError(`Failed to retrieve key ${stubKeyId}: Unknown key ${stubKeyId}`),
new PrivateKeyStoreError(`Failed to retrieve key: Unknown key ${stubKeyIdBase64}`),
);
});
});
Expand All @@ -112,27 +116,27 @@ describe('PrivateKeyStore', () => {

await store.saveNodeKey(stubPrivateKey, stubKeyId);

expect(store.keys).toHaveProperty(stubKeyId);
expect(store.keys[stubKeyId]).toHaveProperty('keyDer', stubPrivateKeyDer);
expect(store.keys[stubKeyId]).toHaveProperty('type', 'node');
expect(store.keys[stubKeyId]).not.toHaveProperty('recipientPublicKeyDigest');
expect(store.keys).toHaveProperty(stubKeyIdBase64);
const keyDatum = store.keys[stubKeyIdBase64];
expect(keyDatum).toHaveProperty('keyDer', stubPrivateKeyDer);
expect(keyDatum).toHaveProperty('type', 'node');
expect(keyDatum).not.toHaveProperty('recipientPublicKeyDigest');
});

test('Numeric key ids should be converted to string', async () => {
test('Key ids should be base64-encoded', async () => {
const store = new StubPrivateKeyStore();
const numericKeyId = 12345;

await store.saveNodeKey(stubPrivateKey, numericKeyId);
await store.saveNodeKey(stubPrivateKey, stubKeyId);

expect(store.keys).toHaveProperty(numericKeyId.toString());
expect(store.keys).toHaveProperty(stubKeyIdBase64);
});

test('Errors should be wrapped', async () => {
const store = new StubPrivateKeyStore(true);

await expectPromiseToReject(
store.saveNodeKey(stubPrivateKey, stubKeyId),
new PrivateKeyStoreError(`Failed to save node key ${stubKeyId}: Denied`),
new PrivateKeyStoreError(`Failed to save key: Denied`),
);
});
});
Expand Down Expand Up @@ -173,7 +177,7 @@ describe('PrivateKeyStore', () => {
describe('fetchSessionKey', () => {
test('Existing, unbound key should be returned', async () => {
const store = new StubPrivateKeyStore();
store.keys[stubKeyId] = stubUnboundPrivateKeyData;
store.registerStubKey(stubKeyId, stubUnboundPrivateKeyData);

const privateKeyData = await store.fetchSessionKey(stubKeyId, stubRecipientPublicKey);

Expand All @@ -185,29 +189,28 @@ describe('PrivateKeyStore', () => {

test('Existing, bound key should be returned', async () => {
const store = new StubPrivateKeyStore();
store.keys[stubKeyId] = stubBoundPrivateKeyData;
store.registerStubKey(stubKeyId, stubBoundPrivateKeyData);

const privateKeyData = await store.fetchSessionKey(stubKeyId, stubRecipientPublicKey);

expect(privateKeyData).toBe(stubPrivateKey);
});

test('Numeric key ids should be converted to string', async () => {
const numericKeyId = 12345;
test('Key ids should be base64-encoded', async () => {
const store = new StubPrivateKeyStore();
store.keys[numericKeyId.toString()] = stubBoundPrivateKeyData;
store.registerStubKey(stubKeyId, stubBoundPrivateKeyData);

const privateKeyData = await store.fetchSessionKey(numericKeyId, stubRecipientPublicKey);
const privateKeyData = await store.fetchSessionKey(stubKeyId, stubRecipientPublicKey);

expect(privateKeyData).toBe(stubPrivateKey);
});

test('Keys bound to another recipient should not be returned', async () => {
const store = new StubPrivateKeyStore();
store.keys[stubKeyId] = {
store.registerStubKey(stubKeyId, {
...stubBoundPrivateKeyData,
recipientPublicKeyDigest: `not ${stubBoundPrivateKeyData.recipientPublicKeyDigest}`,
};
});

await expectPromiseToReject(
store.fetchSessionKey(stubKeyId, stubRecipientPublicKey),
Expand All @@ -217,7 +220,7 @@ describe('PrivateKeyStore', () => {

test('Node keys should not be returned', async () => {
const store = new StubPrivateKeyStore();
store.keys[stubKeyId] = { ...stubBoundPrivateKeyData, type: 'node' as const };
store.registerStubKey(stubKeyId, { ...stubBoundPrivateKeyData, type: 'node' as const });

await expectPromiseToReject(
store.fetchSessionKey(stubKeyId, stubRecipientPublicKey),
Expand All @@ -230,7 +233,7 @@ describe('PrivateKeyStore', () => {

await expectPromiseToReject(
store.fetchSessionKey(stubKeyId, stubRecipientPublicKey),
new PrivateKeyStoreError(`Failed to retrieve key ${stubKeyId}: Unknown key ${stubKeyId}`),
new PrivateKeyStoreError(`Failed to retrieve key: Unknown key ${stubKeyIdBase64}`),
);
});
});
Expand All @@ -241,41 +244,40 @@ describe('PrivateKeyStore', () => {

await store.saveSessionKey(stubPrivateKey, stubKeyId);

expect(store.keys).toHaveProperty(stubKeyId);
expect(store.keys[stubKeyId]).toHaveProperty('keyDer', stubPrivateKeyDer);
expect(store.keys[stubKeyId]).toHaveProperty('type', 'session');
expect(store.keys[stubKeyId]).toHaveProperty('recipientPublicKeyDigest', undefined);
expect(store.keys).toHaveProperty(stubKeyIdBase64);
expect(store.keys[stubKeyIdBase64]).toHaveProperty('keyDer', stubPrivateKeyDer);
expect(store.keys[stubKeyIdBase64]).toHaveProperty('type', 'session');
expect(store.keys[stubKeyIdBase64]).toHaveProperty('recipientPublicKeyDigest', undefined);
});

test('Bound key should be stored', async () => {
const store = new StubPrivateKeyStore();

await store.saveSessionKey(stubPrivateKey, stubKeyId, stubRecipientPublicKey);

expect(store.keys).toHaveProperty(stubKeyId);
expect(store.keys[stubKeyId]).toHaveProperty('keyDer', stubPrivateKeyDer);
expect(store.keys[stubKeyId]).toHaveProperty('type', 'session');
expect(store.keys[stubKeyId]).toHaveProperty(
expect(store.keys).toHaveProperty(stubKeyIdBase64);
expect(store.keys[stubKeyIdBase64]).toHaveProperty('keyDer', stubPrivateKeyDer);
expect(store.keys[stubKeyIdBase64]).toHaveProperty('type', 'session');
expect(store.keys[stubKeyIdBase64]).toHaveProperty(
'recipientPublicKeyDigest',
await keys.getPublicKeyDigestHex(stubRecipientPublicKey),
);
});

test('Numeric key ids should be converted to string', async () => {
test('Key ids should be base64-encoded', async () => {
const store = new StubPrivateKeyStore();
const numericKeyId = 12345;

await store.saveSessionKey(stubPrivateKey, numericKeyId, stubRecipientPublicKey);
await store.saveSessionKey(stubPrivateKey, stubKeyId, stubRecipientPublicKey);

expect(store.keys).toHaveProperty(numericKeyId.toString());
expect(store.keys).toHaveProperty(stubKeyIdBase64);
});

test('Errors should be wrapped', async () => {
const store = new StubPrivateKeyStore(true);

await expectPromiseToReject(
store.saveSessionKey(stubPrivateKey, stubKeyId),
new PrivateKeyStoreError(`Failed to save session key ${stubKeyId}: Denied`),
new PrivateKeyStoreError(`Failed to save key: Denied`),
);
});
});
Expand Down
39 changes: 19 additions & 20 deletions src/lib/privateKeyStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface PrivateKeyData {
export class PrivateKeyStoreError extends RelaynetError {}

export abstract class PrivateKeyStore {
public async fetchNodeKey(keyId: string | number): Promise<CryptoKey> {
public async fetchNodeKey(keyId: Buffer): Promise<CryptoKey> {
const keyData = await this.fetchKeyOrThrowError(keyId);

if (keyData.type !== 'node') {
Expand All @@ -29,10 +29,7 @@ export abstract class PrivateKeyStore {
});
}

public async fetchSessionKey(
keyId: string | number,
recipientPublicKey: CryptoKey,
): Promise<CryptoKey> {
public async fetchSessionKey(keyId: Buffer, recipientPublicKey: CryptoKey): Promise<CryptoKey> {
const keyData = await this.fetchKeyOrThrowError(keyId);

if (keyData.type !== 'session') {
Expand All @@ -49,22 +46,18 @@ export abstract class PrivateKeyStore {
return derDeserializeECDHPrivateKey(keyData.keyDer, 'P-256');
}

public async saveNodeKey(privateKey: CryptoKey, keyId: string | number): Promise<void> {
public async saveNodeKey(privateKey: CryptoKey, keyId: Buffer): Promise<void> {
const privateKeyDer = await derSerializePrivateKey(privateKey);
const privateKeyData: PrivateKeyData = {
keyDer: privateKeyDer,
type: 'node',
};
try {
await this.saveKey(privateKeyData, keyId.toString());
} catch (error) {
throw new PrivateKeyStoreError(error, `Failed to save node key ${keyId}`);
}
await this.saveKeyOrThrowError(privateKeyData, keyId);
}

public async saveSessionKey(
privateKey: CryptoKey,
keyId: string | number,
keyId: Buffer,
recipientPublicKey?: CryptoKey,
): Promise<void> {
const privateKeyData: PrivateKeyData = {
Expand All @@ -74,22 +67,28 @@ export abstract class PrivateKeyStore {
: undefined,
type: 'session',
};
try {
await this.saveKey(privateKeyData, keyId.toString());
} catch (error) {
throw new PrivateKeyStoreError(error, `Failed to save session key ${keyId}`);
}
await this.saveKeyOrThrowError(privateKeyData, keyId);
}

protected abstract async fetchKey(keyId: string): Promise<PrivateKeyData>;

protected abstract async saveKey(privateKeyData: PrivateKeyData, keyId: string): Promise<void>;

private async fetchKeyOrThrowError(keyId: string | number): Promise<PrivateKeyData> {
private async fetchKeyOrThrowError(keyId: Buffer): Promise<PrivateKeyData> {
const keyIdBase64 = keyId.toString('base64');
try {
return await this.fetchKey(keyIdBase64);
} catch (error) {
throw new PrivateKeyStoreError(error, `Failed to retrieve key`);
}
}

private async saveKeyOrThrowError(privateKeyData: PrivateKeyData, keyId: Buffer): Promise<void> {
const keyIdBase64 = keyId.toString('base64');
try {
return await this.fetchKey(keyId.toString());
await this.saveKey(privateKeyData, keyIdBase64);
} catch (error) {
throw new PrivateKeyStoreError(error, `Failed to retrieve key ${keyId}`);
throw new PrivateKeyStoreError(error, `Failed to save key`);
}
}
}

0 comments on commit a1b5441

Please sign in to comment.