Repository navigation
Conversation
The same visible password can arrive as different code points: "é" is U+00E9 (NFC) on most keyboards but "e"+U+0301 (NFD) from some macOS inputs, IMEs and pasted text. TextEncoder turns those into different UTF-8 bytes, so PBKDF2 derived a different key and a file encrypted on one machine failed on another with a misleading "wrong password". Encryption now NFC-normalizes the password in all three implementations (src/lib/crypto.ts, the app's crypto core, the recovery tool). Decryption tries NFC first and, only when the typed string was not already NFC, retries with the exact typed bytes — which is how every pre-existing ciphertext was keyed. Nothing that decrypted before stops decrypting; the only cost is one extra PBKDF2 run for a wrong non-NFC password. Regression suite: - new fixture "nfd-password": produced by the unmodified v3.0.11 crypto.ts with an NFD password, kept as a JSON \u0301 escape so no editor can recompose it; proves the exact-bytes fallback. - fixtures may now carry their own "password". - NFC<->NFD cross checks for crypto.ts, the app core and the recovery file (6 checks that fail against the previous code, pass now). site/index.html, site/ittybitz-recovery.html and SHA256SUMS.txt are the rebuilt artifacts with re-pinned CSP hashes (npm run build).
site/ittybitz-recovery.html carried a hand-maintained copy of the decrypt core. The regression suite caught drift, but every change to the container format or key derivation had to be made twice (the NFC normalization was the latest). Now there is one source: scripts/build/crypto-core.js format + key derivation + decrypt scripts/build/crypto-encrypt.js encrypt, appended for the app only build-app.mjs assembles site/index.html as before (the two files above inside <script id="ittybitz-crypto-core">, so the regression suite's extraction is unchanged) and now also assembles the recovery tool from recovery-head.html + crypto-core.js (<script id="ittybitz-decrypt-core">) + recovery-app.js. It refuses to build if crypto-core.js defines ittybitzEncrypt, so the recovery tool stays decrypt-only by construction. What is tested is still the SHIPPED file: the suite extracts the block from the HTML and replays every fixture through it. 101/101. A second build is byte-identical. README and the suite's comments updated.
|
Also available merged with every other review PR, in dependency order and verified together, as #57 — merge that or the individual PRs, not both. |
|
Thank you, Dean, for all the work and support you've put into IttyBitz. This review was thorough and genuinely useful. I'm closing this one without merging. I'm deeply committed to password security, and I'm keeping the core cryptography exactly as it is. I'm also determined to keep the file as small and tight as possible. I love the changes you've contributed, and several of them are already live in v3.0.12: several files at once, the key-file fingerprint, the printable emergency card, refusing empty key files, keyboard tabs and the accessibility fixes, and the CI reproducibility check. They're credited in the release notes. Please keep the suggestions coming! |
Stacked on #43 — this branch includes that PR's commit (the NFC normalization is what made the duplication bite). The diff specific to this PR is the last commit. Merge #43 first, or I can rebase onto
main.Why
site/ittybitz-recovery.htmlcarried a hand-maintained copy of the decrypt core. The regression suite caught drift — that's what it's for — but every change to the container format or key derivation still had to be made twice, and #43 was exactly that: the same fix applied tocrypto-core.jsand to the recovery file by hand.What
One source for the core, two shipped files assembled from it:
build-app.mjsassemblessite/index.htmlas before —crypto-core.js+crypto-encrypt.jsinside<script id="ittybitz-crypto-core">, so the regression suite's extraction is unchanged — and now also assembles the recovery tool asrecovery-head.html+crypto-core.js(<script id="ittybitz-decrypt-core">) +recovery-app.js. It refuses to build ifcrypto-core.jsdefinesittybitzEncrypt, so the recovery tool stays decrypt-only by construction, not by discipline.Behavioural differences in the recovery tool's core, both for the better: it now validates the password like the app does (length, NUL), and its "newer version" message is the shared one. Its UI is untouched.
What is tested is still the shipped file: the suite extracts the block from the HTML and replays every fixture through it, so a build that assembled the wrong thing fails. With #45's reproducibility gate, CI will also fail if the committed recovery file is not what the build produces.
Verified
npm run buildtwice → byte-identical;npm run test:crypto→ 101/101 (all 33 fixtures through the assembled recovery core, both NFC spellings, the v3.0.11 NFD fixture).Recover/README.md's user-facing "no build step" claims remain true — nothing changes for someone opening the file.