From d5f3e93df3dc3f351e56b603f9f051f97f962c75 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Wed, 9 Jun 2021 22:49:49 +0200 Subject: [PATCH 01/21] start apake msc --- proposals/xxxx-apake_authentication.md | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 proposals/xxxx-apake_authentication.md diff --git a/proposals/xxxx-apake_authentication.md b/proposals/xxxx-apake_authentication.md new file mode 100644 index 00000000000..e2977d53b16 --- /dev/null +++ b/proposals/xxxx-apake_authentication.md @@ -0,0 +1,30 @@ +# \[WIP]MSCXXXX: aPAKE authentication + +Like most password authentication, matrix's [login](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-login) requires sending the password in plain text (though usually encrypted in transit with https). This requirement has as (obvious) downside that a man in the middle attack would allow reading the password, but also requires that the server has temporary access to the plaintext password (which will subsequently be hashed before storage). + +A Password Authenticated Key Exchange (PAKE) can prevent the need for sending the password in plaintext for login, and an aPAKE (asymmetric or augmented) allows for safe authentication without the server ever needing access to the plaintext password. OPAQUE is a modern implementation of an aPAKE that is [currently an ietf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-opaque-05). + +## Proposal + + + +## Potential issues + +Adding this authentication method requires client developers to implement this in all matrix clients. + +OPAQUE is still a draft at ietf, and doesn't have very many implementations, potentially making the lives of client developers harder. *perhaps srp is better? I'd be fine with deciding to go with SRP, as it has more implementations available.* + +## Alternatives + +Secure Remote Password (SRP) is the most commonly used alternate aPAKE, which has a longer track record, and is used by apple and 1password, and is [rfc2945](https://datatracker.ietf.org/doc/html/rfc2945). + +*Bitwardens scheme can be mentioned here as well, since it does allow auth without the server learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash of the password before sending the has to the server for auth, practically making the hash the new password, and as such it doesn't protect against a mitm.* + + +## Security considerations + +Probably loads, though SRP and OPAQUE have had lots of eyes, I would assume. + +## Unstable prefix + + From f218050030bfdabfa613bba1f47311520b81b665 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Thu, 17 Jun 2021 00:48:28 +0200 Subject: [PATCH 02/21] first basic outline of srp login flow --- proposals/xxxx-apake_authentication.md | 62 ++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/proposals/xxxx-apake_authentication.md b/proposals/xxxx-apake_authentication.md index e2977d53b16..8b490bc4f01 100644 --- a/proposals/xxxx-apake_authentication.md +++ b/proposals/xxxx-apake_authentication.md @@ -2,21 +2,75 @@ Like most password authentication, matrix's [login](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-login) requires sending the password in plain text (though usually encrypted in transit with https). This requirement has as (obvious) downside that a man in the middle attack would allow reading the password, but also requires that the server has temporary access to the plaintext password (which will subsequently be hashed before storage). -A Password Authenticated Key Exchange (PAKE) can prevent the need for sending the password in plaintext for login, and an aPAKE (asymmetric or augmented) allows for safe authentication without the server ever needing access to the plaintext password. OPAQUE is a modern implementation of an aPAKE that is [currently an ietf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-opaque-05). +A Password Authenticated Key Exchange (PAKE) can prevent the need for sending the password in plaintext for login, and an aPAKE (asymmetric or augmented) allows for safe authentication without the server ever needing access to the plaintext password. OPAQUE is a modern implementation of an aPAKE that is [currently an ietf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-opaque-05), but as it's still pending and doesn't have many open source implementations using it would require implementing it ourselves. As such choosing to use SRP ([rfc2945](https://datatracker.ietf.org/doc/html/rfc2945)) makes more sense. SRP has as downside that the salt is transmitted to the client before authentication, allowing a precomputation attack which would speed up a followup attack after a server compromise. However, should a server be compromised, it is probably simpler to generate an access token and impersonate the target that way. ## Proposal - +Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. + +### Login flow + +To start the login flow the client sends it's username to obtain the salt and SRP group as: + +`POST /_matrix/client/r0/login` +``` +{ + "type": "m.login.srp6a.init", + "username": "cheeky_monkey" +} +``` +The server responds with the salt and SRP group: +``` +{ + "prime": N, + "generator": g, + "salt": s, + "servervalue": B +} +``` +Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user as supplied in the `POST` and B is the public server value. + +**TODO:** explain the SRP calculation done clientside and serverside? + +The client will then respond with +`POST /_matrix/client/r0/login` +``` +{ + "type": "m.login.srp6a.verify", + "evidencemessage": M1, + "clientvalue": A +} +``` +Here `M1` is the client evidence message, and A is the public client value. +Upon successful authentication the server will respond with the regular login success status code 200: + +*We can also prove the identity of the server here by adding M2, probably worth it?* +``` +{ + "user_id": "@cheeky_monkey:matrix.org", + "access_token": "abc123", + "device_id": "GHTYAJCE", + "well_known": { + "m.homeserver": { + "base_url": "https://example.org" + }, + "m.identity_server": { + "base_url": "https://id.example.org" + } + } +} +``` ## Potential issues Adding this authentication method requires client developers to implement this in all matrix clients. -OPAQUE is still a draft at ietf, and doesn't have very many implementations, potentially making the lives of client developers harder. *perhaps srp is better? I'd be fine with deciding to go with SRP, as it has more implementations available.* +SRP is vulnerable to precomputation attacks and it is incompatible elliptic-curve cryptography. +Matthew Green judges it as ["It’s not ideal, but it’s real." and "not obviously broken"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/) and it's a fairly old protocol. ## Alternatives -Secure Remote Password (SRP) is the most commonly used alternate aPAKE, which has a longer track record, and is used by apple and 1password, and is [rfc2945](https://datatracker.ietf.org/doc/html/rfc2945). +OPAQUE is the more modern protocol, which has the added benefit of not sending the salt in plain text to the client, but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curves. *Bitwardens scheme can be mentioned here as well, since it does allow auth without the server learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash of the password before sending the has to the server for auth, practically making the hash the new password, and as such it doesn't protect against a mitm.* From fc4f4b1855b34be66f8bd546322f861bb7323779 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Thu, 17 Jun 2021 00:50:42 +0200 Subject: [PATCH 03/21] be more in line with json names --- proposals/xxxx-apake_authentication.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/xxxx-apake_authentication.md b/proposals/xxxx-apake_authentication.md index 8b490bc4f01..6c37d15bb75 100644 --- a/proposals/xxxx-apake_authentication.md +++ b/proposals/xxxx-apake_authentication.md @@ -25,7 +25,7 @@ The server responds with the salt and SRP group: "prime": N, "generator": g, "salt": s, - "servervalue": B + "server_value": B } ``` Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user as supplied in the `POST` and B is the public server value. @@ -37,8 +37,8 @@ The client will then respond with ``` { "type": "m.login.srp6a.verify", - "evidencemessage": M1, - "clientvalue": A + "evidence_message": M1, + "client_value": A } ``` Here `M1` is the client evidence message, and A is the public client value. From 86e4a6e5426e1c54cd86b7bc18b0929beaa9acbd Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Thu, 17 Jun 2021 21:12:45 +0200 Subject: [PATCH 04/21] first full description of registration and login flow with srp --- proposals/xxxx-apake_authentication.md | 104 +++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/proposals/xxxx-apake_authentication.md b/proposals/xxxx-apake_authentication.md index 6c37d15bb75..81b903ac549 100644 --- a/proposals/xxxx-apake_authentication.md +++ b/proposals/xxxx-apake_authentication.md @@ -8,6 +8,54 @@ A Password Authenticated Key Exchange (PAKE) can prevent the need for sending th Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. +### Registration flow +*SRP registration flow isn't that different from a normal account registration, but since currently the API only expects 'normal' password auth we may need to add an authentication type key to differentiate from SRP and password* + +`GET /_matrix/client/r0/register` +``` +{ + "auth_types": ["password", "srp6a"] + "srp_groups": [supported groups] +} +``` +Here the server sends it's supported authentication types (in this case only password and srp6a) and if applicable it sends the supported SRP groups, as specified by the [SRP specification](https://datatracker.ietf.org/doc/html/rfc5054#page-16) or [rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). + +The client then chooses an srp group and generates a random salt `s`. +The client then calculates the verifier `v` as: + +$ x = H(s, p) $ +$ v = g^x $ + +Here H() is a secure hash function, and p is the user specified password. +Note that all values are calculated modulo N. + +This is then sent to the server, otherwise mimicking the password registration, through: + +`POST /_matrix/client/r0/register?kind=user +``` +{ + "auth": { + "type": "example.type.foo", + "session": "xxxxx", + "example_credential": "verypoorsharedsecret" + }, + "auth_type": "srp6a", + "username": "cheeky_monkey", + "verifier": v, + "group": [g,N], + "salt": s, + "device_id": "GHTYAJCE", + "initial_device_display_name": "Jungle Phone", + "inhibit_login": false +} +``` + +The server stores the verifier, salt, and group next to the username. + +### Convert from password to SRP + +*I haven't thought this through, ideas are welcome. It should probably be something along the lines of a password change and otherwise more or less follow the registration as specced above.* + ### Login flow To start the login flow the client sends it's username to obtain the salt and SRP group as: @@ -25,12 +73,46 @@ The server responds with the salt and SRP group: "prime": N, "generator": g, "salt": s, - "server_value": B + "server_value": B, + "auth_id": "12345" } ``` -Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user as supplied in the `POST` and B is the public server value. +Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user as supplied in the `POST`, B is the public server value, and auth_id is the id of this authentication flow, can by any unique random string. -**TODO:** explain the SRP calculation done clientside and serverside? +the server calculates B as: + +$ B = kv + g^b $ +where b is a private randomly generated value for this session (server side) and k is given as: + +$ k = H(N, g) $ + +The client then calculates: + +$ A = g^a $ +where a is a private randomly generated value for this session (client side). + +Both then calculate: + +$ u = H(A, B) $ + +Next the client calculates: + +$ x = H(s, p) $ +$ S = (B - kg^x) ^ (a + ux) $ +$ K = H(S) $ + +The server calculates: + +$ S = (Av^u) ^ b $ +$ K = H(S) $ + +Resulting in the shared session key K. + +To complete the authentication we need to prove to the server that the session key K is the same. +*note that this proof is direcrly lifted from the [SRP spec](http://srp.stanford.edu/design.html), anothe proof can be possible as well.* +The client calculates: + +$ M1 = H(H(N) xor H(g), H(I), s, A, B, K) $ The client will then respond with `POST /_matrix/client/r0/login` @@ -38,18 +120,23 @@ The client will then respond with { "type": "m.login.srp6a.verify", "evidence_message": M1, - "client_value": A + "client_value": A, + "auth_id": "12345" } ``` Here `M1` is the client evidence message, and A is the public client value. -Upon successful authentication the server will respond with the regular login success status code 200: +Upon successful authentication (ie M1 matches) the server will respond with the regular login success status code 200: + +To prove the identity of the server to the client we can send back M2 as: + +$ M2= H(A, M, K) $ -*We can also prove the identity of the server here by adding M2, probably worth it?* ``` { "user_id": "@cheeky_monkey:matrix.org", "access_token": "abc123", "device_id": "GHTYAJCE", + "evidence_message": M2 "well_known": { "m.homeserver": { "base_url": "https://example.org" @@ -61,6 +148,9 @@ Upon successful authentication the server will respond with the regular login su } ``` +The client verifies that M2 matches, and is subsequently logged in. + + ## Potential issues Adding this authentication method requires client developers to implement this in all matrix clients. @@ -79,6 +169,8 @@ OPAQUE is the more modern protocol, which has the added benefit of not sending t Probably loads, though SRP and OPAQUE have had lots of eyes, I would assume. +This whole scheme only works if the user can trust the client, which may be an issue in the case of a 'random' javascript hosted matrix client, though this is out of scope for this MSC. + ## Unstable prefix From 8b578fa85eb862932e125ecbe4cd2e656a48e0f6 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Thu, 17 Jun 2021 21:26:57 +0200 Subject: [PATCH 05/21] fix markdown issues --- proposals/xxxx-apake_authentication.md | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/proposals/xxxx-apake_authentication.md b/proposals/xxxx-apake_authentication.md index 81b903ac549..519b7943d0c 100644 --- a/proposals/xxxx-apake_authentication.md +++ b/proposals/xxxx-apake_authentication.md @@ -11,7 +11,7 @@ Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. ### Registration flow *SRP registration flow isn't that different from a normal account registration, but since currently the API only expects 'normal' password auth we may need to add an authentication type key to differentiate from SRP and password* -`GET /_matrix/client/r0/register` +`GET /_matrix/client/r0/register` ``` { "auth_types": ["password", "srp6a"] @@ -23,15 +23,15 @@ Here the server sends it's supported authentication types (in this case only pas The client then chooses an srp group and generates a random salt `s`. The client then calculates the verifier `v` as: -$ x = H(s, p) $ -$ v = g^x $ + x = H(s, p) + v = g^x Here H() is a secure hash function, and p is the user specified password. Note that all values are calculated modulo N. This is then sent to the server, otherwise mimicking the password registration, through: -`POST /_matrix/client/r0/register?kind=user +`POST /_matrix/client/r0/register?kind=user` ``` { "auth": { @@ -60,7 +60,7 @@ The server stores the verifier, salt, and group next to the username. To start the login flow the client sends it's username to obtain the salt and SRP group as: -`POST /_matrix/client/r0/login` +`POST /_matrix/client/r0/login` ``` { "type": "m.login.srp6a.init", @@ -81,41 +81,41 @@ Here N is the prime and g is the generator of the SRP group. s is the stored sal the server calculates B as: -$ B = kv + g^b $ + B = kv + g^b where b is a private randomly generated value for this session (server side) and k is given as: -$ k = H(N, g) $ + k = H(N, g) The client then calculates: -$ A = g^a $ + A = g^a where a is a private randomly generated value for this session (client side). Both then calculate: -$ u = H(A, B) $ + u = H(A, B) Next the client calculates: -$ x = H(s, p) $ -$ S = (B - kg^x) ^ (a + ux) $ -$ K = H(S) $ + x = H(s, p) + S = (B - kg^x) ^ (a + ux) + K = H(S) The server calculates: -$ S = (Av^u) ^ b $ -$ K = H(S) $ + S = (Av^u) ^ b + K = H(S) Resulting in the shared session key K. To complete the authentication we need to prove to the server that the session key K is the same. -*note that this proof is direcrly lifted from the [SRP spec](http://srp.stanford.edu/design.html), anothe proof can be possible as well.* +*note that this proof is directly lifted from the [SRP spec](http://srp.stanford.edu/design.html), anothe proof can be possible as well.* The client calculates: -$ M1 = H(H(N) xor H(g), H(I), s, A, B, K) $ + M1 = H(H(N) xor H(g), H(I), s, A, B, K) The client will then respond with -`POST /_matrix/client/r0/login` +`POST /_matrix/client/r0/login` ``` { "type": "m.login.srp6a.verify", @@ -129,7 +129,7 @@ Upon successful authentication (ie M1 matches) the server will respond with the To prove the identity of the server to the client we can send back M2 as: -$ M2= H(A, M, K) $ + M2 = H(A, M, K) ``` { From 5696f2894d0bd2ecb96463f8cd1d3a4cba8111d8 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Sun, 20 Jun 2021 08:16:32 +0200 Subject: [PATCH 06/21] spelling fixes --- proposals/xxxx-apake_authentication.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/proposals/xxxx-apake_authentication.md b/proposals/xxxx-apake_authentication.md index 519b7943d0c..6517eaeed07 100644 --- a/proposals/xxxx-apake_authentication.md +++ b/proposals/xxxx-apake_authentication.md @@ -9,7 +9,8 @@ A Password Authenticated Key Exchange (PAKE) can prevent the need for sending th Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. ### Registration flow -*SRP registration flow isn't that different from a normal account registration, but since currently the API only expects 'normal' password auth we may need to add an authentication type key to differentiate from SRP and password* + +To allow clients to discover `GET /_matrix/client/r0/register` ``` @@ -109,12 +110,13 @@ The server calculates: Resulting in the shared session key K. To complete the authentication we need to prove to the server that the session key K is the same. -*note that this proof is directly lifted from the [SRP spec](http://srp.stanford.edu/design.html), anothe proof can be possible as well.* +*note that this proof is directly lifted from the [SRP spec](http://srp.stanford.edu/design.html), another proof can be possible as well.* The client calculates: M1 = H(H(N) xor H(g), H(I), s, A, B, K) The client will then respond with + `POST /_matrix/client/r0/login` ``` { @@ -162,12 +164,12 @@ Matthew Green judges it as ["It’s not ideal, but it’s real." and "not obviou OPAQUE is the more modern protocol, which has the added benefit of not sending the salt in plain text to the client, but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curves. -*Bitwardens scheme can be mentioned here as well, since it does allow auth without the server learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash of the password before sending the has to the server for auth, practically making the hash the new password, and as such it doesn't protect against a mitm.* +*Bitwardens scheme can be mentioned here as well, since it does allow auth without the server learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash of the password before sending the hash to the server for auth, practically making the hash the new password, and as such it doesn't protect against a mitm.* ## Security considerations -Probably loads, though SRP and OPAQUE have had lots of eyes, I would assume. +*Probably loads, though SRP and OPAQUE have had lots of eyes, I would assume.* This whole scheme only works if the user can trust the client, which may be an issue in the case of a 'random' javascript hosted matrix client, though this is out of scope for this MSC. From 9e91cf3405c66081bece180632ebe54a676f06b6 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Wed, 30 Jun 2021 22:29:25 +0200 Subject: [PATCH 07/21] work out password change --- proposals/xxxx-apake_authentication.md | 46 +++++++++++++++++++++----- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/proposals/xxxx-apake_authentication.md b/proposals/xxxx-apake_authentication.md index 6517eaeed07..5c4c7551e23 100644 --- a/proposals/xxxx-apake_authentication.md +++ b/proposals/xxxx-apake_authentication.md @@ -10,9 +10,10 @@ Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. ### Registration flow -To allow clients to discover +To allow clients to discover the supported groups (and whether srp6a is supported) the client sends a GET request to /_matrix/client/r0/register + +`GET /_matrix/client/r0/register` -`GET /_matrix/client/r0/register` ``` { "auth_types": ["password", "srp6a"] @@ -32,7 +33,8 @@ Note that all values are calculated modulo N. This is then sent to the server, otherwise mimicking the password registration, through: -`POST /_matrix/client/r0/register?kind=user` +`POST /_matrix/client/r0/register?kind=user` + ``` { "auth": { @@ -55,20 +57,43 @@ The server stores the verifier, salt, and group next to the username. ### Convert from password to SRP -*I haven't thought this through, ideas are welcome. It should probably be something along the lines of a password change and otherwise more or less follow the registration as specced above.* +Mimicking the flow of register above, first a GET request is sent to check if SRP is supported and find the supported groups, here we'll reuse the register endpoint `GET /_matrix/client/r0/register`. *Or we could add a GET endpoint for /_matrix/client/r0/account/password* + +To convert to SRP we'll use the change password endpoint with the `"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. + +`POST /_matrix/client/r0/account/password HTTP/1.1` + +``` +{ + "auth_type": "srp6a", + "verifier": v, + "group": [g,N], + "salt": s, + "logout_devices": false, + "auth": { + "type": "example.type.foo", + "session": "xxxxx", + "example_credential": "verypoorsharedsecret" + } +} +``` + +The server then removes the old password (or old verifier, group, and salt) and stores the new values. ### Login flow To start the login flow the client sends it's username to obtain the salt and SRP group as: -`POST /_matrix/client/r0/login` +`POST /_matrix/client/r0/login` + ``` { "type": "m.login.srp6a.init", "username": "cheeky_monkey" } ``` -The server responds with the salt and SRP group: +The server responds with the salt and SRP group (looked up from the database), and public value `B`: + ``` { "prime": N, @@ -78,11 +103,12 @@ The server responds with the salt and SRP group: "auth_id": "12345" } ``` -Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user as supplied in the `POST`, B is the public server value, and auth_id is the id of this authentication flow, can by any unique random string. +Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user as supplied in the `POST`, B is the public server value, and auth_id is the id of this authentication flow, can by any unique random string, used for the server to keep track of the authentication flow. the server calculates B as: B = kv + g^b + where b is a private randomly generated value for this session (server side) and k is given as: k = H(N, g) @@ -111,13 +137,15 @@ Resulting in the shared session key K. To complete the authentication we need to prove to the server that the session key K is the same. *note that this proof is directly lifted from the [SRP spec](http://srp.stanford.edu/design.html), another proof can be possible as well.* + The client calculates: M1 = H(H(N) xor H(g), H(I), s, A, B, K) -The client will then respond with +The client will then respond with: + +`POST /_matrix/client/r0/login` -`POST /_matrix/client/r0/login` ``` { "type": "m.login.srp6a.verify", From 472fc9c762e9dba484eec43d359bacfc441ac7c7 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Fri, 2 Jul 2021 17:43:15 +0200 Subject: [PATCH 08/21] rename file and wrap lines --- ...cation.md => 3262-apake_authentication.md} | 64 ++++++++++++++----- 1 file changed, 47 insertions(+), 17 deletions(-) rename proposals/{xxxx-apake_authentication.md => 3262-apake_authentication.md} (58%) diff --git a/proposals/xxxx-apake_authentication.md b/proposals/3262-apake_authentication.md similarity index 58% rename from proposals/xxxx-apake_authentication.md rename to proposals/3262-apake_authentication.md index 5c4c7551e23..c3ea669cb56 100644 --- a/proposals/xxxx-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -1,8 +1,23 @@ -# \[WIP]MSCXXXX: aPAKE authentication - -Like most password authentication, matrix's [login](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-login) requires sending the password in plain text (though usually encrypted in transit with https). This requirement has as (obvious) downside that a man in the middle attack would allow reading the password, but also requires that the server has temporary access to the plaintext password (which will subsequently be hashed before storage). - -A Password Authenticated Key Exchange (PAKE) can prevent the need for sending the password in plaintext for login, and an aPAKE (asymmetric or augmented) allows for safe authentication without the server ever needing access to the plaintext password. OPAQUE is a modern implementation of an aPAKE that is [currently an ietf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-opaque-05), but as it's still pending and doesn't have many open source implementations using it would require implementing it ourselves. As such choosing to use SRP ([rfc2945](https://datatracker.ietf.org/doc/html/rfc2945)) makes more sense. SRP has as downside that the salt is transmitted to the client before authentication, allowing a precomputation attack which would speed up a followup attack after a server compromise. However, should a server be compromised, it is probably simpler to generate an access token and impersonate the target that way. +# \[WIP]MSC3262: aPAKE authentication + +Like most password authentication, matrix's +[login](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-login) +requires sending the password in plain text (though usually encrypted in transit with https). +This requirement has as (obvious) downside that a man in the middle attack would allow reading the password, +but also requires that the server has temporary access to the plaintext password +(which will subsequently be hashed before storage). + +A Password Authenticated Key Exchange (PAKE) can prevent the need for sending the password in plaintext for login, +and an aPAKE (asymmetric or augmented) allows for safe authentication without the server ever needing +access to the plaintext password. OPAQUE is a modern implementation of an aPAKE that is +[currently an ietf draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-opaque-05), +but as it's still pending and doesn't have many open source implementations using it would +require implementing it ourselves. As such choosing to use SRP +([rfc2945](https://datatracker.ietf.org/doc/html/rfc2945)) makes more sense. +SRP has as downside that the salt is transmitted to the client before authentication, +allowing a precomputation attack which would speed up a followup attack after a server compromise. +However, should a server be compromised, +it is probably simpler to generate an access token and impersonate the target that way. ## Proposal @@ -10,7 +25,8 @@ Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. ### Registration flow -To allow clients to discover the supported groups (and whether srp6a is supported) the client sends a GET request to /_matrix/client/r0/register +To allow clients to discover the supported groups (and whether srp6a is supported) +the client sends a GET request to /_matrix/client/r0/register `GET /_matrix/client/r0/register` @@ -20,7 +36,10 @@ To allow clients to discover the supported groups (and whether srp6a is supporte "srp_groups": [supported groups] } ``` -Here the server sends it's supported authentication types (in this case only password and srp6a) and if applicable it sends the supported SRP groups, as specified by the [SRP specification](https://datatracker.ietf.org/doc/html/rfc5054#page-16) or [rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). +Here the server sends it's supported authentication types (in this case only password and srp6a) +and if applicable it sends the supported SRP groups, as specified by the +[SRP specification](https://datatracker.ietf.org/doc/html/rfc5054#page-16) or +[rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). The client then chooses an srp group and generates a random salt `s`. The client then calculates the verifier `v` as: @@ -57,9 +76,12 @@ The server stores the verifier, salt, and group next to the username. ### Convert from password to SRP -Mimicking the flow of register above, first a GET request is sent to check if SRP is supported and find the supported groups, here we'll reuse the register endpoint `GET /_matrix/client/r0/register`. *Or we could add a GET endpoint for /_matrix/client/r0/account/password* +Mimicking the flow of register above, first a GET request is sent to check if SRP is +supported and find the supported groups, here we'll reuse the register endpoint +`GET /_matrix/client/r0/register`. *Or we could add a GET endpoint for /_matrix/client/r0/account/password* -To convert to SRP we'll use the change password endpoint with the `"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. +To convert to SRP we'll use the change password endpoint with the +`"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. `POST /_matrix/client/r0/account/password HTTP/1.1` @@ -103,7 +125,9 @@ The server responds with the salt and SRP group (looked up from the database), a "auth_id": "12345" } ``` -Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user as supplied in the `POST`, B is the public server value, and auth_id is the id of this authentication flow, can by any unique random string, used for the server to keep track of the authentication flow. +Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user +as supplied in the `POST`, B is the public server value, and auth_id is the id of this authentication flow, +can by any unique random string, used for the server to keep track of the authentication flow. the server calculates B as: @@ -136,7 +160,8 @@ The server calculates: Resulting in the shared session key K. To complete the authentication we need to prove to the server that the session key K is the same. -*note that this proof is directly lifted from the [SRP spec](http://srp.stanford.edu/design.html), another proof can be possible as well.* +*note that this proof is directly lifted from the [SRP spec](http://srp.stanford.edu/design.html), +another proof can be possible as well.* The client calculates: @@ -186,21 +211,26 @@ The client verifies that M2 matches, and is subsequently logged in. Adding this authentication method requires client developers to implement this in all matrix clients. SRP is vulnerable to precomputation attacks and it is incompatible elliptic-curve cryptography. -Matthew Green judges it as ["It’s not ideal, but it’s real." and "not obviously broken"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/) and it's a fairly old protocol. +Matthew Green judges it as +["It’s not ideal, but it’s real." and "not obviously broken"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/) +and it's a fairly old protocol. ## Alternatives -OPAQUE is the more modern protocol, which has the added benefit of not sending the salt in plain text to the client, but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curves. +OPAQUE is the more modern protocol, which has the added benefit of not sending the salt in plain text to the client, +but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curves. -*Bitwardens scheme can be mentioned here as well, since it does allow auth without the server learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash of the password before sending the hash to the server for auth, practically making the hash the new password, and as such it doesn't protect against a mitm.* +*Bitwardens scheme can be mentioned here as well, since it does allow auth without the server +learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash +of the password before sending the hash to the server for auth, practically making the hash +the new password, and as such it doesn't protect against a mitm.* ## Security considerations *Probably loads, though SRP and OPAQUE have had lots of eyes, I would assume.* -This whole scheme only works if the user can trust the client, which may be an issue in the case of a 'random' javascript hosted matrix client, though this is out of scope for this MSC. +This whole scheme only works if the user can trust the client, which may be an issue +in the case of a 'random' javascript hosted matrix client, though this is out of scope for this MSC. ## Unstable prefix - - From 7a9f20ccb3fdab2f35d8d17f537ebd5c8ef3065a Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Wed, 7 Jul 2021 08:54:36 +0200 Subject: [PATCH 09/21] Link to the password change spec Co-authored-by: Kevin Cox --- proposals/3262-apake_authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index c3ea669cb56..051c1e455a1 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -80,7 +80,7 @@ Mimicking the flow of register above, first a GET request is sent to check if SR supported and find the supported groups, here we'll reuse the register endpoint `GET /_matrix/client/r0/register`. *Or we could add a GET endpoint for /_matrix/client/r0/account/password* -To convert to SRP we'll use the change password endpoint with the +To convert to SRP we'll use the [change password endpoint](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password) with the `"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. `POST /_matrix/client/r0/account/password HTTP/1.1` From 7e337251d84e677b826275eaa0461f2ebf112d93 Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Wed, 7 Jul 2021 12:18:55 +0200 Subject: [PATCH 10/21] thoughts about untrusted servers, security considerations and better explanations --- proposals/3262-apake_authentication.md | 68 ++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index c3ea669cb56..01fdfa1543f 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -26,7 +26,7 @@ Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. ### Registration flow To allow clients to discover the supported groups (and whether srp6a is supported) -the client sends a GET request to /_matrix/client/r0/register +the client sends a GET request. `GET /_matrix/client/r0/register` @@ -47,8 +47,9 @@ The client then calculates the verifier `v` as: x = H(s, p) v = g^x -Here H() is a secure hash function, and p is the user specified password. -Note that all values are calculated modulo N. +Here H() is a secure hash function, and p is the user specified password, +and `g` is the generator of the selected srp group. +Note that all values are calculated modulo N (of the selected srp group). This is then sent to the server, otherwise mimicking the password registration, through: @@ -104,7 +105,20 @@ The server then removes the old password (or old verifier, group, and salt) and ### Login flow -To start the login flow the client sends it's username to obtain the salt and SRP group as: +To start the login flow the client sends a GET request to `/_matrix/client/r0/login`, the server responds with: + +``` +{ + "flows": [ + { + "type": "m.login.srp6a" + } + ] +} +``` +(and any other supported login flows) + +Next the client sends it's username to obtain the salt and SRP group as: `POST /_matrix/client/r0/login` @@ -114,6 +128,18 @@ To start the login flow the client sends it's username to obtain the salt and SR "username": "cheeky_monkey" } ``` + +If the user hasn't registered with SRP the server responds with: + +`Status code: 403` + +``` +{ + "errcode": "M_UNAUTHORIZED", + "error": "User has not registered with SRP." +} +``` + The server responds with the salt and SRP group (looked up from the database), and public value `B`: ``` @@ -210,6 +236,19 @@ The client verifies that M2 matches, and is subsequently logged in. Adding this authentication method requires client developers to implement this in all matrix clients. +As long as the plain password login remains available it is not trivial to allow clients to +assume the server never learns the password, since an evil server can say to a new client that it +doesn't support SRP and learn the password that way. And with the client not being authenticated +before login there's no easy way for the client to know whether the user expects SRP to be used. +The long-term solution to this is to declare the old password authentication as deprecated and +have all matrix clients refuse to login with `m.password`. This may take a long time; "the best +moment to plant a tree is 10 years ago, the second best is today." +The short term solution requires a good UX in clients to notify the user that they expect to +login with SRP but the server doesn't support this. +*My initial thought was to look at whether SSSS is set for the user, or even try to decrypt it* +*with the given password and give a warning if this works, but the server can just lie to the* +*client about that, leaving the user none the wiser.* + SRP is vulnerable to precomputation attacks and it is incompatible elliptic-curve cryptography. Matthew Green judges it as ["It’s not ideal, but it’s real." and "not obviously broken"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/) @@ -225,12 +264,31 @@ learning the plaintext password, but it isn't a PAKE, but rather something along of the password before sending the hash to the server for auth, practically making the hash the new password, and as such it doesn't protect against a mitm.* +The UIA-flow can map pretty well on the proposed login flow and would remove the need for `m.login.srp6a.init`. +This is deemed out of scope for this MSC, but would not be hard to change later. + ## Security considerations -*Probably loads, though SRP and OPAQUE have had lots of eyes, I would assume.* +SRP uses the discreet logarithm problem, deemed unsolved, though Shor's algorithm can become an +issue when quantum computers get scaled up. Work seems to have been done to create a quantum-safe +SPR version, see [here](https://eprint.iacr.org/2017/1196.pdf), though I lack the credentials to +determine whether or not this holds water. + This whole scheme only works if the user can trust the client, which may be an issue in the case of a 'random' javascript hosted matrix client, though this is out of scope for this MSC. +## Outlook +This MSC allows for authentication without the server learning the password, and authenticating +both the server and the client to each other. This may prevent a few security issues, such as +a CDN learning the users password. The main reason for this MSC is that later this may allow +the reuse of the same password for both authentication and SSSS encryption while remaining secure. + ## Unstable prefix +The following mapping will be used for identifiers in this MSC during development: + + +Proposed final identifier | Purpose | Development identifier +------------------------------- | ------- | ---- +`m.login.srp6a.*` | login type | `com.vgorcum.msc3262.login.srp6a.*` From a7fecfacd7235f2d42a69929fd40e769294892f2 Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Wed, 14 Jul 2021 07:36:18 +0200 Subject: [PATCH 11/21] Update proposals/3262-apake_authentication.md Co-authored-by: Matthew Hodgson --- proposals/3262-apake_authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 82ed82b474e..3a9c300760e 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -249,7 +249,7 @@ login with SRP but the server doesn't support this. *with the given password and give a warning if this works, but the server can just lie to the* *client about that, leaving the user none the wiser.* -SRP is vulnerable to precomputation attacks and it is incompatible elliptic-curve cryptography. +SRP is vulnerable to precomputation attacks and it is incompatible with elliptic-curve cryptography. Matthew Green judges it as ["It’s not ideal, but it’s real." and "not obviously broken"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/) and it's a fairly old protocol. From 71036d6c40034ae1aaed0894b063a2084288cba9 Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Wed, 14 Jul 2021 07:36:27 +0200 Subject: [PATCH 12/21] Update proposals/3262-apake_authentication.md Co-authored-by: Matthew Hodgson --- proposals/3262-apake_authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 3a9c300760e..3ef153a93d6 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -270,7 +270,7 @@ This is deemed out of scope for this MSC, but would not be hard to change later. ## Security considerations -SRP uses the discreet logarithm problem, deemed unsolved, though Shor's algorithm can become an +SRP uses the discrete logarithm problem, deemed unsolved, though Shor's algorithm can become an issue when quantum computers get scaled up. Work seems to have been done to create a quantum-safe SPR version, see [here](https://eprint.iacr.org/2017/1196.pdf), though I lack the credentials to determine whether or not this holds water. From 51144abd1aa42c1c21486e4c49030f967cbee52d Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Wed, 14 Jul 2021 07:36:34 +0200 Subject: [PATCH 13/21] Update proposals/3262-apake_authentication.md Co-authored-by: Matthew Hodgson --- proposals/3262-apake_authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 3ef153a93d6..89d6c45162c 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -272,7 +272,7 @@ This is deemed out of scope for this MSC, but would not be hard to change later. SRP uses the discrete logarithm problem, deemed unsolved, though Shor's algorithm can become an issue when quantum computers get scaled up. Work seems to have been done to create a quantum-safe -SPR version, see [here](https://eprint.iacr.org/2017/1196.pdf), though I lack the credentials to +SRP version, see [here](https://eprint.iacr.org/2017/1196.pdf), though I lack the credentials to determine whether or not this holds water. From 3ba3262aff66c11cc0e41746f587c482141848e6 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Sat, 25 Sep 2021 13:46:05 +0200 Subject: [PATCH 14/21] add reference to SCRAM --- proposals/3262-apake_authentication.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 89d6c45162c..8692370d60b 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -41,7 +41,7 @@ and if applicable it sends the supported SRP groups, as specified by the [SRP specification](https://datatracker.ietf.org/doc/html/rfc5054#page-16) or [rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). -The client then chooses an srp group and generates a random salt `s`. +The client then chooses an srp group from the SRP specification and generates a random salt `s`. The client then calculates the verifier `v` as: x = H(s, p) @@ -259,6 +259,8 @@ and it's a fairly old protocol. OPAQUE is the more modern protocol, which has the added benefit of not sending the salt in plain text to the client, but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curves. +[SCRAM](https://www.isode.com/whitepapers/scram.html) serves a similar purpose and is used by (amongst others) XMPP. SRP seems superior here because it does not store enough information server-side to make a valid authentication against the server in case the database is somehow leaked without the server being otherwise compromised. + *Bitwardens scheme can be mentioned here as well, since it does allow auth without the server learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash of the password before sending the hash to the server for auth, practically making the hash From 8aabf4cd6c352fefe8f252e8f01b31facf9d0e45 Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Tue, 28 Sep 2021 00:49:10 +0200 Subject: [PATCH 15/21] add configurable hash function --- proposals/3262-apake_authentication.md | 55 +++++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 8692370d60b..53e58c9637e 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -32,22 +32,25 @@ the client sends a GET request. ``` { - "auth_types": ["password", "srp6a"] - "srp_groups": [supported groups] + "auth_types": ["password", "srp6a"], + "srp_groups": [supported groups], + "hash": [supported hashes] } ``` Here the server sends it's supported authentication types (in this case only password and srp6a) and if applicable it sends the supported SRP groups, as specified by the [SRP specification](https://datatracker.ietf.org/doc/html/rfc5054#page-16) or -[rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). +[rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). The supported hashes are a list of supported hashes +by the server. Initially for this MSC we suggest supporting pbkdf2 and bcrypt, though this may change over time. -The client then chooses an srp group from the SRP specification and generates a random salt `s`. +The client then chooses an srp group from the SRP specification, +and a hash function from the supported list and generates a random salt `s`. The client then calculates the verifier `v` as: x = H(s, p) v = g^x -Here H() is a secure hash function, and p is the user specified password, +Here H() is the chose secure hash function, and p is the user specified password, and `g` is the generator of the selected srp group. Note that all values are calculated modulo N (of the selected srp group). @@ -58,14 +61,16 @@ This is then sent to the server, otherwise mimicking the password registration, ``` { "auth": { - "type": "example.type.foo", + "type": "m.login.srp6a", "session": "xxxxx", "example_credential": "verypoorsharedsecret" }, "auth_type": "srp6a", "username": "cheeky_monkey", "verifier": v, - "group": [g,N], + "group": "selected group", + "hash": "H", + "hash_iterations": i, "salt": s, "device_id": "GHTYAJCE", "initial_device_display_name": "Jungle Phone", @@ -73,7 +78,7 @@ This is then sent to the server, otherwise mimicking the password registration, } ``` -The server stores the verifier, salt, and group next to the username. +The server stores the verifier, salt, hash function, hash iterations, and group next to the username. ### Convert from password to SRP @@ -90,7 +95,9 @@ To convert to SRP we'll use the [change password endpoint](https://matrix.org/do { "auth_type": "srp6a", "verifier": v, - "group": [g,N], + "group": "selected group", + "hash": "H", + "hash_iterations": i, "salt": s, "logout_devices": false, "auth": { @@ -144,16 +151,19 @@ The server responds with the salt and SRP group (looked up from the database), a ``` { - "prime": N, - "generator": g, + "group": "selected group", + "hash": "H", + "hash_iterations": i, "salt": s, "server_value": B, "auth_id": "12345" } ``` -Here N is the prime and g is the generator of the SRP group. s is the stored salt for the user -as supplied in the `POST`, B is the public server value, and auth_id is the id of this authentication flow, -can by any unique random string, used for the server to keep track of the authentication flow. +The client looks up N (the prime) and g (the generator) of the selected SRP group as sent by the server, +`H` is the has algorithm used with `i` iterations. +s is the stored salt for the user as supplied during registration, B is the public server value, +and auth_id is the id of this authentication flow, can be any unique random string, +used for the server to keep track of the authentication flow. the server calculates B as: @@ -259,7 +269,9 @@ and it's a fairly old protocol. OPAQUE is the more modern protocol, which has the added benefit of not sending the salt in plain text to the client, but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curves. -[SCRAM](https://www.isode.com/whitepapers/scram.html) serves a similar purpose and is used by (amongst others) XMPP. SRP seems superior here because it does not store enough information server-side to make a valid authentication against the server in case the database is somehow leaked without the server being otherwise compromised. +[SCRAM](https://www.isode.com/whitepapers/scram.html) serves a similar purpose and is used by (amongst others) XMPP. +SRP seems superior here because it does not store enough information server-side to make a valid authentication +against the server in case the database is somehow leaked without the server being otherwise compromised. *Bitwardens scheme can be mentioned here as well, since it does allow auth without the server learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash @@ -287,6 +299,19 @@ both the server and the client to each other. This may prevent a few security is a CDN learning the users password. The main reason for this MSC is that later this may allow the reuse of the same password for both authentication and SSSS encryption while remaining secure. +Phasing out the old plain password login will take time, as older clients may still immediately +`POST` the plaintext password to `/login` invisibly to the end user, without any checks. +The server should then just respond with a `HTTP Status code 403`: + +``` +{ + "errcode": "M_UNAUTHORIZED", + "error": "Password authentication is not supported." +} +``` + +But then the server still has access to the plain text password. + ## Unstable prefix The following mapping will be used for identifiers in this MSC during development: From decfda053a44c2fbb5ab3cd982aaf169a55d8cde Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Tue, 28 Sep 2021 00:55:30 +0200 Subject: [PATCH 16/21] fixed example.foo --- proposals/3262-apake_authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 53e58c9637e..5c0a49bfb2b 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -101,7 +101,7 @@ To convert to SRP we'll use the [change password endpoint](https://matrix.org/do "salt": s, "logout_devices": false, "auth": { - "type": "example.type.foo", + "type": "m.login.srp6a", "session": "xxxxx", "example_credential": "verypoorsharedsecret" } From df2180347911b4cdbcd19ca8f5c41d9cc0faafe0 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Tue, 28 Sep 2021 19:21:54 +0200 Subject: [PATCH 17/21] cleanups to prepare for review --- proposals/3262-apake_authentication.md | 28 +++++++++----------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 5c0a49bfb2b..af0017991dd 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -33,24 +33,26 @@ the client sends a GET request. ``` { "auth_types": ["password", "srp6a"], - "srp_groups": [supported groups], - "hash": [supported hashes] + "srp": { + "groups": [supported groups], + "hash": [supported hashes] + } } ``` -Here the server sends it's supported authentication types (in this case only password and srp6a) +Here the server sends it's supported authentication types (in this example only password and srp6a) and if applicable it sends the supported SRP groups, as specified by the [SRP specification](https://datatracker.ietf.org/doc/html/rfc5054#page-16) or [rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). The supported hashes are a list of supported hashes by the server. Initially for this MSC we suggest supporting pbkdf2 and bcrypt, though this may change over time. -The client then chooses an srp group from the SRP specification, +The client then chooses a supported srp group from the SRP specification, and a hash function from the supported list and generates a random salt `s`. The client then calculates the verifier `v` as: x = H(s, p) v = g^x -Here H() is the chose secure hash function, and p is the user specified password, +Here H() is the chosen secure hash function, p is the user specified password, and `g` is the generator of the selected srp group. Note that all values are calculated modulo N (of the selected srp group). @@ -83,8 +85,8 @@ The server stores the verifier, salt, hash function, hash iterations, and group ### Convert from password to SRP Mimicking the flow of register above, first a GET request is sent to check if SRP is -supported and find the supported groups, here we'll reuse the register endpoint -`GET /_matrix/client/r0/register`. *Or we could add a GET endpoint for /_matrix/client/r0/account/password* +supported and find the supported groups, and hash functions here we'll reuse the register endpoint +`GET /_matrix/client/r0/register`. To convert to SRP we'll use the [change password endpoint](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password) with the `"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. @@ -108,7 +110,7 @@ To convert to SRP we'll use the [change password endpoint](https://matrix.org/do } ``` -The server then removes the old password (or old verifier, group, and salt) and stores the new values. +The server then removes the old password (or old verifier, hash function, hash iterations, group, and salt) and stores the new values. ### Login flow @@ -196,8 +198,6 @@ The server calculates: Resulting in the shared session key K. To complete the authentication we need to prove to the server that the session key K is the same. -*note that this proof is directly lifted from the [SRP spec](http://srp.stanford.edu/design.html), -another proof can be possible as well.* The client calculates: @@ -255,9 +255,6 @@ have all matrix clients refuse to login with `m.password`. This may take a long moment to plant a tree is 10 years ago, the second best is today." The short term solution requires a good UX in clients to notify the user that they expect to login with SRP but the server doesn't support this. -*My initial thought was to look at whether SSSS is set for the user, or even try to decrypt it* -*with the given password and give a warning if this works, but the server can just lie to the* -*client about that, leaving the user none the wiser.* SRP is vulnerable to precomputation attacks and it is incompatible with elliptic-curve cryptography. Matthew Green judges it as @@ -273,11 +270,6 @@ but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curve SRP seems superior here because it does not store enough information server-side to make a valid authentication against the server in case the database is somehow leaked without the server being otherwise compromised. -*Bitwardens scheme can be mentioned here as well, since it does allow auth without the server -learning the plaintext password, but it isn't a PAKE, but rather something along the lines of a hash -of the password before sending the hash to the server for auth, practically making the hash -the new password, and as such it doesn't protect against a mitm.* - The UIA-flow can map pretty well on the proposed login flow and would remove the need for `m.login.srp6a.init`. This is deemed out of scope for this MSC, but would not be hard to change later. From eab94a10d1c8f6a6300111513fb37c6ceb221654 Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Thu, 30 Sep 2021 00:23:09 +0200 Subject: [PATCH 18/21] add UIA flow, define names for SRP groups --- proposals/3262-apake_authentication.md | 162 ++++++++++++++++++++++--- 1 file changed, 148 insertions(+), 14 deletions(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index af0017991dd..666f4402c1b 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -42,7 +42,17 @@ the client sends a GET request. Here the server sends it's supported authentication types (in this example only password and srp6a) and if applicable it sends the supported SRP groups, as specified by the [SRP specification](https://datatracker.ietf.org/doc/html/rfc5054#page-16) or -[rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). The supported hashes are a list of supported hashes +[rfc3526](https://datatracker.ietf.org/doc/html/rfc3526). +The groups are referenced by name as: + +``` +["1536","2048","3072","4096","6144","8192","1536MODP","2048MODP", +"3072MODP","4096MODP","6144MODP","8192MODP"] +``` + +Note that the ``"1024"` is explicitly excluded as it is now too small to provide practical security. + +The supported hashes are a list of supported hashes by the server. Initially for this MSC we suggest supporting pbkdf2 and bcrypt, though this may change over time. The client then chooses a supported srp group from the SRP specification, @@ -56,7 +66,7 @@ Here H() is the chosen secure hash function, p is the user specified password, and `g` is the generator of the selected srp group. Note that all values are calculated modulo N (of the selected srp group). -This is then sent to the server, otherwise mimicking the password registration, through: +This is then sent (base64 encoded) to the server, otherwise mimicking the password registration, through: `POST /_matrix/client/r0/register?kind=user` @@ -83,13 +93,13 @@ This is then sent to the server, otherwise mimicking the password registration, The server stores the verifier, salt, hash function, hash iterations, and group next to the username. ### Convert from password to SRP - + Mimicking the flow of register above, first a GET request is sent to check if SRP is supported and find the supported groups, and hash functions here we'll reuse the register endpoint `GET /_matrix/client/r0/register`. -To convert to SRP we'll use the [change password endpoint](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password) with the -`"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. +To convert to SRP we'll use the [change password endpoint](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password) +with the `"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. `POST /_matrix/client/r0/account/password HTTP/1.1` @@ -127,7 +137,7 @@ To start the login flow the client sends a GET request to `/_matrix/client/r0/lo ``` (and any other supported login flows) -Next the client sends it's username to obtain the salt and SRP group as: +Next the client sends its username to obtain the salt and SRP group as: `POST /_matrix/client/r0/login` @@ -158,13 +168,13 @@ The server responds with the salt and SRP group (looked up from the database), a "hash_iterations": i, "salt": s, "server_value": B, - "auth_id": "12345" + "session": "12345" } ``` The client looks up N (the prime) and g (the generator) of the selected SRP group as sent by the server, `H` is the has algorithm used with `i` iterations. s is the stored salt for the user as supplied during registration, B is the public server value, -and auth_id is the id of this authentication flow, can be any unique random string, +and `session` is the session id of this authentication flow, can be any unique random string, used for the server to keep track of the authentication flow. the server calculates B as: @@ -212,13 +222,13 @@ The client will then respond with: "type": "m.login.srp6a.verify", "evidence_message": M1, "client_value": A, - "auth_id": "12345" + "session": "12345" } ``` -Here `M1` is the client evidence message, and A is the public client value. +Here `M1` is the (base64 encoded) client evidence message, and A is the public client value. Upon successful authentication (ie M1 matches) the server will respond with the regular login success status code 200: -To prove the identity of the server to the client we can send back M2 as: +To prove the identity of the server to the client we can send back M2 (base64 encoded) as: M2 = H(A, M, K) @@ -241,6 +251,131 @@ To prove the identity of the server to the client we can send back M2 as: The client verifies that M2 matches, and is subsequently logged in. +### UIA flow + +The client starts the UIA flow by sending a post without the `auth` object, to which the server reponds with: + +``` +HTTP/1.1 401 Unauthorized +Content-Type: application/json + +{ + "flows": [ + { + "stages": [ "m.login.srp6a.init", "m.auth.login.verify" ] + } + ], + "session": "12345" +} +``` + +The client then sends its username to obtain the salt and SRP group as: + +`POST` + +``` +auth: { + "type": "m.login.srp6a.init", + "username": "cheeky_monkey" + "session": "12345" + } +``` + +If the user hasn't registered with SRP the server responds with: + +`Status code: 403` + +``` +auth: { + "errcode": "M_UNAUTHORIZED", + "error": "User has not registered with SRP." + } +``` + +The server responds with the salt and SRP group (looked up from the database), and public value `B`: + +``` +auth: { + "group": "selected group", + "hash": "H", + "hash_iterations": i, + "salt": s, + "server_value": B, + "session": "12345" + } +``` +The client looks up N (the prime) and g (the generator) of the selected SRP group as sent by the server, +`H` is the has algorithm used with `i` iterations. +s is the stored salt for the user as supplied during registration, B is the public server value, +and `session` is the session id of this authentication flow, can be any unique random string, +used for the server to keep track of the authentication flow. + +the server calculates B as: + + B = kv + g^b + +where b is a private randomly generated value for this session (server side) and k is given as: + + k = H(N, g) + +The client then calculates: + + A = g^a +where a is a private randomly generated value for this session (client side). + +Both then calculate: + + u = H(A, B) + +Next the client calculates: + + x = H(s, p) + S = (B - kg^x) ^ (a + ux) + K = H(S) + +The server calculates: + + S = (Av^u) ^ b + K = H(S) + +Resulting in the shared session key K. + +To complete the authentication we need to prove to the server that the session key K is the same. + +The client calculates: + + M1 = H(H(N) xor H(g), H(I), s, A, B, K) + +The client will then respond with: + +`POST` + +``` +auth: { + "type": "m.login.srp6a.verify", + "evidence_message": M1, + "client_value": A, + "session": "12345" + } +``` +Here `M1` is the (base64 encoded) client evidence message, and A is the public client value. +Upon successful authentication (ie M1 matches) the server will respond with the regular login success status code 200: + +To prove the identity of the server to the client we can send back M2 (base64 encoded) as: + + M2 = H(A, M, K) + +``` +{ + "evidence_message": M2 +} +``` + +Along with any other data expected returned by the API endpoint. + +The client verifies that M2 matches, and is subsequently authenticated. + + ## Potential issues @@ -270,9 +405,8 @@ but rather uses an 'Oblivious Pseudo-Random Function' and can use elliptic curve SRP seems superior here because it does not store enough information server-side to make a valid authentication against the server in case the database is somehow leaked without the server being otherwise compromised. -The UIA-flow can map pretty well on the proposed login flow and would remove the need for `m.login.srp6a.init`. -This is deemed out of scope for this MSC, but would not be hard to change later. - +The UIA `/login` flow as proposed by [MSC2835](https://github.com/matrix-org/matrix-doc/pull/2835) would basically remove +the whole `/login` part of this MSC, making it a lot simpler. ## Security considerations From 6765a68ad85ee43cca2839dbb118cdb0158a727b Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Thu, 30 Sep 2021 01:26:26 +0200 Subject: [PATCH 19/21] more UIA additions --- proposals/3262-apake_authentication.md | 43 ++++++++++++++++---------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index 666f4402c1b..f73656c8f55 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -25,18 +25,30 @@ Add support for the SRP 6a login flow, as `"type": "m.login.srp6a"`. ### Registration flow -To allow clients to discover the supported groups (and whether srp6a is supported) -the client sends a GET request. +Registration follows the UIA flow, which means the client starts with sending a `POST` +with the requested username to `` +this allow clients to discover the supported groups (and whether srp6a is supported). -`GET /_matrix/client/r0/register` +`POST /_matrix/client/r0/register` +``` +{ + username: "cheeky_monkey" +} +``` + +To which the server reponds with: ``` +HTTP/1.1 401 Unauthorized +Content-Type: application/json + { - "auth_types": ["password", "srp6a"], - "srp": { - "groups": [supported groups], - "hash": [supported hashes] - } + "flows": [ + { + "stages": [ "m.login.srp6a.init", "m.auth.login.verify" ] + } + ], + "session": "xxxxx" } ``` Here the server sends it's supported authentication types (in this example only password and srp6a) @@ -93,19 +105,18 @@ This is then sent (base64 encoded) to the server, otherwise mimicking the passwo The server stores the verifier, salt, hash function, hash iterations, and group next to the username. ### Convert from password to SRP - -Mimicking the flow of register above, first a GET request is sent to check if SRP is -supported and find the supported groups, and hash functions here we'll reuse the register endpoint -`GET /_matrix/client/r0/register`. -To convert to SRP we'll use the [change password endpoint](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password) -with the `"auth_type": "srp6a"` added, and the required `verifier`, `group`, and `salt`. +To convert to SRP we'll use the [change password endpoint](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password). +This uses the UIA flow to authenticate which as the first step return the flows, with srp support as defined in register above. + +After the initial UIA the last step is to send the new credentials to be stored with +`"auth_type": "m.login.srp6a"` added, and the required `verifier`, `group`, and `salt`. `POST /_matrix/client/r0/account/password HTTP/1.1` ``` { - "auth_type": "srp6a", + "auth_type": "m.login.srp6a", "verifier": v, "group": "selected group", "hash": "H", @@ -113,7 +124,7 @@ with the `"auth_type": "srp6a"` added, and the required `verifier`, `group`, and "salt": s, "logout_devices": false, "auth": { - "type": "m.login.srp6a", + "type": "m.login.password", "session": "xxxxx", "example_credential": "verypoorsharedsecret" } From 0da7675edc63bb39e17f06efd301b2d739cbdc4d Mon Sep 17 00:00:00 2001 From: Mathijs van Gorcum Date: Thu, 30 Sep 2021 01:42:46 +0200 Subject: [PATCH 20/21] more UIA fixes --- proposals/3262-apake_authentication.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index f73656c8f55..b09595e465e 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -45,10 +45,16 @@ Content-Type: application/json { "flows": [ { - "stages": [ "m.login.srp6a.init", "m.auth.login.verify" ] + "stages": [ "m.login.srp6a.register"] } ], - "session": "xxxxx" + "session": "xxxxx", + "params": { + "m.login.srp6a.register": { + "groups": [supported groups], + "hash": [supported hashes] + } + } } ``` Here the server sends it's supported authentication types (in this example only password and srp6a) @@ -273,7 +279,7 @@ Content-Type: application/json { "flows": [ { - "stages": [ "m.login.srp6a.init", "m.auth.login.verify" ] + "stages": [ "m.login.srp6a.init", "m.login.srp6a.verify" ] } ], "session": "12345" From f9d17d6ff4e8172cb38b8a8a02e484b0794d13a3 Mon Sep 17 00:00:00 2001 From: mvgorcum Date: Sat, 2 Oct 2021 21:27:29 +0200 Subject: [PATCH 21/21] differentiate between password hash and hash --- proposals/3262-apake_authentication.md | 65 ++++++++++++++++---------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/proposals/3262-apake_authentication.md b/proposals/3262-apake_authentication.md index b09595e465e..f1e33549529 100644 --- a/proposals/3262-apake_authentication.md +++ b/proposals/3262-apake_authentication.md @@ -52,9 +52,10 @@ Content-Type: application/json "params": { "m.login.srp6a.register": { "groups": [supported groups], + "passwordhash": [supported hashes], "hash": [supported hashes] } - } + } } ``` Here the server sends it's supported authentication types (in this example only password and srp6a) @@ -64,23 +65,24 @@ and if applicable it sends the supported SRP groups, as specified by the The groups are referenced by name as: ``` -["1536","2048","3072","4096","6144","8192","1536MODP","2048MODP", -"3072MODP","4096MODP","6144MODP","8192MODP"] +["2048","3072","4096","6144","8192","1536MODP","2048MODP","3072MODP","4096MODP","6144MODP","8192MODP"] ``` -Note that the ``"1024"` is explicitly excluded as it is now too small to provide practical security. +Note that the `"1024"` and `"1536"` are explicitly excluded as they are now too small to provide practical security. -The supported hashes are a list of supported hashes -by the server. Initially for this MSC we suggest supporting pbkdf2 and bcrypt, though this may change over time. +The supported password hashes are a list of supported password hashes by the server, referred to as `PH()` throughout this MSC. +Initially for this MSC we suggest supporting pbkdf2 and bcrypt, though this may change over time. +The supported hashes are the hashfunctions used for all other hash calculations, referred to as `H()` throughout this MSC. +Initially we suggest at least supporting `SHA256` and `SHA512`. The client then chooses a supported srp group from the SRP specification, and a hash function from the supported list and generates a random salt `s`. The client then calculates the verifier `v` as: - x = H(s, p) + x = PH(p, s) v = g^x -Here H() is the chosen secure hash function, p is the user specified password, +Here PH() is the chosen secure password hash function, p is the user specified password, and `g` is the generator of the selected srp group. Note that all values are calculated modulo N (of the selected srp group). @@ -98,10 +100,13 @@ This is then sent (base64 encoded) to the server, otherwise mimicking the passwo "auth_type": "srp6a", "username": "cheeky_monkey", "verifier": v, - "group": "selected group", - "hash": "H", - "hash_iterations": i, "salt": s, + "params": { + "group": "selected group", + "passwordhash": "PH", + "hash_iterations": i, + "hash": "H" + }, "device_id": "GHTYAJCE", "initial_device_display_name": "Jungle Phone", "inhibit_login": false @@ -124,10 +129,13 @@ After the initial UIA the last step is to send the new credentials to be stored { "auth_type": "m.login.srp6a", "verifier": v, - "group": "selected group", - "hash": "H", - "hash_iterations": i, "salt": s, + "params": { + "group": "selected group", + "passwordhash": "PH", + "hash_iterations": i, + "hash": "H" + }, "logout_devices": false, "auth": { "type": "m.login.password", @@ -180,16 +188,19 @@ The server responds with the salt and SRP group (looked up from the database), a ``` { - "group": "selected group", - "hash": "H", - "hash_iterations": i, + "params": { + "group": "selected group", + "passwordhash": "PH", + "hash_iterations": i, + "hash": "H" + }, "salt": s, "server_value": B, "session": "12345" } ``` The client looks up N (the prime) and g (the generator) of the selected SRP group as sent by the server, -`H` is the has algorithm used with `i` iterations. +`PH` is the hash algorithm used with `i` iterations. s is the stored salt for the user as supplied during registration, B is the public server value, and `session` is the session id of this authentication flow, can be any unique random string, used for the server to keep track of the authentication flow. @@ -202,6 +213,7 @@ where b is a private randomly generated value for this session (server side) and k = H(N, g) +Here H() is a secure hash function The client then calculates: A = g^a @@ -213,7 +225,7 @@ Both then calculate: Next the client calculates: - x = H(s, p) + x = PH(p, s) S = (B - kg^x) ^ (a + ux) K = H(S) @@ -247,7 +259,7 @@ Upon successful authentication (ie M1 matches) the server will respond with the To prove the identity of the server to the client we can send back M2 (base64 encoded) as: - M2 = H(A, M, K) + M2 = H(A, M1, K) ``` { @@ -313,10 +325,13 @@ The server responds with the salt and SRP group (looked up from the database), a ``` auth: { - "group": "selected group", - "hash": "H", - "hash_iterations": i, "salt": s, + "params": { + "group": "selected group", + "passwordhash": "PH", + "hash_iterations": i, + "hash": "H" + }, "server_value": B, "session": "12345" } @@ -346,7 +361,7 @@ Both then calculate: Next the client calculates: - x = H(s, p) + x = PH(p, s) S = (B - kg^x) ^ (a + ux) K = H(S) @@ -380,7 +395,7 @@ Upon successful authentication (ie M1 matches) the server will respond with the To prove the identity of the server to the client we can send back M2 (base64 encoded) as: - M2 = H(A, M, K) + M2 = H(A, M1, K) ``` {