Skip to content

Commit 665ddd1

Browse files
author
teycir
committed
fix(seal): improve retrieval security and locked seal handling
Ensure unlock time is checked before any other operations to prevent timing attacks. Decrypt sensitive data (keyB) only when a seal is genuinely unlocked. Prevent exposing iv and unlock message for locked seals by returning early. Remove client-side pulse token generation, as the server will now manage this.
1 parent 125cc82 commit 665ddd1

2 files changed

Lines changed: 31 additions & 13 deletions

File tree

app/page.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,9 @@ export default function HomePage() {
239239
if (sealType === 'timed') {
240240
unlockTime = new Date(unlockDate).getTime();
241241
} else {
242-
// Dead man's switch
242+
// Dead man's switch - server generates pulse token
243243
pulseDuration = pulseDays * 24 * 60 * 60 * 1000;
244244
unlockTime = Date.now() + pulseDuration;
245-
pulseToken = crypto.randomUUID();
246245
}
247246

248247
// Create FormData for API
@@ -254,7 +253,6 @@ export default function HomePage() {
254253
formData.append('isDMS', (sealType === 'deadman').toString());
255254

256255
if (turnstileToken) formData.append('cf-turnstile-response', turnstileToken);
257-
if (pulseToken) formData.append('pulseToken', pulseToken);
258256
if (pulseDuration) formData.append('pulseInterval', pulseDuration.toString());
259257

260258
// Send to API

lib/sealService.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,39 @@ export class SealService {
130130
throw new Error(ErrorCode.SEAL_NOT_FOUND);
131131
}
132132

133+
// Check time BEFORE any other operations to prevent timing attacks
133134
const now = Date.now();
134135
const isUnlocked = now >= seal.unlockTime;
135136

136-
let decryptedKeyB: string | undefined;
137-
if (isUnlocked) {
138-
decryptedKeyB = await decryptKeyBWithFallback(seal.keyB, sealId, [this.masterKey]);
139-
metrics.incrementSealUnlocked();
137+
// Add jitter for locked seals (already done in API route, but double-check here)
138+
if (!isUnlocked) {
139+
auditSealAccessed(sealId, ip, 'locked');
140+
this.auditLogger?.log({
141+
timestamp: now,
142+
eventType: AuditEventType.SEAL_ACCESS_DENIED,
143+
sealId,
144+
ip,
145+
metadata: { unlockTime: seal.unlockTime },
146+
});
147+
148+
return {
149+
id: sealId,
150+
unlockTime: seal.unlockTime,
151+
isDMS: seal.isDMS,
152+
status: 'locked',
153+
blobHash: seal.blobHash,
154+
accessCount: seal.accessCount,
155+
};
140156
}
141157

142-
auditSealAccessed(sealId, ip, isUnlocked ? 'unlocked' : 'locked');
158+
// Only decrypt if unlocked
159+
const decryptedKeyB = await decryptKeyBWithFallback(seal.keyB, sealId, [this.masterKey]);
160+
metrics.incrementSealUnlocked();
161+
162+
auditSealAccessed(sealId, ip, 'unlocked');
143163
this.auditLogger?.log({
144-
timestamp: Date.now(),
145-
eventType: isUnlocked ? AuditEventType.SEAL_UNLOCKED : AuditEventType.SEAL_ACCESS_DENIED,
164+
timestamp: now,
165+
eventType: AuditEventType.SEAL_UNLOCKED,
146166
sealId,
147167
ip,
148168
metadata: { unlockTime: seal.unlockTime },
@@ -152,11 +172,11 @@ export class SealService {
152172
id: sealId,
153173
unlockTime: seal.unlockTime,
154174
isDMS: seal.isDMS,
155-
status: isUnlocked ? 'unlocked' : 'locked',
175+
status: 'unlocked',
156176
keyB: decryptedKeyB,
157-
iv: isUnlocked ? seal.iv : undefined,
177+
iv: seal.iv,
158178
blobHash: seal.blobHash,
159-
unlockMessage: isUnlocked ? seal.unlockMessage : undefined,
179+
unlockMessage: seal.unlockMessage,
160180
accessCount: seal.accessCount,
161181
};
162182
}

0 commit comments

Comments
 (0)