Skip to content

Commit

Permalink
feat(Node): Implement unwrapping of message payload (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarea authored Dec 15, 2020
1 parent fb08b2a commit 8990d2b
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 19 deletions.
115 changes: 115 additions & 0 deletions src/lib/nodes/BaseNode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { arrayBufferFrom, expectBuffersToEqual } from '../_test_utils';
import {
SessionEnvelopedData,
SessionlessEnvelopedData,
} from '../crypto_wrappers/cms/envelopedData';
import {
derSerializePublicKey,
generateECDHKeyPair,
generateRSAKeyPair,
} from '../crypto_wrappers/keys';
import Certificate from '../crypto_wrappers/x509/Certificate';
import { MockPrivateKeyStore, MockPublicKeyStore } from '../keyStores/testMocks';
import { issueGatewayCertificate, issueInitialDHKeyCertificate } from '../pki';
import { StubMessage, StubPayload } from '../ramf/_test_utils';
import { BaseNode } from './BaseNode';

const TOMORROW = new Date();
TOMORROW.setDate(TOMORROW.getDate() + 1);
TOMORROW.setMilliseconds(0);

let senderCertificate: Certificate;
beforeAll(async () => {
const senderKeyPair = await generateRSAKeyPair();
senderCertificate = await issueGatewayCertificate({
issuerPrivateKey: senderKeyPair.privateKey,
subjectPublicKey: senderKeyPair.publicKey,
validityEndDate: TOMORROW,
});
});

let recipientCertificate: Certificate;
let recipientPrivateKey: CryptoKey;
let privateKeyStore: MockPrivateKeyStore;
beforeAll(async () => {
const recipientKeyPair = await generateRSAKeyPair();
recipientPrivateKey = recipientKeyPair.privateKey;

recipientCertificate = await issueGatewayCertificate({
issuerPrivateKey: recipientKeyPair.privateKey,
subjectPublicKey: recipientKeyPair.publicKey,
validityEndDate: TOMORROW,
});

privateKeyStore = new MockPrivateKeyStore();
await privateKeyStore.registerNodeKey(recipientKeyPair.privateKey, recipientCertificate);
});

describe('unwrapMessagePayload', () => {
const payloadPlaintextContent = arrayBufferFrom('content');

let publicKeyStore: MockPublicKeyStore;
beforeEach(() => {
publicKeyStore = new MockPublicKeyStore();
});

const RECIPIENT_ADDRESS = 'https://example.com';

test('Payload plaintext should be returned when using sessionless encryption', async () => {
const payload = await SessionlessEnvelopedData.encrypt(
payloadPlaintextContent,
recipientCertificate,
);
const message = new StubMessage(
RECIPIENT_ADDRESS,
senderCertificate,
Buffer.from(payload.serialize()),
);
const node = new StubNode(privateKeyStore, publicKeyStore);

const payloadPlaintext = await node.unwrapMessagePayload(message);

expectBuffersToEqual(payloadPlaintext.content, payloadPlaintextContent);
expect(publicKeyStore.keys).toBeEmpty();
});

test('Payload plaintext should be returned and session key stored when using a session', async () => {
const sessionKeyPair = await generateECDHKeyPair();
const initialSessionCertificate = await issueInitialDHKeyCertificate({
issuerCertificate: recipientCertificate,
issuerPrivateKey: recipientPrivateKey,
subjectPublicKey: sessionKeyPair.publicKey,
validityEndDate: TOMORROW,
});
await privateKeyStore.registerInitialSessionKey(
sessionKeyPair.privateKey,
initialSessionCertificate,
);
const encryptionResult = await SessionEnvelopedData.encrypt(
payloadPlaintextContent,
initialSessionCertificate,
);
const message = new StubMessage(
RECIPIENT_ADDRESS,
senderCertificate,
Buffer.from(encryptionResult.envelopedData.serialize()),
);
const node = new StubNode(privateKeyStore, publicKeyStore);

const payloadPlaintext = await node.unwrapMessagePayload(message);

expectBuffersToEqual(payloadPlaintext.content, payloadPlaintextContent);

const storedKey = publicKeyStore.keys[await senderCertificate.calculateSubjectPrivateAddress()];
expect(storedKey.publicKeyCreationTime).toEqual(message.creationDate);
expectBuffersToEqual(Buffer.from(encryptionResult.dhKeyId), storedKey.publicKeyId);
expectBuffersToEqual(
await derSerializePublicKey(
(await encryptionResult.envelopedData.getOriginatorKey()).publicKey,
),
storedKey.publicKeyDer,
);
});
});

class StubNode extends BaseNode<StubPayload> {}
35 changes: 35 additions & 0 deletions src/lib/nodes/BaseNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { PrivateKeyStore } from '../keyStores/privateKeyStore';
import { PublicKeyStore } from '../keyStores/publicKeyStore';
import PayloadPlaintext from '../messages/payloads/PayloadPlaintext';
import RAMFMessage from '../messages/RAMFMessage';
import { NodeOptions } from './NodeOptions';

export abstract class BaseNode<Payload extends PayloadPlaintext> {
constructor(
protected privateKeyStore: PrivateKeyStore,
protected publicKeyStore: PublicKeyStore,
protected cryptoOptions: Partial<NodeOptions> = {},
) {}

/**
* Decrypt and return the payload in the `message`.
*
* Also store the session key, if using the channel session protocol.
*
* @param message
*/
public async unwrapMessagePayload<P extends Payload>(message: RAMFMessage<P>): Promise<P> {
const unwrapResult = await message.unwrapPayload(this.privateKeyStore);

// If the sender uses channel session, store its public key for later use.
if (unwrapResult.senderSessionKey) {
await this.publicKeyStore.saveSessionKey(
unwrapResult.senderSessionKey,
message.senderCertificate,
message.creationDate,
);
}

return unwrapResult.payload;
}
}
7 changes: 7 additions & 0 deletions src/lib/nodes/NodeOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { EncryptionOptions } from '../crypto_wrappers/cms/envelopedData';
import { SignatureOptions } from '../crypto_wrappers/cms/SignatureOptions';

export interface NodeOptions {
readonly encryption: Partial<EncryptionOptions>;
readonly signature: Partial<SignatureOptions>;
}
17 changes: 0 additions & 17 deletions src/lib/nodes/baseNode.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/lib/nodes/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import {
} from '../crypto_wrappers/cms/envelopedData';
import Certificate from '../crypto_wrappers/x509/Certificate';
import Cargo from '../messages/Cargo';
import { CargoCollectionRequest } from '../messages/payloads/CargoCollectionRequest';
import CargoMessageSet, { MessageWithExpiryDate } from '../messages/payloads/CargoMessageSet';
import { BaseNode } from './baseNode';
import { BaseNode } from './BaseNode';

export type CargoMessageStream = AsyncIterable<{
readonly message: Buffer;
readonly expiryDate: Date;
}>;

export class Gateway extends BaseNode {
export class Gateway extends BaseNode<CargoMessageSet | CargoCollectionRequest> {
public async *generateCargoes(
messages: CargoMessageStream,
recipientCertificate: Certificate,
Expand Down

0 comments on commit 8990d2b

Please sign in to comment.