Hi,
I was debugging a bug report for my sieve editor implementation concerning an issue with SCRAM-SHA Authorization.
But I am a bit unsure about my findings. It seems to be a server side bug. But I am wondering if I missed something. What puzzles me a bit is that SCRAM-SHA is out since ages and obviously no one ran into this issue. Which feels odd.
When I try to connect to a dovecot server with enabled Authorization, then SCRAM SHA1 or SHA256 fail with a channel binding error. Authorization via SASL PLAIN works perfectly fine.
So I digged into the logs. The trace is below also my findings/conclusion. The username and authorization in the trace are set to "user" the password is "pencil".
Nov 24 14:00:06 auth: Debug: client in: AUTH 1 SCRAM-SHA-256 service=sieve secured=tlssession=sI9yRonRZISsEQAB lip=172.17.0.2 rip=172.17.0.1 lport=4190 rport=33892 resp=bixhPXVzZXIsbj11c2VyLHI9ZmEzZGY1NjNlZjczZTY3YjQzZThhZWRiZjUzNGI1NWIxNzIwNzU3OThjZmJiMjExNmMyMTNkMWM4NDE4MmZkOQ== (previous base64 data may contain sensitive data) Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): Performing passdb lookup Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): lookup: user=user file=/etc/dovecot/users Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): Generating SCRAM-SHA-256 from user 'user', password 'pencil' Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): Finished passdb lookup Nov 24 14:00:06 auth: Debug: Credentials: 343039362c326b3934364452546c326431765a37434b6d556c32513d3d2c46444d364f7173577035464c446e394151666554316547585045427a47566d6965673842427331456d37633d2c79766e5a4169376250392f59706c4a71597872356a79794f7a6e4c664730667636485155325a7a6975376b3d Nov 24 14:00:06 auth: Debug: client passdb out: CONT 1 cj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxzPTJrOTQ2RFJUbDJkMXZaN0NLbVVsMlE9PSxpPTQwOTY= Nov 24 14:00:06 auth: Debug: client in: CONT 1 Yz1iaXhoUFhWelpYSXMscj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxwPUcvZGZWUUlGZy9NMkpSMlpEMXM0cms4MS9jTmFMa0tuMzZFYzFlTTN1enc9 (previous base64 data may contain sensitive data) Nov 24 14:00:06 auth: Info: scram-sha-256(user,172.17.0.1,<sI9yRonRZISsEQAB>): Invalid channel binding data Nov 24 14:00:06 auth: Debug: auth(user,172.17.0.1,<sI9yRonRZISsEQAB>): Auth request finished Nov 24 14:00:08 auth: Debug: client passdb out: FAIL 1 user=user Nov 24 14:00:08 managesieve-login: Info: Disconnected: Aborted login by logging out (auth failed, 1 attempts in 2 secs): user=<user>, method=SCRAM-SHA-256, rip=172.17.0.1, lip=172.17.0.2, TLS, session=<sI9yRonRZISsEQAB> Nov 24 14:00:08 managesieve-login: Debug: SSL alert: close notify
The matching trace from the client, a sieve editor (https://github.com/thsmi/sieve) is as follows:
[15:000:06.192 kw53213a-qc7kf99m1lp] Client -> Server: AUTHENTICATE "SCRAM-SHA-256" "bixhPXVzZXIsbj11c2VyLHI9ZmEzZGY1NjNlZjczZTY3YjQzZThhZWRiZjUzNGI1NWIxNzIwNzU3OThjZmJiMjExNmMyMTNkMWM4NDE4MmZkOQ==" [15:000:06.202 kw53213a-qc7kf99m1lp] Server -> Client "cj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxzPTJrOTQ2RFJUbDJkMXZaN0NLbVVsMlE9PSxpPTQwOTY=" [15:000:06.209 kw53213a-qc7kf99m1lp] Client -> Server: "Yz1iaXhoUFhWelpYSXMscj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxwPUcvZGZWUUlGZy9NMkpSMlpEMXM0cms4MS9jTmFMa0tuMzZFYzFlTTN1enc9" [15:000:08.215 kw53213a-qc7kf99m1lp] Server -> Client NO "Authentication failed."
According to the RFC (https://datatracker.ietf.org/doc/html/rfc5802) the initial request is defined as:
(("p=" cb-name) / "n" / "y") "," [ authzid ] "," client-first-message-bare
This means the decoded message from the trace is:
n,a=user,n=user,r=fa3df563ef73e67b43e8aedbf534b55b172075798cfbb2116c213d1c84182fd9
The final request (which is rejected by the server with the binding error) is defined as :
client-final-message-without-proof = channel-binding "," nonce [","extensions]
or
client-final-message = client-final-message-without-proof "," proof
So the request from the trace decodes into:
c=bixhPXVzZXIs,r=fa3df563ef73e67b43e8aedbf534b55b172075798cfbb2116c213d1c84182fd9'd=4+$WcnXAUNvq9MDdC The channel-binding and cbind-input part are defined as: channel-binding = "c=" base64(cbind-input) cbind-input = (("p=" cb-name) / "n" / "y") "," [ authzid ] "," [
cbind-data ] where cbind-data has to be absent by definition as dovecot does not
support channel binding so no "p=" is possible and thus no cbind-data. This means c=bixhPXVzZXI decodes into.
n,a=user, And this is where it gets strange. The dovecot code section which throws
the error, implements the following: https://github.com/dovecot/core/blob/a5209c83c3a82386c94d466eec5fea394973e88... cbind_input = t_strconcat(request->gs2_cbind_flag, ",,", NULL);
str = t_str_new(MAX_BASE64_ENCODED_SIZE(strlen(cbind_input)));
str_append(str, "c=");
base64_encode(cbind_input, strlen(cbind_input), str); if (strcmp(fields[0], str_c(str)) != 0) {
*error_r = "Invalid channel binding data";
return FALSE;
} As you can see it uses the bind flag which was saved to the request. But
instead of the authzid, it uses always an hardcoded empty string. Thus cbind-input is "n,," instead of "n,a=user," and results in the
request being rejected with an "Invalid channel binding data". Which is on the one hand a funny message because dovecot does not
support channel binding at all, as describe in
https://github.com/dovecot/core/blob/a5209c83c3a82386c94d466eec5fea394973e88...
but it also on the other hand somehow correct because cbind-input string
does not match. Which is an illegal state during channel binding
negotiation which should not happen. As said previously, it looks to me like a server side bug. Or did I miss
something special case in the RFC? Kind Regards Thomas Schmid3e!yX.\\7;b
*X4_o`(;A^rkN-qRd6!x"A~~xe
Hi,
Yep, this whole analysis is correct and actually known for a while now. Fix is in the pipeline (known as DOV-4944). You can try it here: https://github.com/stephanbosch/dovecot-core/commits/sasl-scram-master
The reason this doesn't readily come up is probably that people don't use master login with SCRAM often.
Regards,
Stephan.
On 02/12/2021 00:43, Thomas Schmid wrote:
Hi,
I was debugging a bug report for my sieve editor implementation concerning an issue with SCRAM-SHA Authorization.
But I am a bit unsure about my findings. It seems to be a server side bug. But I am wondering if I missed something. What puzzles me a bit is that SCRAM-SHA is out since ages and obviously no one ran into this issue. Which feels odd.
When I try to connect to a dovecot server with enabled Authorization, then SCRAM SHA1 or SHA256 fail with a channel binding error. Authorization via SASL PLAIN works perfectly fine.
So I digged into the logs. The trace is below also my findings/conclusion. The username and authorization in the trace are set to "user" the password is "pencil".
Nov 24 14:00:06 auth: Debug: client in: AUTH 1 SCRAM-SHA-256 service=sieve secured=tlssession=sI9yRonRZISsEQAB lip=172.17.0.2 rip=172.17.0.1 lport=4190 rport=33892 resp=bixhPXVzZXIsbj11c2VyLHI9ZmEzZGY1NjNlZjczZTY3YjQzZThhZWRiZjUzNGI1NWIxNzIwNzU3OThjZmJiMjExNmMyMTNkMWM4NDE4MmZkOQ==
(previous base64 data may contain sensitive data) Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): Performing passdb lookup Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): lookup: user=user file=/etc/dovecot/users Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): Generating SCRAM-SHA-256 from user 'user', password 'pencil' Nov 24 14:00:06 auth: Debug: passwd-file(user,172.17.0.1,<sI9yRonRZISsEQAB>): Finished passdb lookup Nov 24 14:00:06 auth: Debug: Credentials: 343039362c326b3934364452546c326431765a37434b6d556c32513d3d2c46444d364f7173577035464c446e394151666554316547585045427a47566d6965673842427331456d37633d2c79766e5a4169376250392f59706c4a71597872356a79794f7a6e4c664730667636485155325a7a6975376b3d
Nov 24 14:00:06 auth: Debug: client passdb out: CONT 1 cj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxzPTJrOTQ2RFJUbDJkMXZaN0NLbVVsMlE9PSxpPTQwOTY=
Nov 24 14:00:06 auth: Debug: client in: CONT 1 Yz1iaXhoUFhWelpYSXMscj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxwPUcvZGZWUUlGZy9NMkpSMlpEMXM0cms4MS9jTmFMa0tuMzZFYzFlTTN1enc9
(previous base64 data may contain sensitive data) Nov 24 14:00:06 auth: Info: scram-sha-256(user,172.17.0.1,<sI9yRonRZISsEQAB>): Invalid channel binding data Nov 24 14:00:06 auth: Debug: auth(user,172.17.0.1,<sI9yRonRZISsEQAB>): Auth request finished Nov 24 14:00:08 auth: Debug: client passdb out: FAIL 1 user=user Nov 24 14:00:08 managesieve-login: Info: Disconnected: Aborted login by logging out (auth failed, 1 attempts in 2 secs): user=<user>, method=SCRAM-SHA-256, rip=172.17.0.1, lip=172.17.0.2, TLS, session=<sI9yRonRZISsEQAB> Nov 24 14:00:08 managesieve-login: Debug: SSL alert: close notify
The matching trace from the client, a sieve editor (https://github.com/thsmi/sieve) is as follows:
[15:000:06.192 kw53213a-qc7kf99m1lp] Client -> Server: AUTHENTICATE "SCRAM-SHA-256" "bixhPXVzZXIsbj11c2VyLHI9ZmEzZGY1NjNlZjczZTY3YjQzZThhZWRiZjUzNGI1NWIxNzIwNzU3OThjZmJiMjExNmMyMTNkMWM4NDE4MmZkOQ=="
[15:000:06.202 kw53213a-qc7kf99m1lp] Server -> Client "cj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxzPTJrOTQ2RFJUbDJkMXZaN0NLbVVsMlE9PSxpPTQwOTY="
[15:000:06.209 kw53213a-qc7kf99m1lp] Client -> Server: "Yz1iaXhoUFhWelpYSXMscj1mYTNkZjU2M2VmNzNlNjdiNDNlOGFlZGJmNTM0YjU1YjE3MjA3NTc5OGNmYmIyMTE2YzIxM2QxYzg0MTgyZmQ5J2Q9NCskV2NuWEFVTnZxOU1EZENgM2UheVguXFw3O2JgKlg0X29gKDtBXnJrTi1xUmQ2IXgiQX5+eGU8ZlJHWSxwPUcvZGZWUUlGZy9NMkpSMlpEMXM0cms4MS9jTmFMa0tuMzZFYzFlTTN1enc9"
[15:000:08.215 kw53213a-qc7kf99m1lp] Server -> Client NO "Authentication failed."
According to the RFC (https://datatracker.ietf.org/doc/html/rfc5802) the initial request is defined as:
(("p=" cb-name) / "n" / "y") "," [ authzid ] "," client-first-message-bare
This means the decoded message from the trace is:
n,a=user,n=user,r=fa3df563ef73e67b43e8aedbf534b55b172075798cfbb2116c213d1c84182fd9
The final request (which is rejected by the server with the binding error) is defined as :
client-final-message-without-proof = channel-binding "," nonce [","extensions]
or
client-final-message = client-final-message-without-proof "," proof
So the request from the trace decodes into:
c=bixhPXVzZXIs,r=fa3df563ef73e67b43e8aedbf534b55b172075798cfbb2116c213d1c84182fd9'd=4+$WcnXAUNvq9MDdC
3e!yX.\\7;b
*X4_o`(;A^rkN-qRd6!x"A~~xeThe channel-binding and cbind-input part are defined as:
channel-binding = "c=" base64(cbind-input)
cbind-input = (("p=" cb-name) / "n" / "y") "," [ authzid ] "," [ cbind-data ]
where cbind-data has to be absent by definition as dovecot does not support channel binding so no "p=" is possible and thus no cbind-data.
This means c=bixhPXVzZXI decodes into. n,a=user,
And this is where it gets strange. The dovecot code section which throws the error, implements the following:
https://github.com/dovecot/core/blob/a5209c83c3a82386c94d466eec5fea394973e88...
cbind_input = t_strconcat(request->gs2_cbind_flag, ",,", NULL); str = t_str_new(MAX_BASE64_ENCODED_SIZE(strlen(cbind_input))); str_append(str, "c="); base64_encode(cbind_input, strlen(cbind_input), str);
if (strcmp(fields[0], str_c(str)) != 0) { *error_r = "Invalid channel binding data"; return FALSE; }
As you can see it uses the bind flag which was saved to the request. But instead of the authzid, it uses always an hardcoded empty string.
Thus cbind-input is "n,," instead of "n,a=user," and results in the request being rejected with an "Invalid channel binding data".
Which is on the one hand a funny message because dovecot does not support channel binding at all, as describe in https://github.com/dovecot/core/blob/a5209c83c3a82386c94d466eec5fea394973e88...
but it also on the other hand somehow correct because cbind-input string does not match. Which is an illegal state during channel binding negotiation which should not happen.
As said previously, it looks to me like a server side bug. Or did I miss something special case in the RFC?
Kind Regards
Thomas Schmid
participants (2)
-
Stephan Bosch
-
Thomas Schmid