diff --git a/proposals/4114-matrix-as-a-password-manager.md b/proposals/4114-matrix-as-a-password-manager.md new file mode 100644 index 00000000000..a1943a1d2f8 --- /dev/null +++ b/proposals/4114-matrix-as-a-password-manager.md @@ -0,0 +1,292 @@ +# MSC4114: Matrix as a password manager + +Password managers are used in abundance in both the personal and corporate +space to securely and conveniently store and share secrets. A whole ecosystem +of apps and services has sprouted and users have come to trust them for +their advanced security features. + +Matrix, in turn, is a generalized protocol for securely storing and exchanging +data in a federated network, the primary use case being encrypted messaging. +This proposal outlines a scheme for extending Matrix to act as a password +manager by borrowing from built-in concepts such as encryption, rooms and +spaces. The underlying premise of this is that if it's secure enough to handle +personal communication, it should be secure enough to store passwords. Or put +another way, if you cannot trust Matrix to store your passwords, how can you +trust it to store your potentially equally sensitive private communication? The +truthiness of this assumption is underpinned later in this proposal by +comparing the cryptographic primitives used in Matrix and Bitwarden. + +## Proposal + +On a high level, vaults and secrets are represented as encrypted spaces and +rooms, respectively. Both use the existing encryption mechanics without +introducing further layers. + +Home servers that support storing secrets can generally also allow messaging +via normal rooms and spaces. However, from a client perspective, it's +undesirable to have vaults constantly unlocked. Therefore, the messaging +capabilities should only be used for things like service announcements +and not for general chatting. + +Federation is explicitly excluded from this proposal to reduce its scope. + +### Vaults as spaces / secrets as rooms + +Two new room types `m.vault` and `m.vault.secret` are introduced. Vault-rooms +work similar to [spaces] and can group other vault-rooms or secret-rooms. +Secret-rooms store the actual sensitive data, such as passwords. + +Sending normal `m.room.message` events within vault- and secret-rooms is +discouraged - clients are not generally expected to have a way to render the +timeline of these rooms. As such, vault- and secret-rooms should be created +with `m.room.power_levels` which prohibit normal events by setting +`events_default` to a suitably high number. In the default power level +structure, this would be 100. + +Additionally, vault- and secret-rooms should be created with encryption enabled +and a join rule of `invite` to prevent unintended access without explicit +sharing. + +To include a secret (or another vault) in a vault, an `m.vault.child` state +event is introduced. The state key of the event is the room ID of the secret +(or vault) to include. In `content`, the event has a single field `via` +that lists servers to try and join through. + +``` +{ + "type": "m.vault.child", + "state_key": "!roomid:example.org", + "content": { + "via": [ + "example.org", + "other.example.org" + ] + }, + ... +} +``` + +No rooms other than those of type `m.vault` and `m.vault.secret` are allowed to +be stored in `m.vault.child` events. + +Unlike with spaces, there is no corresponding `m.vault.parent` event, meaning a +vault or secret does not know which parent vaults it is contained in. While +this backlink exists in spaces to aid discoverability, this feature appears +unessential for secret hierarchies. + +### Secret events + +To store secrets, a new room event type `m.secret` is introduced. Building upon +[MSC1767], secret-events contain a single `m.secret.sections` content block that +holds an ordered array of section definitions with the following properties: + +- `title` – A textual description (optional) +- `fields` – An ordered array of field definitions (required) + +Fields in turn have the following properties: + +- `title` – A textual description (optional) +- `type` – An identifier for the type of value stored (optional). One of: + - `text` – Any text. If `type` is omitted, this is the default. + - `username` – A username or other account identifier + - `password` – A password or other account secret + - `url` – A web address + - `email` – An email address + - `address` – A street or postal address + - `date` – A date represented as a UNIX timestamp + - `monthyear` – A month / year combination expressed as `MM/YY` + - `phonenumber` – A phone number + - `security_question` – A security question answer. The question itself is to be + put into the `title` field. +- `value` – The content stored (required). For fields of type `date`, this is an + integer, otherwise a string. +- `conceal` – Display hint indicating whether or not clients should obscure the value + by default (optional). Defaults to `false` for all field types except `password`. + +``` +{ + "type": "m.secret", + "content": { + "m.secret.sections": [{ + "fields": [{ + "type": "username", + "value": "johndoe" + }, { + "type": "password", + "value": "johnboy84" + }] + }, { + "title": "Security questions", + fields: [{ + "type": "security_question", + "title": "What is your favorite ice cream flavor?", + "value": "Lemon" + }] + }] + } +} +``` + +Clients may choose to use the order of sections and fields in the event for +sorting data in the UI but are not required to do so. + +`m.secret` events are not allowed to be used in rooms other than those of type +`m.vault.secret` and should always be encrypted. + +When updating secrets, clients should use [event replacements] which allows +building a history of changes. Similarly, clients can use [redactions] to +clear parts of the change history. At any given time, a secret-room should, +thus, contain at most one non-redacted, non-replaced `m.secret` event which +gives the current state of the secret. + +### Other aspects + +Vaults and secrets can be shared through standard room membership. When adding +a secret-room (or another vault-room) to a vault-room, a restricted [join rule] +should be set so that being invited into a vault-room enables users to also +join all of its child-rooms. + +The standard `m.room.name` and `m.room.avatar` state events can be used to label +and decorate vaults and secrets. These are not currently encryptable but will be +once [MSC3414] lands. While exposing vault and secret names is not considered a +security concern by other password managers such as [pass], it can still be a +privacy concern. Therefore, clients should warn users appropriately in the meantime. + +### Matrix vs. Bitwarden + +#### Login + +[Bitwarden's protocol] uses key stretching on several levels to make it harder +to brute-force a login. The client uses PBKDF2 with 600,000 iterations to +derive a 256-bit master key from the account password and email. The account +password and master key are then turned into a master password hash using +PBKDF-SHA256. This hash is sent to the server where it is hashed again using +PBKDF2-SHA256 with 600,000 iterations before verifying the login. + +Matrix, on the other hand, doesn't mandate the use of key derivation functions +during login and instead makes this aspect an implementation detail. Synapse, +for instance, uses [bcrypt] with a default of [12 iterations][^1] for +`m.login.password` flows. Keycloak, as an exemplary OIDC provider, uses +[PBKDF2-SHA512 with 210,000 iterations] by default. + +It's important to note here that login to a Matrix account doesn't actually +give access to the Megolm keys required to decrypt historic events. The keys +have to either be shared from another existing device or retrieved from the +server-side key backup. The latter is encrypted using AES-256 CBC where the +[256-bit curve25519 private key] is commonly stored in 4S. The key that +unlocks 4S itself can be derived from a passphrase using PBKDF2 where the +number of iterations is [configurable]. + +In summary, Matrix can be configured to provide a similar level of brute-force +login protection as Bitwarden using key stretching on multiple levels. + +#### Encryption + +Bitwarden uses a single 512-bit key, consisting of a 256-bit encryption key +and a 256-bit MAC key, to symmetrically encrypt all vault items using +[AES-256 CBC]. To protect this key, it is encrypted with the HKDF-stretched +master key using AES-256 and a random 128-bit initialization vector. The +encrypted key is then synced across clients via the server. + +Matrix, on the other hand, employs [Megolm] which also uses AES-256 CBC for +symmetric encryption but obtains the key differently. Megolm is session-based +where each session uses a ratchet that is initialized with 1024-bit +cryptographically secure random data. The ratchet is wound forward through +hashing on each encrypted message and the symmetric encryption key is derived +by hashing the ratchet value. Additionally Megaolm uses Ed25519 to provide +message authenticity through signatures. Megolm keys are synced among clients +via either key requests in encrypted to-device messages or the server-side key +backup. + +To sum up, the main difference here is that Bitwarden uses a single symmetric +key to encrypt everything whereas Matrix uses per-event keys. + +#### Sharing + +Bitwarden only allows sharing vault items through organizations. Like users, +organizations have a single symmetric key that is used to encrypt all vault +items. The symmetric key is encrypted with the public part of the organization +creator's RSA key and synced across the creator's devices via Bitwarden's +servers. Each user's RSA key pair is generated upon account creation using +RSA-2048 which, interestingly, is below what the NSA [recommends]. When another +user is invited into the organization, the inviter encrypts the symmetric key +with the invitee's public RSA key and shares it via Bitwarden's servers. +Bitwarden doesn't appear to detail what happens when a user leaves an +organization. + +In Matrix, users are invited into rooms to share future encrypted events with +them but clients don't currently share keys for past events with other users. +For rooms that use the `shared` history visibility setting, the accepted +[MSC3061] defines a way for the inviter to share keys for past messages with +the invitee – even without a key share request. The sharing is done via +[`m.forwarded_room_key`] to-device messages that are encrypted using Olm which +uses Curve25519. + +In summary, there are no material differences here other than the RSA vs. +Curve25519 discrepancy and the already known fact that Bitwarden relies on a +single key while Matrix uses per-message keys. + +## Potential issues + +When not sharing, UTDs on encrypted secrets would be fatal and result in loss +of access to the secret. Clients might be able to mitigate this by offering +offline encrypted backups. + +## Alternatives + +Instead of dedicated space-like `m.vault` rooms, normal [spaces] could be used to +group secret-rooms. This has the downside that secret-rooms and other room types +can mingle in the hierarchy which makes it harder for clients to recognise spaces +devoted exclusively to storing secrets. + +Multiple `m.secret` events could be stored in the same room, eliminating the +need to have different room types for vaults and secrets. However, this doesn't +allow for fine-grained sharing of secrets with other users and would make it +impossible to reuse `m.room.name` and `m.room.avatar` events to annotate secrets. + +Secrets could be stored in state events which already have replacement semantics. +As mentioned earlier though, state events are not encryptable yet. + +The sections and fields of `m.secret` events could be broken out into separate +events. This would, however, complicate client display logic and require an +additional way of sorting sections and fields. + +## Security considerations + +Until [MSC3414] lands, `m.room.name` and `m.room.avatar` events will leak meta +data of vaults and secrets. + +## Unstable prefix + +Until this proposal is accepted into the spec implementations should refer to: + +- `m.vault` as `org.matrix.msc4114.vault` +- `m.vault.secret` as `org.matrix.msc4114.vault.secret` +- `m.vault.child` as `org.matrix.msc4114.vault.child` +- `m.secret` as `org.matrix.msc4114.secret` +- `m.secret.sections` as `org.matrix.msc4114.secret.sections` + +## Dependencies + +None. + +[12 iterations]: https://github.com/element-hq/synapse/blob/develop/synapse/config/registration.py?rgh-link-date=2024-03-07T20%3A31%3A39Z#L79 +[256-bit curve25519 private key]: https://spec.matrix.org/v1.10/client-server-api/#recovery-key +[AES-256 CBC]: https://bitwarden.com/help/what-encryption-is-used +[Bitwarden's protocol]: https://bitwarden.com/help/bitwarden-security-white-paper/ +[bcrypt]: https://github.com/element-hq/synapse/blob/develop/synapse/handlers/auth.py?rgh-link-date=2024-03-07T20%3A31%3A39Z#L1642 +[configurable]: https://spec.matrix.org/v1.10/client-server-api/#deriving-keys-from-passphrases +[event replacements]: https://spec.matrix.org/latest/client-server-api/#event-replacements +[join rule]: https://spec.matrix.org/v1.3/client-server-api/#mroomjoin_rules +[Megolm]: https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md +[MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767 +[MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 +[MSC3414]: https://github.com/matrix-org/matrix-spec-proposals/pull/3414 +[`m.forwarded_room_key`]: https://spec.matrix.org/v1.10/client-server-api/#mforwarded_room_key +[pass]: https://www.passwordstore.org/ +[PBKDF2-SHA512 with 210,000 iterations]: https://www.keycloak.org/docs/latest/server_admin/#hashing-iterations +[recommends]: https://en.wikipedia.org/wiki/Commercial_National_Security_Algorithm_Suite +[redactions]: https://spec.matrix.org/latest/client-server-api/#redactions +[spaces]: https://spec.matrix.org/v1.3/client-server-api/#spaces + +[^1]: For a high-level comparison of bcrypt and PBKDF2 performance, see https://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage/6415#6415.