Skip to content

Commit 67f18f9

Browse files
fix(sdk-lib-mpc): remove date override from encrypt/decrypt, keep +24h tolerance on verify
The original date:null→date:now+24h change made encrypt/decrypt stricter (requiring keys valid for 24 more hours) instead of more tolerant. Flipping to now-24h breaks self-signature validation on fresh keys. The correct fix: omit date from encrypt/decrypt (default key expiry checks) and keep now+24h only on verify (tolerates future-dated OVC signatures). Tests now validate clock-skew tolerance on verify and key expiry on encrypt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 31e3b76 commit 67f18f9

File tree

2 files changed

+63
-21
lines changed

2 files changed

+63
-21
lines changed

modules/sdk-lib-mpc/src/tss/ecdsa-dkls/commsLayer.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,16 @@ import * as pgp from 'openpgp';
1616
* OpenPGP date check is a defense-in-depth measure rather than the primary
1717
* replay mitigation.
1818
*
19-
* A 24-hour window preserves compatibility with OVC flows while still
20-
* rejecting operations against keys that have been expired for more than a day.
19+
* OpenPGP's `date` parameter shifts the reference time for ALL temporal
20+
* checks simultaneously (key expiry, self-signature validity, signature
21+
* freshness). This means a single shifted date cannot independently relax
22+
* key-expiry checks without breaking self-signature validation on fresh keys.
23+
*
24+
* Therefore:
25+
* - encrypt/decrypt omit `date` (use default = current time) for normal key
26+
* expiry checking and self-signature validation.
27+
* - verify uses `now + tolerance` so that signatures from OVC devices whose
28+
* clocks are up to 24 hours ahead are not rejected as "from the future".
2129
*/
2230
export const SIGNATURE_DATE_TOLERANCE_MS = 24 * 60 * 60 * 1000;
2331

@@ -69,7 +77,6 @@ export async function encryptAndDetachSignData(
6977
showVersion: false,
7078
showComment: false,
7179
},
72-
date: new Date(Date.now() + SIGNATURE_DATE_TOLERANCE_MS),
7380
});
7481
const signature = await pgp.sign({
7582
message,
@@ -110,7 +117,6 @@ export async function decryptAndVerifySignedData(
110117
showComment: false,
111118
},
112119
format: 'binary',
113-
date: new Date(Date.now() + SIGNATURE_DATE_TOLERANCE_MS),
114120
});
115121
const verificationResult = await pgp.verify({
116122
message: await pgp.createMessage({ binary: decryptedMessage.data }),

modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsComms.ts

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
decryptAndVerifySignedData,
33
encryptAndDetachSignData,
4+
verifySignedData,
45
SIGNATURE_DATE_TOLERANCE_MS,
56
} from '../../../../src/tss/ecdsa-dkls/commsLayer';
67
import * as openpgp from 'openpgp';
@@ -100,32 +101,67 @@ describe('DKLS Communication Layer', function () {
100101
});
101102

102103
describe('signature date tolerance', function () {
103-
// Key that expires in 1 second — well within the 24-hour tolerance window,
104-
// so encrypt at Date.now() + 24h will see an expired key.
105-
let shortLivedRecipientKey: { publicKey: string; privateKey: string };
106-
107-
before(async function () {
108-
shortLivedRecipientKey = await openpgp.generateKey({
109-
userIDs: [{ name: 'shortlived', email: 'shortlived@username.com' }],
110-
curve: 'secp256k1',
111-
keyExpirationTime: 1,
112-
});
104+
it('should confirm tolerance constant is 24 hours', function () {
105+
SIGNATURE_DATE_TOLERANCE_MS.should.equal(24 * 60 * 60 * 1000);
113106
});
114107

115108
it('should reject encryption to an expired key', async function () {
116-
const text = 'ffffffff';
109+
// Key created 48h ago with a 23h lifetime → expired 25h ago.
110+
const expiredKey = await openpgp.generateKey({
111+
userIDs: [{ name: 'expired', email: 'expired@username.com' }],
112+
curve: 'secp256k1',
113+
date: new Date(Date.now() - 48 * 60 * 60 * 1000),
114+
keyExpirationTime: 23 * 3600,
115+
});
117116

118-
// Encryption checks key validity at Date.now() + 24h, which is past
119-
// the 1-second key expiry, so this should be rejected.
120117
await encryptAndDetachSignData(
121-
Buffer.from(text, 'base64'),
122-
shortLivedRecipientKey.publicKey,
118+
Buffer.from('ffffffff', 'base64'),
119+
expiredKey.publicKey,
123120
senderKey.privateKey
124121
).should.be.rejectedWith('Error encrypting message: Primary key is expired');
125122
});
126123

127-
it('should confirm tolerance constant is 24 hours', function () {
128-
SIGNATURE_DATE_TOLERANCE_MS.should.equal(24 * 60 * 60 * 1000);
124+
it('should accept verification of a signature created by a device whose clock is ahead', async function () {
125+
// Simulate a signature created by a device whose clock is 12 hours
126+
// ahead. The verify tolerance (now + 24h) should accept it.
127+
const futureDate = new Date(Date.now() + 12 * 60 * 60 * 1000);
128+
const message = await openpgp.createMessage({ binary: Buffer.from('ffffffff', 'base64') });
129+
const privateKey = await openpgp.readPrivateKey({ armoredKey: senderKey.privateKey });
130+
const signature = await openpgp.sign({
131+
message,
132+
signingKeys: privateKey,
133+
format: 'armored',
134+
detached: true,
135+
date: futureDate,
136+
config: { rejectCurves: new Set(), showVersion: false, showComment: false },
137+
});
138+
139+
const result = await verifySignedData(
140+
{ message: Buffer.from('ffffffff', 'base64').toString('base64'), signature },
141+
senderKey.publicKey
142+
);
143+
result.should.equal(true);
144+
});
145+
146+
it('should reject verification of a signature created more than 24h in the future', async function () {
147+
// Simulate a signature from a device whose clock is 25 hours ahead.
148+
const farFutureDate = new Date(Date.now() + 25 * 60 * 60 * 1000);
149+
const message = await openpgp.createMessage({ binary: Buffer.from('ffffffff', 'base64') });
150+
const privateKey = await openpgp.readPrivateKey({ armoredKey: senderKey.privateKey });
151+
const signature = await openpgp.sign({
152+
message,
153+
signingKeys: privateKey,
154+
format: 'armored',
155+
detached: true,
156+
date: farFutureDate,
157+
config: { rejectCurves: new Set(), showVersion: false, showComment: false },
158+
});
159+
160+
const result = await verifySignedData(
161+
{ message: Buffer.from('ffffffff', 'base64').toString('base64'), signature },
162+
senderKey.publicKey
163+
);
164+
result.should.equal(false);
129165
});
130166
});
131167
});

0 commit comments

Comments
 (0)