-
Notifications
You must be signed in to change notification settings - Fork 379
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MSC1998: Two-Factor Authentication Providers
In order to avoid overly-complicating the initial implementation we just provide a basic TOTP authentication flow and a recovery code flow. Signed-off-by: Aleksa Sarai <[email protected]>
- Loading branch information
Showing
2 changed files
with
394 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Two-Factor Authentication as a first-class citizen. | ||
- ``m.login.two-factor.totp`` stage for TOTP-based two-factor. | ||
- ``m.login.two-factor.recovery`` stage for one-time recovery code two-factor. | ||
- Semantics for authentication flows when ``m.login.two-factor.*`` stages are configured. | ||
- ``GET /account/two-factor`` and ``POST /account/two-factor*`` routes. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,389 @@ | ||
# 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/two-factor` to get the current state of enabled providers for the account. | ||
* `POST /_matrix/client/r0/account/two-factor` to configure providers. | ||
* `POST /_matrix/client/r0/account/two-factor/disable` to disable providers. | ||
|
||
The `POST` endpoints all require authentication (including the appropriate | ||
`m.login.two-factor.*` stages if already configured). | ||
|
||
**XXX: Should it be possible for a server to require using two-factor | ||
providers, and in that case should the registration process also involve | ||
configuring two-factor?** | ||
|
||
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/two-factor` | ||
|
||
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/two-factor` | ||
|
||
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. | ||
|
||
#### 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/two-factor 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" | ||
] | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### `POST /_matrix/client/r0/account/two-factor/disable` | ||
|
||
**XXX: This path is super ugly, but is required because we need to have | ||
Authentication Data associated with the request and thus cannot use DELETE.** | ||
|
||
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.** | ||
|
||
#### 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 | ||
|
||
``` | ||
POST /_matrix/client/r0/account/two-factor/disable 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. | ||
|
||
* `POST .../two-factor/disable` is used instead of `DELETE .../two-factor` | ||
because the latter doesn't really support request bodies and thus we cannot | ||
pass the JSON `auth` blob. | ||
|
||
## 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. |