-
Notifications
You must be signed in to change notification settings - Fork 379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MSC1998: Two-Factor Authentication Providers #1998
base: old_master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,382 @@ | ||
# Proposal to support two-factor authentication providers | ||
|
||
This proposal intends to solve [#1997][issue1997]. At the moment, there isn't a | ||
way for a user to have a "standard" two-factor authentication flow on their | ||
account. Most homeservers currently only support the `m.login.password` stage, | ||
making the user susceptible to keyloggers or rogue apps being able to | ||
re-authenticate even after the user decides to de-auth the device. | ||
|
||
In the context of end-to-end encryption, users are in a bit of a better | ||
position because new devices are untrusted-by-default and even backed up megolm | ||
sessions (as in [MSC1219][msc1219]) require additional passphrases or recovery | ||
keys. However this is far from ideal because other users might be tricked into | ||
trusting such a device, and without a mechanism such as [MSC1756][msc1756] the | ||
likelihood of such a mistake is quite high. | ||
|
||
Thus, having an easy-to-use two-factor-authentication flow would allow for an | ||
improvement in the baseline security of a Matrix account. Since the vast | ||
majority of web services provide two-factor authentication using TOTP tokens | ||
(as defined in [RFC 6238][rfc6238]) we replicate this for Matrix so users can | ||
re-use their existing TOTP applications. | ||
|
||
It is also necessary to include a recovery code flow (which server admins might | ||
choose to not support) so that a user who loses their TOTP generating device | ||
can get back into their account. We also take care to allow this system to be | ||
extended in the future with more two-factor providers. | ||
|
||
[issue1997]: https://github.com/matrix-org/matrix-doc/issues/1997 | ||
[msc1219]: https://github.com/matrix-org/matrix-doc/issues/1219 | ||
[msc1756]: https://github.com/matrix-org/matrix-doc/pull/1756 | ||
[rfc6238]: https://tools.ietf.org/html/rfc6238 | ||
|
||
## Proposal | ||
|
||
The CS API natively provides the ability to have multi-stage authentication, so | ||
the bulk of this proposal is defining a new set of authentication stages: | ||
|
||
* `m.login.two-factor.totp` for TOTP tokens. | ||
* `m.login.two-factor.recovery` for recovery codes. | ||
|
||
Servers must specify these as required stages in the authentication flow (with | ||
a separate flow for each provider) for users which have enabled them on their | ||
accounts. They must also be placed after a stage (such as `m.login.password`) | ||
where the user being authenticated in this session has been determined, as | ||
otherwise the homeserver cannot be sure what user's token is being verified. | ||
|
||
A concrete example would mean that a user with both two-factor providers | ||
enabled will have the following flows available: | ||
|
||
* `[ "m.login.password", "m.login.two-factor.totp" ]` | ||
* `[ "m.login.password", "m.login.two-factor.recovery" ]` | ||
|
||
We also require that all future additions to the `m.login.two-factor.*` | ||
namespace have similar semantics to the above and act as two-factor providers | ||
in the following CS API endpoints. | ||
|
||
In order to configure two-factor providers, several new CS API endpoints are required: | ||
|
||
* `GET /_matrix/client/r0/account/twoFactor` to get the current state of enabled providers for the account. | ||
* `POST /_matrix/client/r0/account/twoFactor` to configure providers. | ||
* `DELETE /_matrix/client/r0/account/twoFactor` to disable providers. | ||
|
||
The `POST` and `DELETE` and endpoints all require user-interactive | ||
authentication (including the appropriate `m.login.two-factor.*` stages if | ||
already configured). | ||
|
||
The details of these changes are outlined in the next few sections. | ||
|
||
### `m.login.two-factor.totp` | ||
|
||
Clients submit an auth-dict as follows: | ||
|
||
``` | ||
{ | ||
"type": "m.login.two-factor.totp", | ||
"token": "<token>", | ||
"session": "<session ID>" | ||
} | ||
``` | ||
|
||
Verification of the token is done as described in [RFC 6238][rfc6238], with all | ||
of the relevant parameters being set at configure-time. Each homeserver can | ||
decide how many time-steps of clock skew they wish to allow for a successful | ||
login, though they should be aware of the security trade-off with larger | ||
leniency in clock skew. | ||
|
||
If the token is valid then the authentication succeeds. Otherwise the server | ||
must generate a 401 error. | ||
|
||
[rfc6238]: https://tools.ietf.org/html/rfc6238 | ||
|
||
### `m.login.two-factor.recovery` | ||
|
||
Clients submit an auth-dict as follows: | ||
|
||
``` | ||
{ | ||
"type": "m.login.two-factor.recovery", | ||
"token": "<token>", | ||
"session": "<session ID>" | ||
} | ||
``` | ||
|
||
The given token must be a valid, *unused* token provided by the server as a | ||
recovery code for the current account being authenticated. If so, then the | ||
authentication succeeds and the token must be marked as used so it cannot be | ||
re-used for future authentication flows. Otherwise the server must generate a | ||
401 error. | ||
|
||
### `GET /_matrix/client/r0/account/twoFactor` | ||
|
||
This endpoint provides information about how the user's two-factor settings are | ||
configured. | ||
|
||
Clients that wish to find out what two-factor providers are supported by this | ||
homeserver should look at the flows given by `/_matrix/client/r0/login` and | ||
what stages are in the `m.login.two-factor.*` namespace. | ||
|
||
| Parameter | Type | Description | | ||
| ----------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------- | | ||
| `providers` | `map[string: Provider]` | Set of enabled configured providers (keyed by their `m.login.two-factor.*` name) and associated information about them. | | ||
|
||
*Provider* | ||
|
||
| Parameter | Type | Description | | ||
| ------------ | ------- | ------------------------------------------------------------------------------------------------- | | ||
| `changed_at` | integer | Timestamp, in milliseconds, the last time this two-factor provider had its configuration changed. | | ||
| `enabled_at` | integer | Timestamp, in milliseconds, when this two-factor provider was enabled (reset on `/disable`). | | ||
|
||
An example JSON returned would be: | ||
|
||
``` | ||
{ | ||
"providers": { | ||
"m.login.two-factor.totp": { | ||
"changed_at": 1565749119947, | ||
"enabled_at": 1557800304221 | ||
}, | ||
"m.login.two-factor.recovery": { | ||
"changed_at": 1557800304221, | ||
"enabled_at": 1557800304221 | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### `POST /_matrix/client/r0/account/twoFactor` | ||
|
||
This endpoint allows the client to enable (or reset) a two-factor provider. In | ||
order to avoid users locking themselves out of their accounts, servers should | ||
enable `m.login.two-factor.recovery` if it is not already enabled for the user. | ||
Comment on lines
+146
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some 2FA credential variants will require more round-trips to set up, for example WebAuthn (https://www.w3.org/TR/webauthn/). Does this endpoint allow for that? For example, when setting up a credential the client needs to first get a Likewise for TOTP, it's common that as part of credential registration the server requires a valid OTP for the new credential before activating it, to make sure the client has correctly received and supports the parameters used. Perhaps this should be part of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps I'm misunderstanding WebAuthn (and I'd love to hear more about it -- I've only interacted with the first-gen FIDO stuff very superficially), isn't it intended to replace passwords as one of the factors of authentication (rather than being an alternative MFA mechanism)? If so, then adding a new login flow (separate to this proposal) would seem to be a better path forward to have WebAuthn supported in Matrix. But I think you're right that the current design of Matrix's login flow system doesn't allow for multiple ping-pongs between clients and servers. You'd need to create separate login stages (which seems to me to be a little bit scary since forgetting to include a login flow step means that the authentication would succeed if I understand how it works correctly).
Yes it probably should be, and all MFA registration flows I can think of work that way... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WebAuthn can be used in several ways. Also as single factor. But I believe most common is to use instead of SMS or TOTP codes as second factor.
Comment on lines
+146
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One other consideration is that a single 2FA provider may need to support multiple credentials. While I think this might be possible under the current API by leveraging the provider params dict, it does overlay a lot of functionality on a single endpoint. For any given provider we not only have "enable/reset", but also "add credential" and "remove (specific) credential". I'm not sure if this warrants additional endpoints or not. |
||
|
||
#### Request | ||
|
||
| Parameter | Type | Description | | ||
| ----------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------- | | ||
| `providers` | `map[string: Provider Params]` | Providers (keyed by their `m.login.two-factor.*` name) which the user wishes to enable and their configuration. | | ||
| `auth` | Authentication Data | Additional authentication information for the user-interactive authentication API. | | ||
|
||
Authentication Data is identical as in the existing spec. Provider Params is | ||
required to be an empty JSON object (to allow for future extensions that have | ||
configurable two-factor providers). | ||
|
||
##### Example | ||
|
||
``` | ||
POST /_matrix/client/r0/account/twoFactor HTTP/1.1 | ||
Content-Type: application/json | ||
|
||
{ | ||
"providers": { | ||
"m.login.two-factor.totp": {} | ||
}, | ||
"auth": { | ||
"type": "example.type.foo", | ||
"session": "xxxxx", | ||
"example_credential": "verypoorsharedsecret" | ||
} | ||
} | ||
``` | ||
|
||
#### Response | ||
|
||
In order to facilitate changing two-factor provider parameters in the future, | ||
the server provides all of the parameters of the providers used. It should be | ||
noted that the server may decide to configure more providers than the user | ||
requested. If the response is a success code, the server must have enabled at | ||
least the providers the user specified. | ||
|
||
Providers which are already enabled by the user but were not specified in the | ||
request should not be modified by this operation. | ||
|
||
| Parameter | Type | Description | | ||
| ------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `providers` | `map[string: Provider Params]` | Providers (keyed by their `m.login.two-factor.*` name) which the server enabled or re-configured due to this request, and their parameters. | | ||
|
||
The structure of Provider Params depends on which provider is the key. | ||
|
||
##### `m.login.two-factor.totp` | ||
|
||
| Parameter | Type | Description | | ||
| --------- | ----------- | ---------------------------------------------------------- | | ||
| `params` | TOTP Params | Parameters for the TOTP algorithm used. | | ||
| `seed` | string | String used as a seed for [RFC 6238][rfc6238] TOTP tokens. | | ||
|
||
*TOTP Params* | ||
|
||
| Parameter | Type | Description | | ||
| --------- | ------- | ---------------------------------- | | ||
| `type` | string | Identifier for the TOTP algorithm. | | ||
| `step` | integer | Time-step size in seconds. | | ||
| `size` | integer | Number of token digits. | | ||
|
||
`type` must be `m.totp.v1.rfc6238-sha1`. In order to maximise interoperability, | ||
servers should use the following values: | ||
|
||
* `step` should be 30. | ||
* `size` should be either 6 (recommended) or 8. | ||
|
||
##### `m.login.two-factor.recovery` | ||
|
||
| Parameter | Type | Description | | ||
| --------- | ---------------- | ------------------------------------------------------------------------------------------------------ | | ||
| `tokens` | array of strings | List of one-time-use-only tokens that act as recovery codes for use with `m.login.two-factor.recovery` | | ||
|
||
Each token should be a sufficiently randomly generated string and be long | ||
enough that a brute-force attack would be infeasible (but only using | ||
case-insensitive common characters to make it easier for users to enter them). | ||
Our recommendation is 12 alpha-numeric characters excluding look-alikes. | ||
|
||
##### Example | ||
|
||
And then the server returns a JSON object with the following structure: | ||
|
||
``` | ||
{ | ||
"providers": { | ||
"m.login.two-factor.totp": { | ||
"params": { | ||
"type": "m.totp.v1.rfc6238-sha1", | ||
"step": 30, | ||
"size": 6, | ||
}, | ||
"seed": "Kung-fu? I'm going to learn ... kung-fu?" | ||
}, | ||
"m.login.two-factor.recovery": { | ||
"tokens": [ | ||
"neo", | ||
"morpheus", | ||
"trinity", | ||
"apoc", | ||
"switch", | ||
"cypher", | ||
"tank", | ||
"dozer", | ||
"mouse" | ||
] | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### `DELETE /_matrix/client/r0/account/twoFactor` | ||
|
||
This is used to disable two-factor providers. The server must only disable the | ||
providers requested with the exception of `m.login.two-factor.recovery`. If, | ||
after this deletion operation completes, no other two-factor providers would be | ||
enabled anymore then the server may also disable `m.login.two-factor.recovery`. | ||
|
||
The value `m.login.two-factor.*` has a special meaning, and is used to indicate | ||
that all enabled providers should be disabled for this user. **XXX: This | ||
interface is probably racy and might not be super-useful.** | ||
|
||
**NOTE**: While HTTP does not explicitly define the meaning of a content-body | ||
with `DELETE` we assume it to be the same as `POST` here. | ||
|
||
#### Request | ||
|
||
| Parameter | Type | Description | | ||
| ----------- | ------------------- | ---------------------------------------------------------------------------------- | | ||
| `providers` | array of string | Providers (using their `m.login.two-factor.*` name) to be disabled. | | ||
| `auth` | Authentication Data | Additional authentication information for the user-interactive authentication API. | | ||
|
||
Authentication Data is identical as in the existing spec. | ||
|
||
##### Example | ||
|
||
``` | ||
DELETE /_matrix/client/r0/account/twoFactor HTTP/1.1 | ||
Content-Type: application/json | ||
|
||
{ | ||
"providers": [ | ||
"m.login.two-factor.totp", | ||
"m.login.two-factor.recovery" | ||
], | ||
"auth": { | ||
"type": "example.type.foo", | ||
"session": "xxxxx", | ||
"example_credential": "verypoorsharedsecret" | ||
} | ||
} | ||
``` | ||
|
||
#### Response | ||
|
||
| Parameter | Type | Description | | ||
| ----------- | ---------------- | -------------------------------------------------------------------------------------------------- | | ||
| `providers` | array of strings | Providers (using their `m.login.two-factor.*` name) which the server disabled due to this request. | | ||
|
||
**XXX: Maybe the response should be the set of providers that remain enabled?** | ||
|
||
``` | ||
{ | ||
"providers": [ | ||
"m.login.two-factor.totp", | ||
"m.login.two-factor.recovery" | ||
] | ||
} | ||
``` | ||
|
||
## Trade-offs | ||
|
||
* We don't allow clients to configure any of the parameters for the current set | ||
of two-factor providers. This does mean a small reduction in user | ||
configurability but it reduce the threat of bad configurations and makes the | ||
server implementations (marginally) simpler because there is no need for | ||
verification. However we allow for future extensions to add configuration, | ||
since other two-factor providers (like YubiKey) require the client to specify | ||
some parameters. | ||
|
||
* There is a special-case for `m.login.two-factor.recovery` in order to | ||
make it act like most two-factor systems. This does result in an increase in | ||
implementation complexity (this is why the APIs allow the server to configure | ||
more providers than requested), but it will allow for a far smoother user | ||
experience than if every client had to reimplement their own special-case | ||
handling of recovery codes. | ||
|
||
* There is a special-case for `m.login.two-factor.*` in order to provide a | ||
simple "off switch" for two-factor authentication after a user has configured | ||
it. This does result in an increase in implementation complexity, but such | ||
complexity would've had to live in clients if it wasn't provided by servers | ||
(and would've been more racy). | ||
|
||
* There are many other two-factor systems that might have more desirable | ||
security properties, but the primary benefit of TOTP is a very large number | ||
of people are familiar with it and there are many applications which support | ||
it. However the two-factor endpoints have been made generic enough that (in | ||
the future) other two-factor systems (such as YubiKey) could be added easily | ||
and in a backwards-compatible way. | ||
|
||
## Potential issues | ||
|
||
* Significant clock skew issues can result in users not being able to log in. | ||
It's the job of server administrators to decide how much clock skew they | ||
believe is reasonable to handle. They should be aware that increasing the | ||
compensation for clock skew results in more computation (TOTP requires | ||
checking each time-step by computing a HMAC) and results in longer-lasting | ||
TOTP codes that open the potential for reuse. | ||
|
||
## Security considerations | ||
|
||
* The recommended parameters for `m.login.two-factor.totp` come directly from | ||
the RFC, and have been widely implemented. There is no reason to think they | ||
are insecure. | ||
|
||
* The recommended parameters for `m.login.two-factor.recovery` is based on | ||
common practice by many web services that provide two-factor authentication. | ||
Recommending the avoidance of look-alikes is a usability feature and doesn't | ||
meaningfully impact the entropy. | ||
|
||
* We do not reset already-configured recovery codes when other two-factor | ||
methods are configured or disabled. If the concern is that an existing | ||
two-factor provider was compromised, then it seems unlikely that the recovery | ||
codes would also be compromised. Some web services do reset your recovery | ||
codes if you reconfigure TOTP with a new seed, but this appears to be an | ||
implementation detail rather than a security feature. | ||
|
||
## Conclusion | ||
|
||
With this proposal, Matrix would be able to increase the baseline security of | ||
users' accounts by adding a very minimal implementation of the widely-used | ||
TOTP-based two-factor system. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
turns out I can't read, as vdh points out these should be
two_factor
: #1998 (comment)