Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ebc6d8d2a |
@@ -206,6 +206,21 @@ testing. Users concerned about quantum attacks on the seed entropy itself should
|
||||
bigger risk to seed phrases is classical (theft, phishing), not quantum. Note that BIP39's PBKDF2
|
||||
iteration count is low; the seed phrase should be treated as a high-value secret and stored offline.
|
||||
|
||||
### Secret Material Lifetime and Zeroization (G56-10)
|
||||
|
||||
The web app derives the BIP39 seed, secp256k1 key, and five PQ secret keys in browser memory.
|
||||
On sign-out, the app **zeroizes all `Uint8Array` secret buffers** (fills with zeros before
|
||||
nulling references), **clears the DOM** (seed display, seed input, entropy hint), and **clears
|
||||
the clipboard** (in case the user copied the seed phrase).
|
||||
|
||||
**Important limitation:** JavaScript garbage collection does **not** guarantee erasure. The
|
||||
browser runtime may copy buffers internally, and zeroizing a `Uint8Array` only overwrites the
|
||||
JS-visible buffer — the engine may retain copies in its own memory management. Browser
|
||||
extensions, devtools memory inspectors, and page snapshots may still recover secrets during
|
||||
the session. The real fix is moving key derivation and signing into a hardware or native signer
|
||||
boundary (see G56-06 and G56-08). The zeroization implemented here is a best-effort reduction
|
||||
of the exposure window, not a guarantee.
|
||||
|
||||
---
|
||||
|
||||
## Component 2: Multi-Scheme Cross-Signed Key-Link Events
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|---|---:|---:|---:|---:|
|
||||
| Critical | 2 | 2 | 0 | 0 |
|
||||
| High | 6 | 6 | 0 | 0 |
|
||||
| Medium | 8 | 6 | 0 | 1 |
|
||||
| Medium | 8 | 7 | 0 | 0 |
|
||||
| Low / Info | 4 | 0 | 0 | 3 |
|
||||
| **Total** | **20** | **14** | **0** | **4** |
|
||||
| **Total** | **20** | **15** | **0** | **3** |
|
||||
|
||||
## Findings status
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
| G56-07 | High | Verifier throws on malformed relay/paste input | ✅ Fixed | v0.0.12, v0.0.19 | v0.0.12 wrapped base64/hex in try/catch + verifyNIPQRContent fail closed + verify.html call-site wrapping. v0.0.19 added structural validation + try/catch to `verifyNostrEvent()` (returns false on malformed input), wrapped `parseOtsFile()` body in try/catch (returns null), added `MAX_OTS_PROOF_SIZE` (1 MiB) and `MAX_EVENT_JSON_SIZE` (64 KiB) size guards, 23 fuzz/property tests (random JSON, random bytes, truncation, oversized inputs, wrong-typed objects) |
|
||||
| G56-08 | High | Browser origin security insufficient for seed handling | ✅ Fixed | v0.0.23 | Code fixes: extracted all inline JS to external files (`index-app.mjs`, `verify-app.mjs`, `version-display.js`), removed `'unsafe-inline'` from `script-src` in CSP, added `frame-ancestors 'none'` + `<meta name="referrer" content="no-referrer">`, added SRI hashes (`integrity`/`crossorigin`) to nostr-login-lite scripts. Server headers applied to nginx `conf.d/default.conf` (scoped to `/post-quantum/` location only): HSTS, `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, `Permissions-Policy`, COOP, CORP. Verified headers present on `/post-quantum/` and absent on other paths. Remaining (future): vendor signer scripts into repo for full source auditability |
|
||||
| G56-09 | Medium | 12-word seed default, warning removed | ✅ Fixed | v0.0.17 | Defaulted `generateSeedPhrase()` and `generateSeedPhraseWithEntropy()` to 24 words (256-bit); added 12/24-word selector UI; restored entropy-strength warning; removed "quantum-safe root of trust" / "Make your Nostr identity quantum-resistant" claims from index.html, README.md, nip_proposal.md; 4 new tests |
|
||||
| G56-10 | Medium | Secret material not zeroized, retained in globals/DOM/clipboard | ❌ Not started | — | Minimize secret lifetime, overwrite typed arrays |
|
||||
| G56-10 | Medium | Secret material not zeroized, retained in globals/DOM/clipboard | ✅ Fixed | v0.0.26 | Sign-out now zeroizes all `Uint8Array` secret buffers (BIP39 seed, secp private key, 5 PQ secret keys) via `fill(0)` before nulling references; clears DOM (seed display, seed input, entropy hint); clears clipboard. Documented the JS zeroization limitation in README (garbage collection doesn't guarantee erasure; real fix is hardware/native signer boundary) |
|
||||
| G56-11 | Medium | Publication advances despite zero relay acceptance | ✅ Fixed | v0.0.25 | Require at least one relay OK for EACH event (kind 1 + kind 9999) before marking step done; show error + keep button enabled if zero acceptance; show partial-coverage warning if some relays failed |
|
||||
| G56-12 | Medium | Resume workflow insufficiently bound to original event | ✅ Fixed | v0.0.14, v0.0.25 | v0.0.14: fetch all candidates + match by event ID. v0.0.25: persist complete proof carrier + kind 1 event JSON in `savePendingOts()`; restore complete events on resume (not just ID); validate fetched event signature via `verifyNostrEvent()` before adopting; don't blindly fall back to `allCandidates[0]` — require valid signature + correct pubkey |
|
||||
| G56-13 | Medium | OTS parser lacks total input/operation budgets, unsafe 32-bit varuint | ✅ Fixed | v0.0.25 | Replaced 32-bit bitwise `readVaruint()` with safe arithmetic (`Math.pow(2, shift)` + max value/length checks); added `OTS_MAX_OPERATIONS` (10000) and `OTS_MAX_BRANCHES` (1000) budgets tracked via a budget object passed through `_parseTimestamp`/`_processTimestampEntry`; `MAX_OTS_PROOF_SIZE` (1 MiB) already added in G56-07 |
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "post_quantum_nostr",
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.26",
|
||||
"description": "A migration strategy for bringing post-quantum security to Nostr without breaking the social graph, without requiring consensus on a single post-quantum algorithm, and without forcing existing users to abandon their identities.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -952,6 +952,25 @@
|
||||
if (window.NOSTR_LOGIN_LITE && window.NOSTR_LOGIN_LITE.logout) {
|
||||
try { await window.NOSTR_LOGIN_LITE.logout(); } catch (e) { console.warn('[post-quantum] NLL logout failed:', e); }
|
||||
}
|
||||
// G56-10: Zeroize secret typed arrays before nulling references.
|
||||
// Note: JavaScript garbage collection does not guarantee erasure —
|
||||
// this is a best-effort reduction of the exposure window. See README
|
||||
// for the full limitation discussion.
|
||||
function zeroize(arr) {
|
||||
if (arr instanceof Uint8Array) {
|
||||
arr.fill(0);
|
||||
}
|
||||
}
|
||||
if (pqSeed) zeroize(pqSeed);
|
||||
if (pqSecpKeys) {
|
||||
if (pqSecpKeys.privateKey) zeroize(pqSecpKeys.privateKey);
|
||||
if (pqSecpKeys.publicKey) zeroize(pqSecpKeys.publicKey);
|
||||
}
|
||||
if (pqKeys) {
|
||||
for (const alg of ['mlDsa44', 'mlDsa65', 'slhDsa', 'falcon512', 'mlKem']) {
|
||||
if (pqKeys[alg] && pqKeys[alg].secretKey) zeroize(pqKeys[alg].secretKey);
|
||||
}
|
||||
}
|
||||
// Clear local state
|
||||
currentPubkey = null;
|
||||
pqMnemonic = null;
|
||||
@@ -964,6 +983,17 @@
|
||||
pendingOtsBytes = null;
|
||||
currentBlockHeight = 0;
|
||||
pqRelays = DEFAULT_RELAYS.slice();
|
||||
|
||||
// G56-10: Clear DOM elements that displayed secret material
|
||||
const seedDisplay = document.getElementById('pqSeedDisplay');
|
||||
if (seedDisplay) seedDisplay.innerHTML = '';
|
||||
const seedInput = document.getElementById('pqSeedInput');
|
||||
if (seedInput) seedInput.value = '';
|
||||
const entropyHint = document.getElementById('pqEntropyHint');
|
||||
if (entropyHint) entropyHint.textContent = 'Optional: click here and type random characters, move your mouse to add entropy...';
|
||||
|
||||
// G56-10: Clear the clipboard (in case the user copied the seed phrase)
|
||||
try { navigator.clipboard.writeText(''); } catch (e) { /* clipboard may not be available */ }
|
||||
// Clear auth/session storage, then restore only the OTS workflow record.
|
||||
try {
|
||||
localStorage.clear();
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.0.25",
|
||||
"VERSION_NUMBER": "0.0.25",
|
||||
"BUILD_DATE": "2026-07-24T18:43:29.373Z"
|
||||
"VERSION": "v0.0.26",
|
||||
"VERSION_NUMBER": "0.0.26",
|
||||
"BUILD_DATE": "2026-07-25T11:30:14.286Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user