Skip to content

Commit

Permalink
feat: Extend pong message expiry date to 14 days (#338)
Browse files Browse the repository at this point in the history
* refactor

* complete
  • Loading branch information
gnarea authored Jul 5, 2021
1 parent 9aea3be commit b704c7a
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 20 deletions.
21 changes: 21 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@relaycorp/relaynet-pohttp": "^1.7.4",
"buffer-to-arraybuffer": "0.0.6",
"bull": "^3.22.8",
"date-fns": "^2.22.1",
"env-var": "^7.0.1",
"fastify": "^2.15.1",
"fastify-sentry": "^1.4.0",
Expand Down
27 changes: 20 additions & 7 deletions src/app/background_queue/processor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
PDACertPath,
} from '@relaycorp/relaynet-testing';
import { Job } from 'bull';
import { addDays, subMinutes, subSeconds } from 'date-fns';

import {
expectBuffersToEqual,
Expand Down Expand Up @@ -209,6 +210,24 @@ describe('PingProcessor', () => {
expect(certificatePath.pdaGrantee.isEqual(deliveredParcel.senderCertificate)).toBeTrue();
});

test('Parcel creation date should be 5 minutes in the past to tolerate clock drift', () => {
const cutoffDate = subMinutes(new Date(), 5);
expect(deliveredParcel.creationDate).toBeBefore(cutoffDate);
expect(deliveredParcel.creationDate).toBeAfter(subSeconds(cutoffDate, 5));
});

test('Parcel expiry date should be 14 days in the future', () => {
const cutoffDate = addDays(new Date(), 14);
expect(deliveredParcel.expiryDate).toBeBefore(cutoffDate);
expect(deliveredParcel.expiryDate).toBeAfter(subSeconds(cutoffDate, 5));
});

test('Parcel payload should be encrypted with recipient certificate', () => {
expect(SessionlessEnvelopedData.encrypt).toBeCalledTimes(1);
const encryptCall = getMockContext(SessionlessEnvelopedData.encrypt).calls[0];
expect(encryptCall[1].getCommonName()).toEqual(pingSenderCertificate.getCommonName());
});

test('Service message type should be application/vnd.awala.ping-v1.pong', () => {
expect(ServiceMessage.prototype.serialize).toBeCalledTimes(1);
const serviceMessage = getMockContext(ServiceMessage.prototype.serialize).instances[0];
Expand All @@ -221,12 +240,6 @@ describe('PingProcessor', () => {
expect(serviceMessage.content.toString()).toEqual(pingId);
});

test('Parcel payload should be encrypted with recipient certificate', () => {
expect(SessionlessEnvelopedData.encrypt).toBeCalledTimes(1);
const encryptCall = getMockContext(SessionlessEnvelopedData.encrypt).calls[0];
expect(encryptCall[1].getCommonName()).toEqual(pingSenderCertificate.getCommonName());
});

test('Parcel should be delivered to the specified gateway', () => {
const deliverParcelCall = getMockContext(pohttp.deliverParcel).calls[0];
expect(deliverParcelCall[0]).toEqual(stubGatewayAddress);
Expand Down Expand Up @@ -307,7 +320,7 @@ describe('PingProcessor', () => {
await processor.deliverPongForPing(stubJob);

expect(encryptSpy).toBeCalledTimes(1);
expect(encryptSpy.mock.results[0].value).toResolve();
await expect(encryptSpy.mock.results[0].value).toResolve();

// Check plaintext
const encryptCallArgs = encryptSpy.mock.calls[0];
Expand Down
47 changes: 34 additions & 13 deletions src/app/background_queue/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
ServiceMessage,
SessionEnvelopedData,
SessionlessEnvelopedData,
UnboundKeyPair,
} from '@relaycorp/relaynet-core';
import { deliverParcel, PoHTTPInvalidParcelError } from '@relaycorp/relaynet-pohttp';
import bufferToArray from 'buffer-to-arraybuffer';
import { Job } from 'bull';
import { addDays, differenceInSeconds, subMinutes } from 'date-fns';
import pino = require('pino');

import { deserializePing, Ping } from '../pingSerialization';
Expand All @@ -36,22 +38,14 @@ export class PingProcessor {
// Service message was invalid; errors were already logged.
return;
}

const ping = unwrappingResult.ping;
const pongParcelPayload = await this.generatePongParcelPayload(
ping.id,
unwrappingResult.originatorKey ?? pingParcel.senderCertificate,
const pongParcelSerialized = await this.makePongParcel(
unwrappingResult.ping,
pingParcel.senderCertificate,
keyPair,
unwrappingResult.originatorKey,
);
const pongParcel = new Parcel(
pingParcel.senderCertificate.getCommonName(),
ping.pda,
pongParcelPayload,
{ senderCaCertificateChain: ping.pdaChain },
);
const parcelSerialized = await pongParcel.serialize(keyPair.privateKey);
try {
await deliverParcel(job.data.gatewayAddress, parcelSerialized);
await deliverParcel(job.data.gatewayAddress, pongParcelSerialized);
} catch (err) {
if (err instanceof PoHTTPInvalidParcelError) {
logger.info({ err }, 'Discarding pong delivery because server refused parcel');
Expand Down Expand Up @@ -126,4 +120,31 @@ export class PingProcessor {
}
return Buffer.from(pongParcelPayload.serialize());
}

private async makePongParcel(
ping: Ping,
recipientCertificate: Certificate,
keyPair: UnboundKeyPair,
originatorKey?: OriginatorSessionKey,
): Promise<ArrayBuffer> {
const pongParcelPayload = await this.generatePongParcelPayload(
ping.id,
originatorKey ?? recipientCertificate,
recipientCertificate,
);
const now = new Date();
const expiryDate = addDays(now, 14);
const creationDate = subMinutes(now, 5);
const pongParcel = new Parcel(
recipientCertificate.getCommonName(),
ping.pda,
pongParcelPayload,
{
creationDate,
senderCaCertificateChain: ping.pdaChain,
ttl: differenceInSeconds(expiryDate, creationDate),
},
);
return pongParcel.serialize(keyPair.privateKey);
}
}

0 comments on commit b704c7a

Please sign in to comment.