summaryrefslogtreecommitdiff
path: root/docs/SECURITY.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/SECURITY.md')
-rw-r--r--docs/SECURITY.md7
1 files changed, 4 insertions, 3 deletions
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
index c68cdc1..3091fb4 100644
--- a/docs/SECURITY.md
+++ b/docs/SECURITY.md
@@ -23,7 +23,8 @@ Everything lives in the app's private `Application.Storage`:
| `imp`, `imp.<n>` | dictionary, bytes | Waiting multi-part import: `imp` = `{"n": chunks, "t": saved at}`, `imp.<n>` = chunks of one SealedBox (device key, aad `OTPW-IMP-1`) of the text | `ImportBuffer` |
| `res` | dictionary | Latest settings results (`l` lines, `u` unseen) | `Results` |
| `fh` | array | Keyed hashes of the last 32 applied settings forms (below) | `Forms` |
-| `q` | array | Phone changes to locked entries waiting for the PIN: one SealedBox per change (device key, aad `OTPW-Q-1`), at most 16 | `ChangeQueue` |
+| `q` | array | Phone changes to locked entries waiting for the PIN, at most 16. Each item is `[action, box]` (plus the material number for a replacement): a SealedBox of the change (device key, aad `OTPW-Q-2` and the action code), so the action can be counted without decrypting and a changed code fails to open. Items written by 0.9.0 are bare SealedBoxes (aad `OTPW-Q-1`) and are still read | `ChangeQueue` |
+| `qm.<n>` | bytes | The validated new secret or contents of a waiting replacement: a SealedBox (device key, aad `OTPW-QM-1.<n>.<id>`), deleted with its item | `ChangeQueue` |
| `cs` | string | Keyed hash of what the About fields and the glance code were last written from (IDs, pending IDs, "Encrypt names", the glance option, waiting changes, version); absent while names still need converting | `AboutScan` |
| `st` | array | Version and day of the last passed self-test | `AppState` |
@@ -132,7 +133,7 @@ PIN key = PBKDF2-HMAC-SHA256(PIN as ASCII digits, salt, n, 32 bytes)
## Pending locked entries
-A locked entry that arrives while the session is locked, or before a PIN exists, is sealed with the device key under `p.<id>` and listed in `m.pending`. It doesn't appear in the list. After the next unlock, `promotePending()` re-seals it with the master key, writes `e.<id>`, updates the index and only then deletes `p.<id>` (`vaultQueuesLockedEntriesWhileLocked`).
+A locked entry that arrives while the session is locked, or before a PIN exists, is sealed with the device key under `p.<id>` and listed in `m.pending`. It doesn't appear in the list. After the next unlock, the PIN screen's `ChangeQueue.LockJob` promotes them one at a time (`Vault.promoteNext`): it re-seals each with the master key, writes `e.<id>`, updates the index and only then deletes `p.<id>` (`vaultQueuesLockedEntriesWhileLocked`). Then it applies the waiting "Require PIN" changes, one per step, before the app continues.
## Write order and crashes
@@ -191,7 +192,7 @@ Kfp = HKDF-SHA256(device key, info = "OTPW fp v1")
Kfp is the fingerprint key above. Without the device key a stored hash reveals nothing. With it, an attacker can only test guesses of a whole form, as with fingerprints. The fields go to the native HMAC one by one; the short ones (every secret field) are zeroed afterwards. An import form's text is too long to zero byte by byte within the watchdog limit; it stays in memory as the property's string anyway.
- **Secrets in the forms.** Add OTP's secret, Add code's contents and Manage entry's new secret are cleared first when the watch processes a form, also after a problem (the other fields stay so you can correct them). Messages never quote a secret.
-- **Changes from the phone to locked entries** (README 7.9). Edit, replace, set counter, "Don't require PIN", the favourite mark and delete of a locked entry are not applied when they arrive. Each one is sealed with the device key under `q`, which is no weaker than the settings text it came in. They are applied only after someone enters the PIN on the watch under "Phone changes", which always asks for the PIN, even while the session is unlocked. Moving an entry is applied at once: the order isn't protected, and positions are absolute. Changes to unlocked entries apply at once and are listed on the results screen (`manageQueuesLockedEntryChanges`, `manageQueueIsSealedAndLimited`).
+- **Changes from the phone to locked entries** (README 7.9). Edit, replace, set counter, "Don't require PIN", the favourite mark and delete of a locked entry are not applied when they arrive. Each one is sealed with the device key under `q`, which is no weaker than the settings text it came in. They are applied only after someone enters the PIN on the watch under "Phone changes", which always asks for the PIN, even while the session is unlocked. A replacement is validated when it arrives (Base32 decoded, or the code contents checked), and its material is sealed apart under `qm.<n>`, bound to its item number and entry ID. "Confirm all" and "Reject all" act only on the changes the menu showed (identified by their SealedBox IVs); any that arrive while the PIN is typed keep waiting (`queueStoresMaterialApart`, `queueChecksReplaceFirst`, `queueActsOnWhatWasShown`). Moving an entry is applied at once: the order isn't protected, and positions are absolute. Changes to unlocked entries apply at once and are listed on the results screen (`manageQueuesLockedEntryChanges`, `manageQueueIsSealedAndLimited`).
- **The About catalog** sends entry IDs and names to the phone's settings, where your entries came from in the first place. With "Encrypt names" on it sends only IDs and types.
- **"Encrypt names"** puts names and accounts into the encrypted part of each record. Turning it on or off re-seals every entry the watch can reach: unlocked ones at once, locked ones the next time the PIN is entered (`settingsAppliesEncryptNames`). The barcode cache is kept.