Channel Binding
With the latest update to the popular Conversations app, which requires “channel binding” by default, lots of people are encountering channel binding for the first time. This post aims to answer questions for both Prosody operators and users.
Channel binding is a security feature which helps to keep your connection secure. We’ll dig into exactly what it is, how it is used, and what (if anything) you need to do.
A brief TLS primer
When you establish connections on the internet today, they will almost certainly be using TLS (on the web, TLS connections are referred to as HTTPS). It is well known that TLS provides encryption, but there is no point in encrypting your bytes if you are sending them to an attacker, e.g. someone who can intercept or divert your internet traffic. TLS therefore provides something else - authentication, or the ability to cryptographically prove who you are communicating with - using certificates.
Anyone can create a certificate that claims they are anybody. You can trivially
use the openssl command-line tool to create a certificate for microsoft.com
or google.com. But nobody would trust your certificate - you have to have it
signed by a recognised Certificate Authority. The most well-known CA is
Let’s Encrypt, but there are many more. Operating systems and browsers ship a
list of trusted certificate authorities, and software automatically trusts
certificates that have been signed by one of these CAs.
Flaws in the CA ecosystem
However, this arrangement has suffered numerous problems. Any trusted CA can issue a certificate for any domain, and in the past, they have. By utilizing Chrome’s pinning and telemetry, Google discovered multiple cases of CAs issuing certificates for domains without permission. This led them to develop Certificate Transparency, a system of public logs that (these days) every CA trusted by browsers is required to submit every single issued certificate to, leading to transparency and accountability.
Browsers have now started requiring every certificate to include a proof that it has been submitted to a Certificate Transparency log. This is known as a Signed Certificate Timestamp (SCT).
SCTs solve the problem of CAs silently misissuing certificates - via CT logs every certificate can now be tracked and audited. CAs found to have incorrectly issued certificates can be investigated and potentially distrusted by browsers and operating systems.
But the CT/SCT solution isn’t perfect. It provides a record after the attack has already happened, and this record can assist with remediation. But it does not prevent the attack in the first place.
It also does not protect against:
- Certificates that have been correctly issued by the CA, but have been compromised
- Self-signed or private CA certificates (CT logs typically only track certificates issued by mainstream CAs).
- Compromised CAs
Real attacks
A case of a compromised CA happened just a couple of weeks ago. The eMudhra CA incident found that this CA’s keys were extremely weak and guessable, meaning anyone could generate a certificate that looked like it was officially signed by eMudhra. Although CT helped to determine the impact after the compromise was discovered, during the period between the discovery of the weak keys and the CA being distrusted by systems, anyone could forge a valid-looking certificate, submit it to CT logs, and obtain SCTs for the forged certificates. The attack was not prevented by CTs/SCTs, they only made things auditable after the compromise became known.
In another kind of incident in 2023, a public XMPP server was found to have had its TLS connections intercepted. Because the intercepting machine had access to the legitimate server’s traffic, it was also able to intercept the “challenge” requests from Let’s Encrypt, and obtain a valid certificate. This certificate was published in CT logs, while the attack lasted for months.
From the perspective of Let’s Encrypt, this was not a misissued certificate.
They performed the expected checks before issuing the certificate.
Unfortunately the victim domain did not have a CAA record with a pinned
accounturi. This was a mechanism introduced in 2019 to prevent
this kind of attack.
However, even with CAA records, it is not that hard to imagine situations where a CA could be tricked or compelled into issuing certificates.
Another mechanism - you guessed it, channel binding - would have made both these attacks immediately detectable by XMPP clients.
Channel binding
Enough of the problems. What can we do? Well, XMPP has for some time supported a technique known as channel binding. This is actually part of the authentication layer - the layer that verifies your password/token - and all it does is cryptographically “bind” that authentication to the encrypted data channel (your TLS connection).
To work, channel binding requires a few things:
- A shared secret - this is typically your account password, or an auth token. It doesn’t actually have to be known to the server, it can be hashed.
- Mutual authentication - this is a feature of some authentication mechanisms.
In a non-mutual authentication mechanism, the client might send the password to the server and the server says “Correct password! You’re now authenticated.” But with mutual authentication, the client submits proof that it has the password/token, and the server also returns a proof that it has the password/token (or a hash of it) to the client. This allows the client to ensure it is logging in to the legitimate server.
Client Attacker Server
(MITM)
| | |
| == TLS connection A ==> | === TLS connection B ===> |
| attacker's valid cert | attacker poses as client |
| | |
|-- password proof ------->|-- relayed as-is ---------->| proof OK
|<---------- server proof -|<----------- relayed as-is -|
| | |
Logged in! Attacker sees and can modify Logged in!
everything in both directions
SCRAM is a widely used authentication mechanism which supports mutual authentication and channel binding (SCRAM “PLUS”), and allows the client and the server to store a hashed version of the credentials.
Channel binding extends mutual authentication to include unique per-connection data from the TLS layer. This means that the entity you are authenticating with must now also be the entity that is decrypting your TLS connection. If you have a malicious machine in the middle, decrypting the client’s TLS and then encrypting it again and sending it onwards to the real server, this would now be detected - because the client’s connection to the MITM is not the same connection as the one between the MITM and the server.
Client Attacker Server
(MITM)
| | |
| == TLS connection A ==> | === TLS connection B ===> |
| | |
|-- proof bound to A ----->|-- relayed as-is ---------->| I'm on B, but
| | | this proof is
| | | for A: REJECT
| |<----------------- failure -|
| | |
The attacker can't generate a correct proof for connection B
because it needs knowledge of the password. For the same reason,
it also can't fake a proof from the server and trick the client
into believing that authentication succeeded.
Channel binding seems rather magical. It doesn’t depend on the CA ecosystem at all, and works with any certificate, including self-signed. It doesn’t matter if the CA has been compromised - if your mutual authentication with channel binding passes, your connection is being handled by the same server that you are authenticating to.
It prevents a range of attacks, and is relatively simple from an operational perspective. However, for it to be an effective defence, the client must not simply try authentication without channel binding when channel binding fails. Otherwise an attacker can just make channel binding fail, and when the client tries again without channel binding, the attack will succeed. This is known as a downgrade attack.
How do I enable channel binding in Prosody?
Channel binding is actually already enabled in Prosody out of the box, there is nothing specific you need to do to enable it. The requirements are simply:
- Prosody 13.0 or later (earlier versions supported channel binding, but not with TLS 1.3 which is mostly used these days)
- The default configuration -
authentication = "internal_hashed"andnetwork_backend = "epoll"(the libevent backend also works in latest 13.0.x) - LuaSec 1.2.0+ (you can view your version in ‘prosodyctl about’)
But this doesn’t mean that it is available in every deployment. As discussed above, channel binding provides proof that the server authenticating you is the same server your TLS connection is being handled by. Channel binding cannot work if:
- Authentication credentials are being checked by an external service (e.g. Cyrus SASL, Dovecot, LDAP)
- TLS is being offloaded outside of Prosody (proxies are okay, as long as they don’t decrypt TLS)
In both these cases, channel binding breaks because the situation is similar to a MITM attack. Something is decrypting your TLS connection before the authentication. Channel binding is trying to ensure that your authentication server is also your encryption server.
For the TLS offloading case, there is a version of channel binding which can work by providing
a hash of the certificate. This will then be compared with the hash of the certificate that the
client sees. It has weaker security guarantees, but it allows things to work even if
Prosody is not terminating the TLS connection. If you have such a setup, set tls_server_end_point_hash
in your configuration to the hash of your DER-encoded certificate, and note that this needs
to be updated every time your certificate changes (which, with modern CAs, can be rather frequently).
If you’re having problems with channel binding and you’re not sure why, enable
debug logs (or use prosodyctl shell watch log) - it’s likely that the debug
logs will tell you whether channel binding is available, and if not, why not.
What if I need to authenticate against an external service?
Some external services can work in theory. Authentication providers which are compatible with channel binding are those which can provide to Prosody either:
- SCRAM-formatted credentials (e.g. as described in RFC 5803). Prosody’s default internal authentication provider stores SCRAM-formatted hashes in a file or in your database.
- The plaintext password (!). Although not recommended, there are still some integrations people use which use this method, and it is compatible with channel binding.
If your authentication provider is receiving either a plain or hashed password from Prosody, and making its own decision about whether the authentication was successful, your authentication provider is very likely to be incompatible with channel binding.
Conversations won’t connect!
If your server doesn’t support channel binding, it’s not the end of the world.
The new Conversations requirement for channel binding can be turned off in the security settings of the app, and you only need to do this once. Even with the setting disabled, Conversations will still use channel binding when a server supports it. For other servers you will still benefit from standard TLS, just make sure you have taken other precautions such as adding CAA records for your domain.
Is channel binding the ultimate protection?
Channel binding is great, but it’s not going to save you from every kind of attack (nothing will). You should still do things like add CAA records and use DNSSEC.
Channel binding has the following weaknesses:
- It depends on the secrecy of the credentials used for the authentication. If a server’s database is compromised for example, the attacker will have everything they need to forge the correct channel binding response.
- It requires a shared secret - it doesn’t work for server-to-server connections because there are no such secrets used there. DNSSEC/DANE is one alternative approach here.
- It simply doesn’t work with some deployments, e.g. where you delegate authentication decisions to an external service.
Summary
Channel binding is a powerful tool for ensuring connection security. It’s generally automatically enabled for most people, if your server is up to date and has the recommended configuration.
If not, just disable the requirement in Conversations. Your connections will still be encrypted!
About
Prosody is a lightweight and flexible XMPP server designed with ease-of-use and extensibility in mind.