14 KiB
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. Iftype
is omitted, this is the default.username
– A username or other account identifierpassword
– A password or other account secreturl
– A web addressemail
– An email addressaddress
– A street or postal addressdate
– A date represented as a UNIX timestampmonthyear
– A month / year combination expressed asMM/YY
phonenumber
– A phone numbersecurity_question
– A security question answer. The question itself is to be put into thetitle
field.
value
– The content stored (required). For fields of typedate
, this is an integer, otherwise a string.conceal
– Display hint indicating whether or not clients should obscure the value by default (optional). Defaults tofalse
for all field types exceptpassword
.
{
"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
asorg.matrix.msc4114.vault
m.vault.secret
asorg.matrix.msc4114.vault.secret
m.vault.child
asorg.matrix.msc4114.vault.child
m.secret
asorg.matrix.msc4114.secret
m.secret.sections
asorg.matrix.msc4114.secret.sections
Dependencies
None.
-
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. ↩︎