“What happens if our password manager is hacked?” is an easy question, but answering it is rather difficult – “getting hacked” can mean many things, and hiding behind “oh, we have encryption” is not the answer users deserve. This article is a rough overview over what attacks we considered, and how we tried to address them. In future, we plan to expand on them in a series of in-depth articles focusing on details (e.g., concrete database design).
(Mainly inspired by ‘On The Security of Password Manager Database Formats’, P. Gasti & K. Rasmussen.)
- Two adversaries are to be considered: Adv_r (read access, e.g. access to database dumps, cloud hosters, …), and Adv_rw (read and write access, i.e. active infiltration)
- Storage has to be considered as untrusted – especially with external deployments, we have no influence on physical security, or security of underlying storage systems (SANs, etc.)
Quite obviously, database dumps (or files, in the case of SQLite) by themselves should be as worthless as possible to attackers. More subtle are attacks by a writing adversary: Injection of malicious data must be impossible under all conditions (to avoid RCE etc. pp.), to ensure safety.
- As much data as possible must use authenticated encryption: Encryption to prevent data exfiltration, authentication to detect tampering.
- The remaining data must at least be authenticated to detect tampering.
- All of the above must work in a multi-user, multi-keyed environment.
Point 3 is the important one here: Authentication is trivial for single-tenant systems that don’t require key management, but here we need to authenticate keys, and the content signed by them.
As all de- and encryption happens client-side the usual problems apply: Side channel attacks must be impossible, buffer overflows and such must not leak sensitive data, data exfiltration of unencrypted material must be impossible, and of encrypted material at the very least infeasible. This includes private keys as well as managed passwords.
Possible adversaries here are the usual suspects:
- Adv_r and Adv_rw on the database
- Local disk adv_r and adv_rw
- Sidechannel observation (including, but not limited to, power supply fluctuations, power supply audible fluctuations, cache pressure, response timing etc. pp.)
- Network adversaries controlling ARP, DNS, NTP, HKP and HTTPS traffic for good measure
Assuming a working key management model and a fully authenticated database, adv_rw will have very little advantage over adv_r: They can enforce a denial of service, or launch a replay attack, by serving the client a known working, but outdated, database.
Preventing a denial of service attack is out of scope (and, arguably, an unlikely threat as it attracts significant attention without giving attackers insight into sensitive data – as we’re not planning any cloud-based centralized hosting the “DoS to blackmail a hoster into paying protection money” angle is unlikely to be relevant either); replay attacks are to be considered together with key management and authentication.
Sidechannel resistance is delegated to the underlying cryptographic schemes, which has been selected by this criterium (i.e., libsodium, always the original native implementation, never ports running inside garbage-collected VMs or worse).
Network integrity will be deferred to (optionally certificate pinned) TLS.
Client-side data protection apart from these issues is split into two parts:
- Private key storage
- Software integrity
Server-side software integrity is of a lesser concern, as the data served by it has to pass client-side verification.
Users will, over the life span of the application, be identified by multiple keys – because they have per-device keys, their otherwise global keys are upgraded to a higher key length, theft – or all of the above.
This means we must be able to deal with a varying array of keys that have to be trusted for different purposes:
- Keys authenticate each user against the server.
- Keys authenticate and encrypt all data exchanged with other users, including:
- Passwords and their metadata
- Structural information (groups)
- Metadata about other users, including their keys (renewal, revocation, etc.)
The last point introduces a chicken-egg problem: We need trusted keys to trust keys. The least hack-ish solution we came up with is having a root CA/key that is cached and signed by (one of) each local installation(‘s user keys). Switching root keys is an unsolved problem in various fields, but should happen rarely enough to allow leaving the process to be invasive and require out-of-band validation of the new keys.
This leaves the problem of structuring the key network we have: X.509-ish centralized PKI, GPG-ish web of trust, or something else entirely.
To recap, we need the following structure:
- Locally signed root key(s)
- Root-signed administrative keys (for structural data)
- Somehow signed user keys (for authentication and/or encryption)
However, we need limitations for different key types:
- Administrative keys should not be able to sign root keys (unresolvable circular dependency).
- Users should not be able to sign other users’ keys (impersonation), at most their own.
With these, we can rule out adapting GPG’s web of trust model, as it would require far too much tweaking to impose our key roles on its egalitarian trust architecture.
The signature of user keys will be the interesting point: Should users be allowed to sign their own keys or not? If all keys are signed by a central authority, we can implement a centralized CA (with e.g. one root certificate, kept permanently off-line and never needed in regular operations; one administrative CA, usually off-line as well, but required sporadically to delegate administrative powers; and one user CA used to sign each user key).
Whether we should is a different question – setting up the full required infrastructure including CRL/OCSP distribution is a considerable deployment complexity – but if users should be delegated the power to sign their own keys, we will have to abandon the concept entirely.
Letting users sign their own keys puts one user at risk if a key is exfiltrated stealthily, as thieves can now add as many of their own keys as they want (assuming continued access to the system!); having a central user CA makes it an obvious target to ursurp any user on the system.
For this reason, we prefer to not employ a strict X.509 PKI, and instead use a slightly adapted “graph of trust”:
- The root CA signs user and administrative CAs
- Administrative CAs sign keys used for administrative purposes not involving user accounts (global settings, group memberships etc. pp.)
- User CAs sign bootstrap keyrings of users (consisting of, at least, one signing key, optionally also a separate encryption key)
Now, depending on the complexity requirements of the client, users can either use their bootstrap keyring (e.g., on a HSM) directly to sign and encrypt their data (cf. User A) or choose to keep this key offline, and only use it to enroll personal device keys (cf. User B). This way, theft of one device key will not allow adversaries to enroll their own additional keys, and use of the User CA is limited to first-time user enrollment, hopefully allowing its secure off-line storage.
(For the Pave’s first release, we omit user CAs, and make users use their admin-signed keys directly, to reduce implementation and user complexity. The full three-tier architecture will be adopted at a later version.)
Database authentication and encryption
Given availability of trusted keys, we require signatures over all database fields that have to be readable by the server application (to authenticate users and manage ACLs) using libsodium detached signatures, and authenticated encryption (automatically using random nonces) for all other fields. Assuming integrity of the underlying cryptograpic primitives and implementation, this ensures tamper resistance.
Database replay attacks
This is a more delicate problem to solve, as we cannot rely on external implementations abstracting this problem away. The only feasible way known to the author is to rely on client-side caching of ‘known good’ states and ensuring that the synced data is strictly newer than existing data, using some property of the synced data to determine age. Time would be the most obvious one, but given the concerning state of NTP security (or lack thereof) not necessarily the most trustworthy one. An authenticated and/or encrypted version column seems to be a safer choice.
Both cases leave two problems to solve:
- First initalisation. Here we have to rely on out-of-band verification to ensure that a client does not get served a critically outdated database.
- Malevolent intermediaries: Assume a user Alice with a known state of n. Database version n+1 is leaked to Eve, together with Bob’s private key. Bob revokes his key and resets his passwords, generating database version n+2. Eve now serves database n+1 to Alice¹. If Alice now saves a password shared with Bob, Eve will have access to it.
However, Eve will not be able to serve as MitM to pass the password to Bob: Even with Bob’s public key, Eve cannot forge Alice’s signature required to create a password entry for Bob, nor can Eve serve Alice Bob’s new key without hiding the changed version.
As such, it might be best to combine both measures: Relying on monotonic versions in addition to timestamps makes attacks more difficult, as Eve will now have to continuously fudge clocks for all participants to avoid detection.
However, assuming merely difficult attacks to be infeasible is a dangerous notion, and the necessary precautions to catch tampering by correlating various signatures might be too complex to be done without introducing side effects.
More research and deliberation is needed here. The first version will not include any form of (database-level) replay protection.
¹ This assumes a completely compromised network, where Eve can also control DNS and has a TLS certificate for the domain configured in Alice’s client. TCP-level replay attacks are already mitigated by TLS.
Private key security
Private key storage is, while not necessarily a solved, at least an actively considered problem, with various solutions suggested:
- Hardware Security Modules – smart cards, USB keys, TPMs, …
- Password-secured key files
Both will (hopefully) protect against an attacker getting read access to a client’s private key – at least for a long enough time to notice the theft and cycle the keys and passwords protected by it – which allows us to derive from it as source of trust for server-sent data.
What we cannot guarantee is Perfect Forward Secrecy: It depends on the ability to negotiate session keys on the fly, which we cannot do in a system with only loose synchronisation between clients. (Round-trip times of several days to encrypt a single password are generally frowned upon.)
To be determined – minimal integrity checks are provided by using AuthentiCode/GateKeeper signed binaries (both for updates and Electron itself), but as of now we cannot ensure integrity of JS payloads, nor are AuthentiCode and GateKeeper entirely trustworthy, due to their reliance on external actors, and the impossibility of pinning certificates.