Skip to content

Commit

Permalink
Merge pull request #196
Browse files Browse the repository at this point in the history
Fix forwarding helpers: accept `date` option and use serverTime instead of local time by default
  • Loading branch information
larabr authored Mar 12, 2024
2 parents 80e765a + b2bff02 commit bc24425
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 20 deletions.
21 changes: 12 additions & 9 deletions lib/key/forwarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BigIntegerInterface from '@openpgp/noble-hashes/esm/biginteger/interface'
import NativeBigInteger from '@openpgp/noble-hashes/esm/biginteger/native.interface';
import { KDFParams, PrivateKey, UserID, SecretSubkeyPacket, SecretKeyPacket, MaybeArray, Subkey, config as defaultConfig, SubkeyOptions, enums } from '../openpgp';
import { generateKey, reformatKey } from './utils';
import { serverTime } from '../serverTime';

let initializedBigInteger = false;
const getBigInteger = async () => {
Expand Down Expand Up @@ -36,11 +37,11 @@ export async function computeProxyParameter(
return proxyParameter;
}

async function getEncryptionKeysForForwarding(forwarderKey: PrivateKey) {
async function getEncryptionKeysForForwarding(forwarderKey: PrivateKey, date: Date) {
const curveName = 'curve25519';
const forwarderEncryptionKeys = await forwarderKey.getDecryptionKeys(
undefined,
undefined,
date,
undefined,
{ ...defaultConfig, allowInsecureDecryptionWithSigningKeys: false }
) as any as (PrivateKey | Subkey)[]; // TODO wrong TS defintion for `getDecryptionKeys`
Expand All @@ -62,13 +63,13 @@ async function getEncryptionKeysForForwarding(forwarderKey: PrivateKey) {
/**
* Whether the given key can be used as input to `generateForwardingMaterial` to setup forwarding.
*/
export async function doesKeySupportForwarding(forwarderKey: PrivateKey): Promise<boolean> {
export async function doesKeySupportForwarding(forwarderKey: PrivateKey, date: Date = serverTime()): Promise<boolean> {
if (!forwarderKey.isDecrypted()) {
return false;
}

try {
const keys = await getEncryptionKeysForForwarding(forwarderKey);
const keys = await getEncryptionKeysForForwarding(forwarderKey, date);
return keys.length > 0;
} catch {
return false;
Expand All @@ -79,8 +80,8 @@ export async function doesKeySupportForwarding(forwarderKey: PrivateKey): Promis
* Whether all the encryption-capable (sub)keys are setup as forwarding keys.
* This function also supports encrypted private keys.
*/
export const isForwardingKey = (keyToCheck: PrivateKey) => (
getEncryptionKeysForForwarding(keyToCheck)
export const isForwardingKey = (keyToCheck: PrivateKey, date: Date = serverTime()) => (
getEncryptionKeysForForwarding(keyToCheck, date)
// @ts-ignore missing `bindingSignatures` definition
.then((keys) => keys.every((key) => key.bindingSignatures[0].keyFlags & enums.keyFlags.forwardedCommunication))
.catch(() => false)
Expand All @@ -97,19 +98,21 @@ export const isForwardingKey = (keyToCheck: PrivateKey) => (
*/
export async function generateForwardingMaterial(
forwarderKey: PrivateKey,
userIDsForForwardeeKey: MaybeArray<UserID>
userIDsForForwardeeKey: MaybeArray<UserID>,
date: Date = serverTime()
) {
if (!forwarderKey.isDecrypted()) {
throw new Error('Forwarder key must be decrypted');
}

const curveName = 'curve25519';
const forwarderEncryptionKeys = await getEncryptionKeysForForwarding(forwarderKey);
const forwarderEncryptionKeys = await getEncryptionKeysForForwarding(forwarderKey, date);
const { privateKey: forwardeeKeyToSetup } = await generateKey({ // TODO handle v6 keys
type: 'ecc',
userIDs: userIDsForForwardeeKey,
subkeys: new Array<SubkeyOptions>(forwarderEncryptionKeys.length).fill({ curve: curveName, forwarding: true }),
format: 'object'
format: 'object',
date
});

// Setup forwardee encryption subkeys and generated corresponding proxy params
Expand Down
4 changes: 2 additions & 2 deletions test/key/forwarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ec as EllipticCurve } from 'elliptic';
import BN from 'bn.js';

import { decryptKey, enums, KeyID, PacketList } from '../../lib/openpgp';
import { generateKey, generateForwardingMaterial, doesKeySupportForwarding, encryptMessage, decryptMessage, readMessage, readKey, readPrivateKey } from '../../lib';
import { generateKey, generateForwardingMaterial, doesKeySupportForwarding, encryptMessage, decryptMessage, readMessage, readKey, readPrivateKey, serverTime } from '../../lib';
import { computeProxyParameter, isForwardingKey } from '../../lib/key/forwarding';
import { hexStringToArray, concatArrays, arrayToHexString } from '../../lib/utils';

Expand Down Expand Up @@ -98,7 +98,7 @@ yGZuVVMAK/ypFfebDf4D/rlEw3cysv213m8aoK8nAUO8xQX3XQq3Sg+EGm0BNV8E
const charlieKey = await readKey({ armoredKey: forwardeeKey.armor() }); // ensure key is correctly serialized and parsed

// Check subkey differences
const bobSubkey = await bobKey.getEncryptionKey();
const bobSubkey = await bobKey.getEncryptionKey(undefined, serverTime());
const charlieSubkey = charlieKey.subkeys[0];

expect(charlieSubkey.bindingSignatures[0].keyFlags![0]).to.equal(enums.keyFlags.forwardedCommunication);
Expand Down
11 changes: 6 additions & 5 deletions test/key/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
getMatchingKey,
generateSessionKeyForAlgorithm,
generateSessionKey,
getSHA256Fingerprints
getSHA256Fingerprints,
serverTime
} from '../../lib';

describe('key utils', () => {
Expand Down Expand Up @@ -39,7 +40,7 @@ describe('key utils', () => {
userIDs: [{ name: 'name', email: '[email protected]' }],
format: 'object'
});
const now = new Date();
const now = serverTime();
expect(Math.abs(+privateKey.getCreationTime() - +now) < 24 * 3600).to.be.true;
});

Expand All @@ -48,7 +49,7 @@ describe('key utils', () => {
userIDs: [{ name: 'name', email: '[email protected]' }],
format: 'object'
});
const { selfCertification } = await privateKey.getPrimaryUser();
const { selfCertification } = await privateKey.getPrimaryUser(serverTime());
expect(selfCertification.preferredSymmetricAlgorithms).to.include(enums.symmetric.aes256);
expect(selfCertification.preferredHashAlgorithms).to.include(enums.hash.sha256);
expect(selfCertification.preferredCompressionAlgorithms).to.include(enums.compression.zlib);
Expand All @@ -70,7 +71,7 @@ describe('key utils', () => {
});

it('isExpiredKey - it can correctly detect an expired key', async () => {
const now = new Date();
const now = serverTime();
// key expires in one second
const { privateKey: expiringKey } = await generateKey({
userIDs: [{ name: 'name', email: '[email protected]' }],
Expand All @@ -93,7 +94,7 @@ describe('key utils', () => {

it('isRevokedKey - it can correctly detect a revoked key', async () => {
const past = new Date(0);
const now = new Date();
const now = serverTime();

const { privateKey: key, revocationCertificate } = await generateKey({
userIDs: [{ name: 'name', email: '[email protected]' }],
Expand Down
4 changes: 2 additions & 2 deletions test/message/context.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { verifyMessage, signMessage, generateKey, readSignature, readMessage, decryptMessage, encryptMessage, readKey, ContextError } from '../../lib';
import { verifyMessage, signMessage, generateKey, readSignature, readMessage, decryptMessage, encryptMessage, readKey, ContextError, serverTime } from '../../lib';
import { VERIFICATION_STATUS } from '../../lib/constants';

// verification without passing context should fail
Expand Down Expand Up @@ -233,7 +233,7 @@ describe('context', () => {
});

it('does not verify a message without context based on cutoff date (`expectFrom`)', async () => {
const now = new Date();
const now = serverTime();
const nextHour = new Date(+now + (3600 * 1000));
const { privateKey, publicKey } = await generateKey({
userIDs: [{ name: 'name', email: '[email protected]' }],
Expand Down
9 changes: 7 additions & 2 deletions test/setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { use as chaiUse } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';

import { init } from '../lib';
import { init, updateServerTime } from '../lib';

chaiUse(chaiAsPromised);
before(init);
before(() => {
// set server time in the future to spot functions that use local time unexpectedly
const HOUR = 3600 * 1000;
updateServerTime(new Date(Date.now() + HOUR));
init();
});

0 comments on commit bc24425

Please sign in to comment.