SASL (Simple Authentication and Security Layer) as used in XMPP is broken. In this blog post I’ll try to explain why and propose some fixes.
Update (2023-02-04): Since I originally wrote this blog post, I’ve had the ability to discuss several of my solutions with Dave (the original author of XEP-0388 dubbed SASL2) and MattJ (one of the authors of the prosody xmpp server) and others. Most, if not all of my issues are now addressed in a bunch of updates to existing XEPs as well as new XEPs:
- Update of XEP-0388 (SASL2): https://github.com/xsf/xeps/pull/1214
- SASL2 upgrade ProtoXEP: https://github.com/xsf/xeps/pull/1248
- New XEP for downgrade protection when using SCRAM: https://xmpp.org/extensions/xep-0474.html
- Already existing XEP that only now gets adopted by server developers: XEP-0440
The rest of this blog post remains as is and can be used for a deeper introduction into the material and as explanation of some of the rationale behind the SASL2 updates and my ProtoXEP.
A Modern Authentication Protocol
In my opinion a modern authentication protocol should have at least the following properties. Of course that can be subject to debate, but I think most of us will agree on the following list.
- allow for protocol agility (e.g. adding new authentication mechanisms, like adding new cipher suites in TLS)
- prevent downgrade attacks on authentication mechanisms (we don’t want an active attacker to be able to force us to use a weak mechanism)
- prevent storage of plaintext passwords on the server (that’s obvious: we don’t want a hacker to be able to steal our plaintext passwords once he hacked the database)
- prevent replay attacks (we don’t want a MITM (man in the middle) be able to steal a (possibly hashed) password and use it to authenticate herself)
- (possibly) prevent/detect MITM altogether
How SASL tries to fulfill these properties
First of all: XMPP Core mandates the use of TLS for everything, including authentication. Keep that in mind, when reading the rest of this blog post.
SASL as defined for XMPP allows the server to present a list of authentication methods. The client then picks the one having the highest perceived strength (see XMPP-Core 6.3.3) among the ones it implements. XEP-0438: Best practices for password hashing and storage lists some common authentication methods and how they should be ordered. Currently, the methods PLAIN (plaintext password), EXTERNAL (using client certificates to authenticate the user) and SCRAM (Salted Challenge Response Authentication Mechanism) are common.
(I will not talk about SASL EXTERNAL in this blog post, because it does not use passwords and seems to be super uncommon in the XMPP world.)
This generally allows adding new mechanisms in the future, protocol agility seems to be fulfilled, right?
Current mandatory SASL methods include SCRAM-SHA-1 and SCRAM-SHA-1-PLUS (if possible). SCRAM generally allows the server to store a salted hash instead of plaintext passwords.
Even if the client uses the PLAIN method, the server could store the password as salted hash.
And EXTERNAL usually does not use any form of password.
So that’s another property of our list that is fulfilled, right?
Using SCRAM it is even possible to prevent replay attacks because of the used challenge-response scheme. That’s even possible if no TLS channel is used.
Cool, another property that’s fulfilled by SCRAM as well, right?
SCRAM is even cooler because it allows for TLS channel-binding.
This allows both entities (client and server) taking part in the SCRAM authentication to bind the authentication to the TLS channel using a HMAC (Hash-Based Message Authentication Code) to sign a unique data blob tied to the TLS session in use.
If the TLS channel is intercepted by a MITM, the attacker would have to use two separate TLS channels, one to the server and one to the client.
Binding the authentication to the TLS channel allows the server and client to detect such an attacker and fail the authentication.
To indicate, that a server supports channel-binding, it appends the string
-PLUS to the advertised SCRAM methods.
If the client supports channel-binding, it picks
SCRAM-SHA-1-PLUS instead of
In case the client supports channel-binding, but only received methods without channel-binding, the client uses the SCRAM method without -PLUS, but also indicates that it would have used the PLUS varriant if offered by the server (SCRAM Channel Binding).
This allows for the server to detect downgrade attacks and fail the authentication.
Because there are multiple different kinds of channel-binding to TLS possible, the client also specifies which binding it uses during the SCRAM flow (protocol agility).
Channel binding prevents MITM altogether, right? Another property that’s fulfilled!
What’s broken with SASL in XMPP
Cautious readers will have noticed, that I left out property 2 (downgrade attack prevention) in my above explanation. That’s because SASL does not prevent downgrade attacks regarding the method negotiation at all. And that’s one of the main reasons why the whole SASL in XMPP stuff is so horribly broken.
Downgrade of SASL methods
The XMPP Core RFC even mentions downgrade attacks and suggests using TLS to mitigate them. But that’s not enough. If we assume the TLS channel to always be secure and MITM-free, we don’t even need SCRAM but could solely use PLAIN. The TLS channel already ensures no replay attacks can happen and storing the passwords securely using salted hashes on the server is still possible. We don’t need any channel-binding either, because that’s only needed if we assume someone has tampered with the TLS channel in the first place. In this scenario MITM-Prevention (property 5) is simply out of scope for SASL, because we assume TLS to always be MITM-free. That means our properties mentioned above are all fulfilled (property 5 by definition, the other ones by use of TLS) even if we abandon SCRAM and solely use PLAIN.
If we, on the other hand, don’t assume to always have a MITM-free TLS channel, then the above listed properties 2, 4 and 5 are all not fulfilled (that means: no downgrade prevention, no replay attack prevention and no MITM detection/prevention). The attacker could simply remove every advertised SASL method except PLAIN and thus get the plaintext password which is replayable and neither the server nor the client will detect this MITM. Supporting SCRAM in clients and servers does not help at all with this, because it simply will not be negotiated.
Well, okay, but XEP-0438: Best practices for password hashing and storage (and some RFC I don’t remember) says, we should pin the last used SASL mechanism in the client to prevent downgrade attacks in further SASL sessions. That way the client won’t use SASL mechanisms having a perceived lower strength than the pinned one. Using a stronger one is still possible. That’s right, but it makes matters worse in regard to protocol agility while still not preventing the downgrade attack for the first connection of a client to the XMPP server.
Broken protocol agility
Protocol agility means we can specify new authentication methods later on and our client will always use the best one advertised by the server. That’s important because it allows us to gradually upgrade the security strength of our authentication while still maintaining backwards compatibility with older clients, eventually removing an old authentication method once most/all clients use a newer one (like replacing DIGEST-MD5 or CRAM-MD5 with SCRAM-SHA-XXX).
SCRAM without PLUS variants
Because of SCRAM fulfilling property 3 (preventing storage of plaintext passwords on the server), we can not upgrade the stored password hashes in the server’s database to a new SCRAM-based SASL mechanism. The obvious partial solution is to store new user’s passwords using the newer SCRAM algorithm and leave the old user’s passwords like they are. That way at least some of your users get to use the new SCRAM algorithm, slowly phasing out the old one eventually. An example would be a server that previously only supported SCRAM-SHA-1 now advertising support for SCRAM-SHA-256.
Oh, no! That doesn’t work either! The current specification of SASL in XMPP does send the list of supported SASL methods to the client before knowing which username the client wants to authenticate for. That means the server will always advertise SCRAM-SHA-256, even if the hash in the database is still SHA-1. If the client supports SCRAM-SHA-256 as well, it will happily pick that one and the server, only having the SHA-1 hash at hand, won’t be able to authenticate the user. The client on the other hand will have no way to detect why the authentication failed and switch to a percieved lower strength SASL algorithm (and even if that would be possible, it could possibly be used as a downgrade vector if done wrong).
Well, method pinning to the rescue! We already talked about SASL method pinning. The client obviously knows which SASL method it used the last time it authenticated successfully and can always use just this method, no other, even if it implements some having a higher strength. That extends the pinning described in XEP-0438: Best practices for password hashing and storage to algorithms having a higher strength as well, something that, to my knowledge, isn’t specified anywhere. Additionally, that means we completely loose protocol agility after our first authentication. And newly offering SCRAM-SHA-256 on a server not storing plaintext passwords after it previously only advertised SCRAM-SHA-1 will likely still break authentication for all clients not doing this exact “always use the mechanism used on first auth” pinning.
The only way to advertise support for a new SCRAM hash algorithm and make clients use it is to upgrade your complete password database on the server by forcing a password reset upon all of your existing users. This must presumably be done out of band for XMPP. Clients implementing the strict pinning outlined above will have to reset the pinning once a new password gets entered by the user. If they don’t do so, this strict pinning to the old algorithm will still be in place and this client won’t be able to authenticate the user after the password reset.
And I’ve not even started to talk about a user having two clients, one that only supports an old SCRAM algorithm (say SCRAM-SHA-1) and one that supports a new one (say SCRAM-SHA-256). The server obviously must store both hashes in its database to allow logins for both clients.
Sounds all pretty bad, right? How come, that hasn’t been discovered yet? Well, someone already identified these problems back in 2019. See this thread on the firstname.lastname@example.org mailinglist. But no attempt was made to fix them. Even the new XEP-0388: Extensible SASL Profile still uses the same protocol flow with no fix, albeit allowing for additional handshakes during the authorization phase like pipelining a bind request or resumption via XEP-0198: Stream Management onto the authorization.
Sidenote: One could solve this mess by sacrificing property 3 (not storing plaintext passwords on the server). If the plaintext password is stored on the server, every SCRAM algorithm can be used by the client. But do we really want that? At least that would allow the server to advertise new SCRAM algorithms without having to force a password reset.
SCRAM with PLUS variants
When looking at the channel-binding situation we already saw that the client specifies the type of channel-binding it wants to use. That allows for protocol agility, right?
No! The client does not have any way to detect which channel-binding type the server supports (appending
-PLUS to the advertised SCRAM algorithm does not in any way specify which channel-binding to use). And if the client uses the wrong channel-binding the server does not support, the server will simply fail the authentication. The client will have no way to detect if the authentication failed because of the wrong channel-binding type used or if the actual password was wrong, like with using the “wrong” SASL algorithm above.
This renders the whole channel-binding protocol agility completely useless. And protocol agility is needed even for channel-binding!
Well, that’s pretty bad news, right? But I don’t want to simply rant and leave the shard for someone else to collect, but propose fixes instead. And to my knowledge some of these problems are really fixable.
In this section I want to propose fixes to at least some of these problems. These fixes are open to debate and if we come up with even better solutions during this debate, that’d be great.
Restoring protocol agility for channel-binding
The server must not use
-PLUS to indicate SCRAM algorithms with channel-binding, but use the name of the concrete channel-binding type as SCRAM algorithm suffix. That way the client will be able to determine if it supports that type of channel-binding and only select those SCRAM algorithms having a channel-binding it supports. The server is only allowed to advertise channel binding methods supported by the currently used channel (e.g. don’t advertise
tls-unique on a TLS 1.3 channel, but only
tls-exporter (if implemented)). Clients and servers may choose to still support the
-PLUS SCRAM method names in addition to these new ones containing the concrete channel-binding type, but I don’t think that gets us anywhere.
SCRAM-SHA-512_TLS-EXPORTER. That, of course, does not help at all, if channel-binding support can be rendered useless by a downgrade attack.
Preventing downgrade of SASL methods
Downgrades can be prevented by only allowing SASL EXTERNAL and SCRAM methods (or something similar to SCRAM that then mutual authenticates the whole protocol flow). The SCRAM client-final message must contain a client proof built using a HMAC not only covering the
client-final-message-without-proof, but also the (sorted) SASL method list. That allows the server to verify if the client used the correct list and this could not be manipulated, because it is ultimately signed with the client password, a MITM attacker can not know.
We can achieve this by adding an optional SCRAM attribute to
client-first-message-bare which will be ignored by non-supporting servers, but secure the handshake against downgrades on supporting servers. This attribute (let’s name it
d) will contain a base64-encoded hash of the sorted SASL method list as received by the client (using the same hash method as used in the whole SCRAM stuff). The server then checks if that hash matches the one it calculated itself by hashing the sorted SCRAM method list it advertised and fail the authentication, if these hashes don’t match.
The sorting can be done alphabetical in ascending order and the individual SASL methods be separated by
< like done in XEP-0115: Entity Capabilities before the whole string is hashed.
We now have a working downgrade attack prevention that’s even backwards compatible with existing SCRAM methods and servers not supporting this new SCRAM attribute.
Restoring protocol agility for SASL methods part #1
First of all, the server will have to store SCRAM hashes (or something similar for non-scram methods) for all methods it does support. If the server operator later decides to not offer a specific SASL method anymore, they can delete the (hash) data stored for that method from their server.
Second, the server can only advertise those methods it has hashes for in its database. That means it needs to know the username before advertising which methods it supports. This requires a change in the protocol flow for mechanism negotiation, which is not codified in the SASL RFC and can be changed via XEP. A good candidate would be XEP-0388: Extensible SASL Profile (also dubbed SASL2) which is not yet in
Final state and thus can be adjusted to our needs.
Let’s assume a SASL2 protocol flow by advertising support for this protocol, but not simultaneously advertising which SASL methods the server supports. The client would then first send the desired username it wants to authenticate for, and the server would respond with a list of supported SASL mechanisms for exactly this user. The username is of course not safe from a MITM attacker, but it will be included in the SCRAM authentication flow and the server will fail the authentication if that user differs from the one given earlier.
An example protocol flow using a modified SASL2 might look as follows (resembling more or less a normal SCRAM flow):
<!-- Server sends stream features indicating support for SASL2. --> <stream:features> <authentication xmlns='urn:xmpp:sasl:1'/> </stream:features> <!-- Client intiates authentication by sending the base64 encoded username it wishes to authenticate for. --> <request xmlns='urn:xmpp:sasl:1'> <user>dXNlcg==</user> </request> <!-- Server sends the list of supported mechanisms for this user. The sorted list will be 'SCRAM-SHA-1<SCRAM-SHA-1_TLS-EXPORTER<SCRAM-SHA-256<SCRAM-SHA-256_TLS-EXPORTER', the corresponding base64 encoded SHA-1 hash (SHA-1 will be used because negotiated below) is: 'U3vZANxXbl1pMOMBAFPnTb5YXWk=' --> <mechanisms xmlns='urn:xmpp:sasl:1'> <mechanism>SCRAM-SHA-1</mechanism> <mechanism>SCRAM-SHA-1_TLS-EXPORTER</mechanism> <mechanism>SCRAM-SHA-256</mechanism> <mechanism>SCRAM-SHA-256_TLS-EXPORTER</mechanism> </mechanisms> <!-- Client sends the selected mechanism alogside the initial-response data. --> <authenticate xmlns='urn:xmpp:sasl:1' mechanism='SCRAM-SHA-1_TLS-EXPORTER'> <!-- Base64 of: 'p=tls-exporter,,n=user,r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6,d=U3vZANxXbl1pMOMBAFPnTb5YXWk=' --> <initial-response>cD10bHMtZXhwb3J0ZXIsLG49dXNlcixyPTEyQzRDRDVDLUUzOEUtNEE5OC04RjZELTE1QzM4RjUxQ0NDNixkPVUzdlpBTnhYYmwxcE1PTUJBRlBuVGI1WVhXaz0=</initial-response> </authenticate> <!-- SCRAM-SHA-1 challenge issued by the server. Base64 of: 'r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6a09117a6-ac50-4f2f-93f1-93799c2bddf6,s=QSXCR+Q6sek8bf92,i=4096' --> <challenge xmlns='urn:xmpp:sasl:1'> cj0xMkM0Q0Q1Qy1FMzhFLTRBOTgtOEY2RC0xNUMzOEY1MUNDQzZhMDkxMTdhNi1hYzUwLTRmMmYtOTNmMS05Mzc5OWMyYmRkZjYscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng== </challenge> <!-- The client responds with the base64 encoded client-final-message (password: 'pencil'). Base64 of: 'c=cD10bHMtZXhwb3J0ZXIsLMcoQvOdBDePd4OswlmAWV3dg1a1Wh1tYPTBwVid10VU,r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6a09117a6-ac50-4f2f-93f1-93799c2bddf6,p=icrRuoQBB0htw5+K/6RNEDJ0Q4Y=' The c-attribute contains the GS2-header and channel-binding data blob (32 bytes). --> <response xmlns='urn:xmpp:sasl:1'> Yz1jRDEwYkhNdFpYaHdiM0owWlhJc0xNY29Rdk9kQkRlUGQ0T3N3bG1BV1YzZGcxYTFXaDF0WVBUQndWaWQxMFZVLHI9MTJDNENENUMtRTM4RS00QTk4LThGNkQtMTVDMzhGNTFDQ0M2YTA5MTE3YTYtYWM1MC00ZjJmLTkzZjEtOTM3OTljMmJkZGY2LHA9aWNyUnVvUUJCMGh0dzUrSy82Uk5FREowUTRZPQ== </response> <!-- This completes, so the Server sends a success containing the base64 encoded server signature. A SASL2 success always includes the authorization identifier. --> <success xmlns='urn:xmpp:sasl:1'> <authorization-identifier>email@example.com</authorization-identifier> <!-- Base64 of: 'v=Ax+ZEP5hf90z8+KnakwspK9mEhk=' --> <additional-data> dj1BeCtaRVA1aGY5MHo4K0tuYWt3c3BLOW1FaGs9 </additional-data> </success>
Restoring protocol agility for SASL methods part #2
Activating a new SASL (SCRAM) method and saving new hashes for these methods for already known users on the server is a bit trickier. To solve this, the server offers new SASL mechanisms indicating that he allows for hash upgrades, if he doesn’t have all required hashes in its database yet. The ordering of these new upgrade mechanisms should use a stable sorting algorithm. First sorting by perceived strength of the algorithm updated to, then by the percieved strength of the algorithm used for authentication (e.g. use the highest strength for authentication and upgrade to the highest strength possible with this authentication).
For reference, the SCRAM flow as stated in RFC 5802 section 3 is as follows, with HMAC() and HASH() corresponding to the hash method used to authenticate (e.g. SHA-256 for SCRAM-SHA-256):
SaltedPassword := Hi(Normalize(password), salt, i) ClientKey := HMAC(SaltedPassword, "Client Key") StoredKey := H(ClientKey) AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof ClientSignature := HMAC(StoredKey, AuthMessage) ClientProof := ClientKey XOR ClientSignature ServerKey := HMAC(SaltedPassword, "Server Key") ServerSignature := HMAC(ServerKey, AuthMessage)
SaltedPassword is the hash saved in the database on the server alongside the
salt. Using additional SASL2 tasks we can now require the client to perform an additional task which consists of sending the
SaltedPassword for the hash algorithm to upgrade to. The server just provides the required salt (that must be a new random value not equal to the one used for the old hash) and iteration count. By providing the salted hash after the successful completion of our SCRAM authentication, server and client can be sure to talk to the right one and when channel-binding is used, both can be sure no MITM is involved that could intercept the new
SaltedPassword. I strongly suggest to only support password upgrades if channel-binding is used.
An example protocol flow using a modified SASL2 might look as follows (resembling more or less the SCRAM flow used in part #1 above and adding a second task for the desired hash upgrade):
<!-- Server sends stream features indicating support for SASL2. --> <stream:features> <authentication xmlns='urn:xmpp:sasl:1'/> </stream:features> <!-- Client intiates authentication by sending the base64 encoded username it wishes to authenticate for. --> <request xmlns='urn:xmpp:sasl:1'> <user>dXNlcg==</user> </request> <!-- Server sends the list of supported mechanisms for this user. The sorted list will be 'SCRAM-SHA-1_TLS-EXPORTER<SCRAM-SHA-256_TLS-EXPORTER<UPGRADE-SCRAM-SHA-256_SCRAM-SHA-1<UPGRADE-SCRAM-SHA-256_SCRAM-SHA-1_TLS-EXPORTER', the corresponding base64 encoded SHA-1 hash (SHA-1 will be used because negotiated below) is: 'nKFUXQ7h9IL3eo17pKygmacnEsk=' --> <mechanisms xmlns='urn:xmpp:sasl:1'> <mechanism>SCRAM-SHA-1</mechanism> <mechanism>SCRAM-SHA-1_TLS-EXPORTER</mechanism> <mechanism>UPGRADE-SCRAM-SHA-256_SCRAM-SHA-1</mechanism> <mechanism>UPGRADE-SCRAM-SHA-256_SCRAM-SHA-1_TLS-EXPORTER</mechanism> </mechanisms> <!-- Client sends the selected mechanism alogside the initial-response data. --> <authenticate xmlns='urn:xmpp:sasl:1' mechanism='SCRAM-SHA-1_TLS-EXPORTER'> <!-- Base64 of: 'p=tls-exporter,,n=user,r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6,d=nKFUXQ7h9IL3eo17pKygmacnEsk=' --> <initial-response>cD10bHMtZXhwb3J0ZXIsLG49dXNlcixyPTEyQzRDRDVDLUUzOEUtNEE5OC04RjZELTE1QzM4RjUxQ0NDNixkPVUzdlpBTnhYYmwxcE1PTUJBRlBuVGI1WVhXaz0=</initial-response> </authenticate> <!-- SCRAM-SHA-1 challenge issued by the server. Base64 of: 'r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6a09117a6-ac50-4f2f-93f1-93799c2bddf6,s=QSXCR+Q6sek8bf92,i=4096' --> <challenge xmlns='urn:xmpp:sasl:1'> cj0xMkM0Q0Q1Qy1FMzhFLTRBOTgtOEY2RC0xNUMzOEY1MUNDQzZhMDkxMTdhNi1hYzUwLTRmMmYtOTNmMS05Mzc5OWMyYmRkZjYscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng== </challenge> <!-- The client responds with the base64 encoded client-final-message (password: 'pencil'). Base64 of: 'c=cD10bHMtZXhwb3J0ZXIsLMcoQvOdBDePd4OswlmAWV3dg1a1Wh1tYPTBwVid10VU,r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6a09117a6-ac50-4f2f-93f1-93799c2bddf6,p=UApo7xo6Pa9J+Vaejfz/dG7BomU=' The c-attribute contains the GS2-header and channel-binding data blob (32 bytes). --> <response xmlns='urn:xmpp:sasl:1'> Yz1jRDEwYkhNdFpYaHdiM0owWlhJc0xNY29Rdk9kQkRlUGQ0T3N3bG1BV1YzZGcxYTFXaDF0WVBUQndWaWQxMFZVLHI9MTJDNENENUMtRTM4RS00QTk4LThGNkQtMTVDMzhGNTFDQ0M2YTA5MTE3YTYtYWM1MC00ZjJmLTkzZjEtOTM3OTljMmJkZGY2LHA9VUFwbzd4bzZQYTlKK1ZhZWpmei9kRzdCb21VPQ== </response> <!-- This completes, so the Server sends a continue containing the base64 encoded server signature and the upgrade task to perform. --> <continue xmlns='urn:xmpp:sasl:1'> <authorization-identifier>firstname.lastname@example.org</authorization-identifier> <!-- Base64 of: 'msVHs/BzIOHDqXeVH7EmmDu9id8=' --> <additional-data> dj1tc1ZIcy9CeklPSERxWGVWSDdFbW1EdTlpZDg9 </additional-data> <tasks> <task>UPGRADE-SCRAM-SHA-256</task> </tasks> </continue> <!-- The client provides the SaltedPassword hash for SCRAM-SHA-256 --> <next xmlns='urn:xmpp:sasl:1' task='UPGRADE-SCRAM-SHA-256'/> <!-- The server sends the required salt and iteration count encoded as base64 encoded SASL attributes. Base64 of: 's=A_SXCRXQ6sek8bf_Z,i=4096' --> <challenge xmlns='urn:xmpp:sasl:1'> cz1BX1NYQ1JYUTZzZWs4YmZfWixpPTQwOTY= </challenge> <!-- The client responds with the base64 encoded SaltedPassword. --> <response xmlns='urn:xmpp:sasl:1'> BzOnw3Pc5H4bOLlPZ/8JAy6wnTpH05aH21KW2+Xfpaw= </response> <!-- Finally, the server sends a success after adding the salted SHA-256 hash to it's database. A SASL2 success always includes the authorization identifier. --> <success xmlns='urn:xmpp:sasl:1'> <authorization-identifier>email@example.com</authorization-identifier> </success>
If the server needs to upgrade to multiple new SCRAM algorithms, he can do so one at a time on every new authentication. This is no “do everything once” anyways, because not every client might support every upgrade possible.
To conclude this, we now identified several improvements to regain the properties listed in the beginning. These improvements can mainly be achieved by updating the SASL2 XEP (XEP-0388: Extensible SASL Profile). The downgrade prevention (the additional SCRAM attribute
d) should possibly be published as RFC, but a XEP could suffice, too.
Feel free to comment on anything in here. I’m always open to feedback and improvements. Just contact me at firstname.lastname@example.org.