Skip to content

Commit

Permalink
feat: Implement Parcel messages (#35)
Browse files Browse the repository at this point in the history
* Remove CMS encryption handling from RAMF serialization

* Reimplement RAMF serialization/deserialization as standalone functions

* Implement Parcel class

* Fix typo
  • Loading branch information
gnarea authored Jan 3, 2020
1 parent 758167b commit 23868d7
Show file tree
Hide file tree
Showing 10 changed files with 711 additions and 563 deletions.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ export { SignatureOptions } from './lib/crypto_wrappers/cms/signedData';

// RAMF
export { default as Message } from './lib/ramf/Message';
export { MessageSerializer } from './lib/ramf/MessageSerializer';
export { default as Payload } from './lib/ramf/Payload';
export { default as ServiceMessage } from './lib/ramf/ServiceMessage';
export { default as RAMFError } from './lib/ramf/RAMFError';
export { default as RAMFSyntaxError } from './lib/ramf/RAMFSyntaxError';
export { default as RAMFValidationError } from './lib/ramf/RAMFValidationError';
export { default as Parcel } from './lib/Parcel';

//endregion
71 changes: 71 additions & 0 deletions src/lib/Parcel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* tslint:disable:no-let */
import bufferToArray from 'buffer-to-arraybuffer';

import { generateStubCert, getMockContext } from './_test_utils';
import { generateRSAKeyPair } from './crypto_wrappers/keyGenerators';
import Parcel from './Parcel';
import * as serialization from './ramf/serialization';

afterAll(() => {
jest.restoreAllMocks();
});

describe('Parcel', () => {
describe('serialize', () => {
let parcel: Parcel;
let senderPrivateKey: CryptoKey;
beforeAll(async () => {
const senderKeyPair = await generateRSAKeyPair();
const senderCertificate = await generateStubCert({
issuerPrivateKey: senderKeyPair.privateKey,
});
senderPrivateKey = senderKeyPair.privateKey;
parcel = new Parcel('address', senderCertificate, bufferToArray(Buffer.from('hi')));
});

const expectedSerialization = bufferToArray(Buffer.from('serialized'));
const serializeSpy = jest.spyOn(serialization, 'serialize');
beforeAll(() => {
serializeSpy.mockResolvedValueOnce(expectedSerialization);
});
afterEach(() => {
serializeSpy.mockReset();
});

test('Result should be RAMF serialization', async () => {
const messageSerialized = await parcel.serialize(senderPrivateKey);

expect(serializeSpy).toBeCalledTimes(1);
expect(messageSerialized).toBe(expectedSerialization);
});

test('Concrete message type should be 0x50', async () => {
await parcel.serialize(senderPrivateKey);

const serializeCallArs = getMockContext(serialization.serialize).calls[0];
expect(serializeCallArs[1]).toEqual(0x50);
});

test('Concrete message version should be 0x0', async () => {
await parcel.serialize(senderPrivateKey);

const serializeCallArs = getMockContext(serialization.serialize).calls[0];
expect(serializeCallArs[2]).toEqual(0);
});

test('Message should be signed with private key specified', async () => {
await parcel.serialize(senderPrivateKey);

const serializeCallArs = getMockContext(serialization.serialize).calls[0];
expect(serializeCallArs[3]).toEqual(senderPrivateKey);
});

test('Signature options should be honored', async () => {
const signatureOptions = { hashingAlgorithmName: 'SHA-384' };
await parcel.serialize(senderPrivateKey, signatureOptions);

const serializeCallArs = getMockContext(serialization.serialize).calls[0];
expect(serializeCallArs[4]).toEqual(signatureOptions);
});
});
});
21 changes: 21 additions & 0 deletions src/lib/Parcel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SignatureOptions } from './crypto_wrappers/cms/signedData';
import Message from './ramf/Message';
import * as serialization from './ramf/serialization';

const concreteMessageTypeOctet = 0x50;
const concreteMessageVersionOctet = 0;

export default class Parcel extends Message {
public async serialize(
senderPrivateKey: CryptoKey,
signatureOptions?: SignatureOptions,
): Promise<ArrayBuffer> {
return serialization.serialize(
this,
concreteMessageTypeOctet,
concreteMessageVersionOctet,
senderPrivateKey,
signatureOptions,
);
}
}
14 changes: 0 additions & 14 deletions src/lib/ramf/Message.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,6 @@ describe('Message', () => {
});
});

describe('Payload', () => {
test('Payload should be imported when present', async () => {
const message = new StubMessage(recipientAddress, senderCertificate, payload);

expect(message.payloadPlaintext).toBe(payload);
});

test('Payload import should be skipped when it is absent', async () => {
const message = new StubMessage(recipientAddress, senderCertificate);

expect(message.payloadPlaintext).toBe(undefined);
});
});

describe('Sender certificate chain', () => {
test('Sender certificate chain should only contain sender certificate by default', () => {
const message = new StubMessage(recipientAddress, senderCertificate, payload);
Expand Down
20 changes: 11 additions & 9 deletions src/lib/ramf/Message.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import uuid4 from 'uuid4';

import { SignatureOptions } from '../..';
import Certificate from '../crypto_wrappers/x509/Certificate';

const DEFAULT_TTL_SECONDS = 5 * 60; // 5 minutes
Expand All @@ -23,25 +24,26 @@ export default abstract class Message {
constructor(
readonly recipientAddress: string,
readonly senderCertificate: Certificate,
payloadPlaintext?: ArrayBuffer,
readonly payloadSerialized: ArrayBuffer,
options: Partial<MessageOptions> = {},
) {
this.id = options.id || uuid4();
this.date = options.date || new Date();
this.ttl = options.ttl !== undefined ? options.ttl : DEFAULT_TTL_SECONDS;

//region Payload
if (payloadPlaintext) {
this.importPayload(payloadPlaintext);
}
//endregion

//region Sender certificate (chain)
const initialChain = options.senderCertificateChain || new Set([]);
this.senderCertificateChain = new Set([...initialChain, senderCertificate]);
//endregion
}

public abstract exportPayload(): ArrayBuffer;
protected abstract importPayload(payloadPlaintext: ArrayBuffer): void;
// TODO:
// public abstract unwrapPayload(privateKey: CryptoKey): PayloadSpecialization;

// This method would be concrete if TS allowed us to store the message type and version as
// properties
public abstract async serialize(
senderPrivateKey: CryptoKey,
signatureOptions?: SignatureOptions,
): Promise<ArrayBuffer>;
}
Loading

0 comments on commit 23868d7

Please sign in to comment.