Skip to content

Commit

Permalink
MSC1998: Two-Factor Authentication Providers
Browse files Browse the repository at this point in the history
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
cyphar committed May 14, 2019
1 parent ba18a6e commit 218eef6
Show file tree
Hide file tree
Showing 2 changed files with 389 additions and 0 deletions.
5 changes: 5 additions & 0 deletions changelogs/client_server/newsfragments/1998.feature
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.
384 changes: 384 additions & 0 deletions proposals/1998-two-factor-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,384 @@
# 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 TOTP, 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 TOTP, and in that case
should the registration process also involve generating a TOTP seed?**

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. |

Stage Data:
| 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. |

```
{
"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.

0 comments on commit 218eef6

Please sign in to comment.