Letting the client choose the protocol to use during a SASL exchange
Hi all,
There is still one open item pending for SCRAM that has not been
treated which is mentioned here:
/messages/by-id/b081887e-1712-3aa4-7dbe-e012333d50e4@iki.fi
When doing an authentication with SASL, the server decides what is the
mechanism that the client has to use. As SCRAM-SHA-256 is only one of
such mechanisms, it would be nice to have something more generic and
have the server return to the client a list of protocols that the
client can choose from. And also the server achnowledge which protocol
is going to be used.
Note that RFC4422 has some content on the matter
https://tools.ietf.org/html/rfc4422#section-3.1:
Mechanism negotiation is protocol specific.
Commonly, a protocol will specify that the server advertises
supported and available mechanisms to the client via some facility
provided by the protocol, and the client will then select the "best"
mechanism from this list that it supports and finds suitable.
So once the server sends back the list of mechanisms that are
supported, the client is free to use what it wants.
On HEAD, a 'R' message with AUTH_REQ_SASL followed by
SCRAM_SHA256_NAME is sent to let the client know what is the mechanism
to use for the SASL exchange. In the future, this should be extended
so as a list of names is sent, for example a comma-separated list, but
we are free to choose the format we want here. With this list at hand,
the client can then choose the protocol it thinks is the best. Still,
there is a gap with our current implementation because the server
expects the first message from the client to have a SCRAM format, but
that's true only if SCRAM-SHA-256 is used as mechanism.
In order to cover this gap, it seems to me that we need to have an
intermediate state before the server is switched to FE_SCRAM_INIT so
as the mechanism used is negotiated between the two parties. Once the
protocol negotiation is done, the server can then move on with the
mechanism to use. This would be important in the future to allow more
SASL mechanisms to work. I am adding an open item for that.
For extensibility, we may also want to revisit the choice of defining
'scram' in pg_hba.conf instead of 'sasl'...
Thoughts?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Apr 04, 2017 at 03:02:30PM +0900, Michael Paquier wrote:
There is still one open item pending for SCRAM that has not been
treated which is mentioned here:
/messages/by-id/b081887e-1712-3aa4-7dbe-e012333d50e4@iki.fiWhen doing an authentication with SASL, the server decides what is the
mechanism that the client has to use. As SCRAM-SHA-256 is only one of
such mechanisms, it would be nice to have something more generic and
have the server return to the client a list of protocols that the
client can choose from. And also the server achnowledge which protocol
is going to be used.Note that RFC4422 has some content on the matter
https://tools.ietf.org/html/rfc4422#section-3.1:
Mechanism negotiation is protocol specific.Commonly, a protocol will specify that the server advertises
supported and available mechanisms to the client via some facility
provided by the protocol, and the client will then select the "best"
mechanism from this list that it supports and finds suitable.So once the server sends back the list of mechanisms that are
supported, the client is free to use what it wants.On HEAD, a 'R' message with AUTH_REQ_SASL followed by
SCRAM_SHA256_NAME is sent to let the client know what is the mechanism
to use for the SASL exchange. In the future, this should be extended
so as a list of names is sent, for example a comma-separated list, but
we are free to choose the format we want here. With this list at hand,
the client can then choose the protocol it thinks is the best. Still,
there is a gap with our current implementation because the server
expects the first message from the client to have a SCRAM format, but
that's true only if SCRAM-SHA-256 is used as mechanism.In order to cover this gap, it seems to me that we need to have an
intermediate state before the server is switched to FE_SCRAM_INIT so
as the mechanism used is negotiated between the two parties. Once the
protocol negotiation is done, the server can then move on with the
mechanism to use. This would be important in the future to allow more
SASL mechanisms to work. I am adding an open item for that.
If any SCRAM open item is a beta blocker, it's this one. (But SASLprep is
also in or near that status.) Post-beta wire protocol changes are bad,
considering beta is normally the time for projects like pgjdbc and npgsql to
start adapting to such changes.
[Action required within three days. This is a generic notification.]
The above-described topic is currently a PostgreSQL 10 open item. Heikki,
since you committed the patch believed to have created it, you own this open
item. If some other commit is more relevant or if this does not belong as a
v10 open item, please let us know. Otherwise, please observe the policy on
open item ownership[1]/messages/by-id/20170404140717.GA2675809@tornado.leadboat.com and send a status update within three calendar days of
this message. Include a date for your subsequent status update. Testers may
discover new open items at any time, and I want to plan to get them all fixed
well in advance of shipping v10. Consequently, I will appreciate your efforts
toward speedy resolution. Thanks.
[1]: /messages/by-id/20170404140717.GA2675809@tornado.leadboat.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/06/2017 08:13 AM, Noah Misch wrote:
If any SCRAM open item is a beta blocker, it's this one. (But SASLprep is
also in or near that status.) Post-beta wire protocol changes are bad,
considering beta is normally the time for projects like pgjdbc and npgsql to
start adapting to such changes.[Action required within three days. This is a generic notification.]
The above-described topic is currently a PostgreSQL 10 open item.
I will work on this next week. I haven't given it much thought yet, but
I think it's going to be pretty straightforward. It won't require much
code yet, as we only support one SASL mechanism. We just need to ensure
that we don't paint ourselves in the corner with the protocol.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 06/04/17 19:05, Heikki Linnakangas wrote:
On 04/06/2017 08:13 AM, Noah Misch wrote:
If any SCRAM open item is a beta blocker, it's this one. (But
SASLprep is
also in or near that status.) Post-beta wire protocol changes are bad,
considering beta is normally the time for projects like pgjdbc and
npgsql to
start adapting to such changes.[Action required within three days. This is a generic notification.]
The above-described topic is currently a PostgreSQL 10 open item.
I will work on this next week. I haven't given it much thought yet,
but I think it's going to be pretty straightforward. It won't require
much code yet, as we only support one SASL mechanism. We just need to
ensure that we don't paint ourselves in the corner with the protocol.
I think this could easily extended from the current message. SCRAM
does not force any concrete way of negotiating the protocols supported.
The current SASL message sends the only SCRAM method supported. I think
it should be enough to make this a CSV, which for a single value, is the
same as we have today (so no code change required, only documentation).
The other message that needs to be changed is the password one, the
first time the client sends the SCRAM "client-first-message", which
needs to contain the algorithm selected. As of today, that could be a
constant (at least in the code) and validate is value.
So I guess code changes would be minimal. I could be wrong, of course.
I'm working myself on Java's (pgjdbc) implementation, and I will
hopefully have a prototype by next week to try it.
�lvaro
--
�lvaro Hern�ndez Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 4 April 2017 at 02:02, Michael Paquier <michael.paquier@gmail.com> wrote:
Hi all,
There is still one open item pending for SCRAM that has not been
treated which is mentioned here:
/messages/by-id/b081887e-1712-3aa4-7dbe-e012333d50e4@iki.fiWhen doing an authentication with SASL, the server decides what is the
mechanism that the client has to use. As SCRAM-SHA-256 is only one of
such mechanisms, it would be nice to have something more generic and
have the server return to the client a list of protocols that the
client can choose from. And also the server achnowledge which protocol
is going to be used.Note that RFC4422 has some content on the matter
https://tools.ietf.org/html/rfc4422#section-3.1:
Mechanism negotiation is protocol specific.Commonly, a protocol will specify that the server advertises
supported and available mechanisms to the client via some facility
provided by the protocol, and the client will then select the "best"
mechanism from this list that it supports and finds suitable.So once the server sends back the list of mechanisms that are
supported, the client is free to use what it wants.On HEAD, a 'R' message with AUTH_REQ_SASL followed by
SCRAM_SHA256_NAME is sent to let the client know what is the mechanism
to use for the SASL exchange. In the future, this should be extended
so as a list of names is sent, for example a comma-separated list, but
we are free to choose the format we want here. With this list at hand,
the client can then choose the protocol it thinks is the best. Still,
there is a gap with our current implementation because the server
expects the first message from the client to have a SCRAM format, but
that's true only if SCRAM-SHA-256 is used as mechanism.
How would we provide the list of protocols? Surely the protocol is
defined by pg_hba.conf, which makes it dependent upon username,
database and ip range. If the list were accurate, it would allow an
attacker to discover how best to attack. If the list were inaccurate
it would just be an annoyance.
At minimum, providing the list of protocols means an extra round trip
to the server.
So while RFC4422 might say what "commonly" happens, I don't think it
applies sensibly to PostgreSQL, especially when we only have one
method.
ISTM that if you have a valid role to connect to then you'll also know
what authentication mechanism to use so you should be able to specify
the mechanism in your connection message and save the extra trip.
In order to cover this gap, it seems to me that we need to have an
intermediate state before the server is switched to FE_SCRAM_INIT so
as the mechanism used is negotiated between the two parties. Once the
protocol negotiation is done, the server can then move on with the
mechanism to use. This would be important in the future to allow more
SASL mechanisms to work. I am adding an open item for that.
So I don't see a gap or an open item on that point. I see a potential
future feature.
For extensibility, we may also want to revisit the choice of defining
'scram' in pg_hba.conf instead of 'sasl'...
I'd like to see us replace "scram" with "sasl=scram-sha-256".
So when we extend it in future, we already have the syntax in place to
support "sasl=newmethod", rather than introduce syntax that we already
know will become outdated in future.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Simon Riggs <simon@2ndquadrant.com> writes:
How would we provide the list of protocols? Surely the protocol is
defined by pg_hba.conf, which makes it dependent upon username,
database and ip range. If the list were accurate, it would allow an
attacker to discover how best to attack. If the list were inaccurate
it would just be an annoyance.
At minimum, providing the list of protocols means an extra round trip
to the server.
Yeah, that's a problem.
ISTM that if you have a valid role to connect to then you'll also know
what authentication mechanism to use so you should be able to specify
the mechanism in your connection message and save the extra trip.
I do not buy that in the least. It has never been the case before now
that clients know in advance what the auth challenge method will be.
If we put that requirement on them for SCRAM, we're just going to be
exporting a lot of pain and end-user-visible inconsistency.
Perhaps we could turn this around: have the client send (in the connection
request packet) a list of auth protocols it thinks it is able to handle.
(I'm envisioning this as being more or less fixed for any one version of
any one client, since it would basically mean "I have code to do X, Y, or
Z".) Then the server can pick one that is allowed by pg_hba.conf, or it
can just ignore the list and send what it wants anyway, probably leading
to client disconnect.
We could avoid this being a protocol break by having the server's default
assumption being that the client can handle all pre-SCRAM auth protocols.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 06/04/17 22:05, Tom Lane wrote:
Simon Riggs <simon@2ndquadrant.com> writes:
How would we provide the list of protocols? Surely the protocol is
defined by pg_hba.conf, which makes it dependent upon username,
database and ip range. If the list were accurate, it would allow an
attacker to discover how best to attack. If the list were inaccurate
it would just be an annoyance.
At minimum, providing the list of protocols means an extra round trip
to the server.Yeah, that's a problem.
I don't see it. The message AuthenticationSASL.String could contain
a CSV of the SCRAM protocols supported. This is specially important to
support channel binding (which is just another protocol name for this
matter), which is the really enhanced security mechanism of SCRAM. Since
this message is sent regardless, and the client replies with
PasswordMessage, no extra round trip is required. However,
PasswordMessage needs to also include a field with the name of the
selected protocol (it is the client who picks). Or a different message
would need to be created, but no extra round-trips more than those
required by SCRAM itself (4 messages for SCRAM + 1 extra for the server
to tell the client it needs to use SCRAM).
ISTM that if you have a valid role to connect to then you'll also know
what authentication mechanism to use so you should be able to specify
the mechanism in your connection message and save the extra trip.I do not buy that in the least. It has never been the case before now
that clients know in advance what the auth challenge method will be.
If we put that requirement on them for SCRAM, we're just going to be
exporting a lot of pain and end-user-visible inconsistency.Perhaps we could turn this around: have the client send (in the connection
request packet) a list of auth protocols it thinks it is able to handle.
Per the SCRAM RFC, it is the server who advertises and the client
who picks.
Regards,
�lvaro
--
�lvaro Hern�ndez Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 6 April 2017 at 16:05, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps we could turn this around: have the client send (in the connection
request packet) a list of auth protocols it thinks it is able to handle.
(I'm envisioning this as being more or less fixed for any one version of
any one client, since it would basically mean "I have code to do X, Y, or
Z".) Then the server can pick one that is allowed by pg_hba.conf,
+1
Much better plan.
or it
can just ignore the list and send what it wants anyway, probably leading
to client disconnect.
It would need to follow one of the requested protocols, but mark the
request as doomed. Otherwise we'd be revealing information. That's
what SCRAM does now.
Since the list is currently length one, we can add more later when we
get a list potentially > 1.
We could avoid this being a protocol break by having the server's default
assumption being that the client can handle all pre-SCRAM auth protocols.
+1
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Apr 7, 2017 at 5:15 AM, Álvaro Hernández Tortosa <aht@8kdata.com> wrote:
I don't see it. The message AuthenticationSASL.String could contain a
CSV of the SCRAM protocols supported. This is specially important to support
channel binding (which is just another protocol name for this matter), which
is the really enhanced security mechanism of SCRAM. Since this message is
sent regardless, and the client replies with PasswordMessage, no extra round
trip is required. However, PasswordMessage needs to also include a field
with the name of the selected protocol (it is the client who picks). Or a
different message would need to be created, but no extra round-trips more
than those required by SCRAM itself (4 messages for SCRAM + 1 extra for the
server to tell the client it needs to use SCRAM).
Yes, it seems to me that the list of protocols to send should be done
by sendAuthRequest(). Then the client parses the received string, and
sends an extra 'p' message with its choice before sending the first
SCRAM message. So there is no need for any extra round trips.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 6 April 2017 at 16:15, Álvaro Hernández Tortosa <aht@8kdata.com> wrote:
Per the SCRAM RFC, it is the server who advertises and the client who picks.
Yes, but what does the RFC say about how to handle servers with an pg_hba.conf?
How and what will we advertise?
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Apr 7, 2017 at 7:29 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
On 6 April 2017 at 16:15, Álvaro Hernández Tortosa <aht@8kdata.com> wrote:
Per the SCRAM RFC, it is the server who advertises and the client who picks.
Yes, but what does the RFC say about how to handle servers with an pg_hba.conf?
How and what will we advertise?
Did you read the first email of this thread? The RFC says that the
protocol implementers are free to do what they want as this is
protocol-specific. At least that's what I understand.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/06/2017 11:16 PM, Simon Riggs wrote:
or it
can just ignore the list and send what it wants anyway, probably leading
to client disconnect.It would need to follow one of the requested protocols, but mark the
request as doomed. Otherwise we'd be revealing information. That's
what SCRAM does now.
It's not a secret today, what authentication method the server requires.
You can't really hide it, anyway, as the client could probe with
different lists of supported methods, and see which method the server
picks in each case.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/06/2017 11:05 PM, Tom Lane wrote:
Perhaps we could turn this around: have the client send (in the connection
request packet) a list of auth protocols it thinks it is able to handle.
(I'm envisioning this as being more or less fixed for any one version of
any one client, since it would basically mean "I have code to do X, Y, or
Z".) Then the server can pick one that is allowed by pg_hba.conf, or it
can just ignore the list and send what it wants anyway, probably leading
to client disconnect.
That list of supported authentication methods would need to be included
in the startup message. Unfortunately, there is no way to add options to
the startup message, without breaking compatibility with old servers. If
there is an option in the startup message that the server doesn't
understand, it will treat it as a GUC, and you get an "unrecognized
configuration parameter" after authentication.
It would be nice to change that, so that the server would ignore
parameters that it doesn't understand that begin with "optional_"
prefix, for example. But it won't help us right now.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 7 April 2017 at 16:33, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
That list of supported authentication methods would need to be included in
the startup message. Unfortunately, there is no way to add options to the
startup message, without breaking compatibility with old servers. If there
is an option in the startup message that the server doesn't understand, it
will treat it as a GUC, and you get an "unrecognized configuration
parameter" after authentication.
sasl.mechanisms = 'SCRAM_SHA256'
:p
No, I'm not seriously suggesting we abuse that.
Personally I think it's reasonable enough to let the server send a 'B'
message with supported auth modes. I'm not overly concerned about the
small information leak that provides. We're unlikely to be able to
convincingly fake execution of any and all SASL auth methods the
client may request, and since they may require any arbitrary number of
message exchanges we'd basically land up blackholeing clients that try
an unsupported auth-method.
No thanks. It's one area I'd rather honestly say "nope, we don't
support that". In which case the client can easily enough probe for
all known methods, and we might as well just tell it up front.
This is hardly new. Most servers with neotiable auth, like SMTP, IMAP,
etc, have the server supply a list of auth mechs.
--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/07/2017 11:57 AM, Craig Ringer wrote:
On 7 April 2017 at 16:33, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
That list of supported authentication methods would need to be included in
the startup message. Unfortunately, there is no way to add options to the
startup message, without breaking compatibility with old servers. If there
is an option in the startup message that the server doesn't understand, it
will treat it as a GUC, and you get an "unrecognized configuration
parameter" after authentication.sasl.mechanisms = 'SCRAM_SHA256'
:p
No, I'm not seriously suggesting we abuse that.
Hmm, that's not such a bad idea, actually. It only goes back to 9.2,
though. Before that, the prefix needed to be listed in
custom_variable_classes, or you got an error. 9.2 is the oldest
supported version, but libpq should still be able to connect to older
versions.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/07/2017 01:13 AM, Michael Paquier wrote:
On Fri, Apr 7, 2017 at 5:15 AM, Álvaro Hernández Tortosa <aht@8kdata.com> wrote:
I don't see it. The message AuthenticationSASL.String could contain a
CSV of the SCRAM protocols supported. This is specially important to support
channel binding (which is just another protocol name for this matter), which
is the really enhanced security mechanism of SCRAM. Since this message is
sent regardless, and the client replies with PasswordMessage, no extra round
trip is required. However, PasswordMessage needs to also include a field
with the name of the selected protocol (it is the client who picks). Or a
different message would need to be created, but no extra round-trips more
than those required by SCRAM itself (4 messages for SCRAM + 1 extra for the
server to tell the client it needs to use SCRAM).Yes, it seems to me that the list of protocols to send should be done
by sendAuthRequest(). Then the client parses the received string, and
sends an extra 'p' message with its choice before sending the first
SCRAM message. So there is no need for any extra round trips.
I started writing down the protocol docs, based on the above idea. See
attached. The AuthenticationSASL message now contains a list of mechanisms.
Does that seem clear to you? If so, I'll change the code to match the
attached docs.
I added two new message formats to the docs, SASLResponse and
SASLInitialResponse. Those use the same type byte as PasswordMessage,
'p', but I decided to describe them as separate messages for
documentation purposes, since the content is completely different
depending on whether the message is sent as part of SASL, GSS, md5, or
password authentication. IOW, this is not a change in the
implementation, only in the way it's documented.
While working on this, and reading the RFCs more carefully, I noticed
one detail we should change, to be spec-complicant. The SASL spec
specifies that a SASL authentication exchange consists of
challenge-response pairs. There must be a response to each challenge. If
the last message in the authentication mechanism (SCRAM in this case)
goes in the server->client direction, then that message must sent as
"additional data" in the server->client message that tells the client
that the authentication was successful. That's AuthenticationOK in the
PostgreSQL protocol. In the current implementation, the
server-final-message is sent as an AuthenticationSASLContinue message,
and the client doesn't respond to that.
We should change that, so that the server-final-message is sent as
"additional data" in the AuthenticationOK message. The attached docs
patch describes that, rather than what the current implementation does.
(For your convenience, I built the HTML docs with this patch, and put
them up at http://hlinnaka.iki.fi/temp/scram-wip-docs/protocol.html for
viewing)
- Heikki
Attachments:
0001-Add-docs-for-SASL-authentication-in-protocol.patchinvalid/octet-stream; name=0001-Add-docs-for-SASL-authentication-in-protocol.patchDownload
From 212af2998f87734e4d057d26db3c5221e0f7432f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 10 Apr 2017 15:49:04 +0300
Subject: [PATCH 1/1] Add docs for SASL authentication in protocol.
---
doc/src/sgml/protocol.sgml | 260 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 258 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 4e8bb32d33..3bc62a4b82 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1326,6 +1326,119 @@
</sect2>
</sect1>
+<sect1 id="sasl-authentication">
+<title>SASL Authentication</title>
+
+<para>
+SASL is a framework for authentication in connection-oriented protocols.
+At the moment, PostgreSQL implements only one SASL authentication mechanism,
+SCRAM-SHA-256, but more might be added in the future. The below steps
+illustrate how SASL authentication is performed in general, while the next
+subsection gives more details on SCRAM-SHA-256.
+</para>
+
+<procedure>
+<title>SASL Authentication Message Flow</title>
+<step id="sasl-auth-begin">
+<para>
+ To begin an SASL authentication exchange, the server sends the client an
+ AuthenticationSASL message. It includes a list of SASL authentication
+ mechanisms that the server can accept, in the server's preferred order.
+</para>
+<step id="sasl-auth-initial-response">
+<para>
+ The client selects one of the supported mechanisms from the list, and sends
+ a SASLInitialResponse message to the server. The message includes the name
+ of the selected mechanism, and possibly an Initial Client Response, if the
+ selected mechanism supports that.
+</para>
+
+<step id="sasl-auth-continue">
+<para>
+ One or more server-challenge and client-response message will follow. Each
+ server-challenge is sent in an AuthenticationSASLContinue message, followed
+ by a response from client in an SASLResponse message. The particulars of
+ the messages are mechanism specific.
+</para>
+
+<step id="sasl-auth-end">
+<para>
+ Finally, when the authentication exchange is completed successfully, the
+ server sends an AuthenticationOK message. The AuthenticationOK message can
+ include an "additional data" field, whose content is particular to the
+ selected authentication mechanism.
+</para>
+
+ <sect2 id="sasl-scram-sha256">
+ <title>SCRAM-SHA-256 authentication</title>
+
+ <para>
+ SCRAM-SHA-256 is the only implemented SASL mechanism, at the moment. It is
+ described in detail in [RFC7677] and [RFC5741]. (called just SCRAM from now on)
+ </para>
+
+ <para>
+When SCRAM-SHA-256 is used in PostgreSQL, the server will ignore the username
+that the client sends in the client-first-message. The username that was
+already sent in the startup message is used instead. PostgreSQL supports
+multiple character encodings, while SCRAM dictates UTF-8 to be used for the
+username, so it might be impossible to represent the PostgreSQL username in
+UTF-8. To avoid confusion, the client should use an empty string as the
+username in the client-first-message.
+ </para>
+
+ <para>
+The SCRAM specification dictates that the password is also in UTF-8, and is
+processed with the SASLprep algorithm. PostgreSQL, however, does not require
+UTF-8 to be used for the password. When a user's password is set, it is
+processed with SASLprep as if it was in UTF-8, regardless of the actual
+encoding used. However, if it is not a legal UTF-8 byte sequence, or it
+contains UTF-8 byte sequences that are prohibited by the SASLprep algorithm,
+the raw password will be used without SASLprep processing, instead of
+throwing an error. This allows the password to be normalized when it is in
+UTF-8, but still allows a non-UTF-8 password to be used, and doesn't require
+the system to know which encoding the password is in.
+ </para>
+
+ <para>
+Channel binding has not been implemented yet.
+ </para>
+
+ <para>
+Example:
+
+<procedure>
+<step id="scram-begin">
+<para>
+ The server sends an AuthenticationSASL message. It includes of a
+ list of SASL authentication mechanisms that the server can accept.
+</para>
+<step id="scram-client-first">
+<para>
+ The client responds by sending a SASLInitialResponse message, which
+ indicates the chosen mechanism, <literal>SCRAM-SHA-256</>. In the Initial
+ Client response field, the message containts a SCRAM
+ <literal>client-first-message</>, as specified by [RFC5802] and [RFC7677].
+</para>
+<step id="scram-server-first">
+<para>
+ Server sends an AuthenticationSASLContinue message, with a SCRAM
+ <literal>server-first message</literal> as the content.
+</para>
+<step id="scram-client-final">
+<para>
+ Client sends a SASLResponse messsage, with SCRAM
+ <literal>client-final-message</literal> as the content.
+</para>
+<step id="scram-server-final">
+<para>
+ Server sends an AuthenticationOK message, with the SCRAM
+ <literal>server-final-message</> in the "additional data" field.
+</para>
+
+</sect2>
+
+
<sect1 id="protocol-replication">
<title>Streaming Replication Protocol</title>
@@ -2501,7 +2614,7 @@ AuthenticationOk (B)
</varlistentry>
<varlistentry>
<term>
- Int32(8)
+ Int32
</term>
<listitem>
<para>
@@ -2519,6 +2632,29 @@ AuthenticationOk (B)
</para>
</listitem>
</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of authentication method specific "additional
+ data" that follows. If this message contains no additional
+ data, this field is omitted.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+ Authentication method specific "additional data". If this
+ message contains no additional data, this field is omitted.
+</para>
+</listitem>
+</varlistentry>
</variablelist>
</para>
@@ -2894,13 +3030,19 @@ AuthenticationSASL (B)
</para>
</listitem>
</varlistentry>
+</variablelist>
+The message body is a list of SASL authentication mechanisms, in the
+server's order of preference. A zero byte is required as terminator after
+the last authentication mechanism name. For each mechanism, there is the
+following:
+<variablelist>
<varlistentry>
<term>
String
</term>
<listitem>
<para>
- Name of a SASL authentication mechanism.
+ Name of SASL authentication mechanism.
</para>
</listitem>
</varlistentry>
@@ -4761,6 +4903,120 @@ PasswordMessage (F)
<varlistentry>
<term>
+SASLInitialresponse (F)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+ Byte1('p')
+</term>
+<listitem>
+<para>
+ Identifies the message as an initial SASL response. Note that
+ this is also used for GSSAPI, SSPI and password response messages.
+ The message type is deduced from the context.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ String
+</term>
+<listitem>
+<para>
+ Name of the SASL authentication mechanism that the client
+ selected.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of SASL mechanism specific "Initial Response" that
+ follows, or -1 if there is no Initial Response.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+ SASL mechanism specific "Initial Response".
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+
+<varlistentry>
+<term>
+SASLResponse (F)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+ Byte1('p')
+</term>
+<listitem>
+<para>
+ Identifies the message as a SASL response. Note that
+ this is also used for GSSAPI, SSPI and password response messages.
+ The message type can be deduced from the context.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+ SASL mechanism specific message data.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+
+<varlistentry>
+<term>
PortalSuspended (B)
</term>
<listitem>
--
2.11.0
On 10/04/17 14:57, Heikki Linnakangas wrote:
On 04/07/2017 01:13 AM, Michael Paquier wrote:
On Fri, Apr 7, 2017 at 5:15 AM, Álvaro Hernández Tortosa
<aht@8kdata.com> wrote:I don't see it. The message AuthenticationSASL.String could
contain a
CSV of the SCRAM protocols supported. This is specially important to
support
channel binding (which is just another protocol name for this
matter), which
is the really enhanced security mechanism of SCRAM. Since this
message is
sent regardless, and the client replies with PasswordMessage, no
extra round
trip is required. However, PasswordMessage needs to also include a
field
with the name of the selected protocol (it is the client who picks).
Or a
different message would need to be created, but no extra round-trips
more
than those required by SCRAM itself (4 messages for SCRAM + 1 extra
for the
server to tell the client it needs to use SCRAM).Yes, it seems to me that the list of protocols to send should be done
by sendAuthRequest(). Then the client parses the received string, and
sends an extra 'p' message with its choice before sending the first
SCRAM message. So there is no need for any extra round trips.I started writing down the protocol docs, based on the above idea. See
attached. The AuthenticationSASL message now contains a list of
mechanisms.Does that seem clear to you? If so, I'll change the code to match the
attached docs.I added two new message formats to the docs, SASLResponse and
SASLInitialResponse. Those use the same type byte as PasswordMessage,
'p', but I decided to describe them as separate messages for
documentation purposes, since the content is completely different
depending on whether the message is sent as part of SASL, GSS, md5, or
password authentication. IOW, this is not a change in the
implementation, only in the way it's documented.While working on this, and reading the RFCs more carefully, I noticed
one detail we should change, to be spec-complicant. The SASL spec
specifies that a SASL authentication exchange consists of
challenge-response pairs. There must be a response to each challenge.
If the last message in the authentication mechanism (SCRAM in this
case) goes in the server->client direction, then that message must
sent as "additional data" in the server->client message that tells the
client that the authentication was successful. That's AuthenticationOK
in the PostgreSQL protocol. In the current implementation, the
server-final-message is sent as an AuthenticationSASLContinue message,
and the client doesn't respond to that.We should change that, so that the server-final-message is sent as
"additional data" in the AuthenticationOK message. The attached docs
patch describes that, rather than what the current implementation does.(For your convenience, I built the HTML docs with this patch, and put
them up at http://hlinnaka.iki.fi/temp/scram-wip-docs/protocol.html
for viewing)- Heikki
Thanks for posting the patched HTML. In my opinion, all looks good
except that:
- I will add an extra String (a CSV) to AuthenticationSASL message for
channel binding names, so that message format can remain without changes
when channel binding is implemented. It can be empty.
- If the username used is the one sent in the startup message, rather
than leaving it empty in the client-first-message, I would force it to
be the same as the used in the startuo message. Otherwise we may confuse
some client implementations which would probably consider that as an
error; for one, my implementation would currently throw an error if
username is empty, and I think that's correct.
Álvaro
--
Álvaro Hernández Tortosa
-----------
<8K>data
On 04/10/2017 09:33 PM, Álvaro Hernández Tortosa wrote:
Thanks for posting the patched HTML. In my opinion, all looks good
except that:- I will add an extra String (a CSV) to AuthenticationSASL message for
channel binding names, so that message format can remain without changes
when channel binding is implemented. It can be empty.
Note that SCRAM-SHA-256 with channel binding has a different SASL
mechanism name, SRAM-SHA-256-PLUS. No need for a separate flag or string
for channel binding. When support for channel binding is added to the
server, it will advertise two SASL mechanisms in the AuthenticationSASL
message, SCRAM-SHA-256 and SCRAM-SHA-256-PLUS. (Or just
SCRAM-SHA-256-PLUS, if channel-binding is required).
- If the username used is the one sent in the startup message, rather
than leaving it empty in the client-first-message, I would force it to
be the same as the used in the startuo message.
The problem with that is that the SCRAM spec dictates that the username
must be encoded in UTF-8, but PostgreSQL supports non-UTF-8 usernames.
Or did you mean that, if the username is sent, it must match the one in
the startup packet, but an empty string would always be allowed? That
would be reasonable.
Otherwise we may confuse
some client implementations which would probably consider that as an
error; for one, my implementation would currently throw an error if
username is empty, and I think that's correct.
I'm not sure I follow. The username is sent from client to server, and
currently, the server will ignore it. If you're writing a client
library, it can send whatever it wants. (Although again I would
recommend an empty string, to avoid confusion. Sending the same username
as in the startup packet, as long as it's in UTF-8, seems reasonable too.)
Thanks for reviewing this! I'll start hacking on code changes to go with
these docs.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/04/17 21:41, Heikki Linnakangas wrote:
On 04/10/2017 09:33 PM, Álvaro Hernández Tortosa wrote:
Thanks for posting the patched HTML. In my opinion, all looks good
except that:- I will add an extra String (a CSV) to AuthenticationSASL message for
channel binding names, so that message format can remain without changes
when channel binding is implemented. It can be empty.Note that SCRAM-SHA-256 with channel binding has a different SASL
mechanism name, SRAM-SHA-256-PLUS. No need for a separate flag or
string for channel binding. When support for channel binding is added
to the server, it will advertise two SASL mechanisms in the
AuthenticationSASL message, SCRAM-SHA-256 and SCRAM-SHA-256-PLUS. (Or
just SCRAM-SHA-256-PLUS, if channel-binding is required).
Channel binding needs to specify actually three things:
- The mechanism, which is indeed suffixed "-PLUS".
- The channel binding name, which is described here:
https://tools.ietf.org/html/rfc5056. Types are also IANA-registered (see
https://www.iana.org/assignments/channel-binding-types/channel-binding-types.xhtml).
SCRAM mandates to implement 'tls-unique', but other channel binding
types could be supported (like 'tls-server-end-point' for example).
- The channel binding data, which is channel binding mechanism
dependent, and is sent as part of the client last message.
What I'm talking about here is the second one, the channel binding
type (name).
- If the username used is the one sent in the startup message, rather
than leaving it empty in the client-first-message, I would force it to
be the same as the used in the startuo message.The problem with that is that the SCRAM spec dictates that the
username must be encoded in UTF-8, but PostgreSQL supports non-UTF-8
usernames.Or did you mean that, if the username is sent, it must match the one
in the startup packet, but an empty string would always be allowed?
That would be reasonable.Otherwise we may confuse
some client implementations which would probably consider that as an
error; for one, my implementation would currently throw an error if
username is empty, and I think that's correct.I'm not sure I follow. The username is sent from client to server, and
currently, the server will ignore it. If you're writing a client
library, it can send whatever it wants. (Although again I would
recommend an empty string, to avoid confusion. Sending the same
username as in the startup packet, as long as it's in UTF-8, seems
reasonable too.)
OK, understood. I will not let then the SCRAM implementation I'm
writing to allow for empty string as the user name, but in the pgjdbc
glue code send "ignore" as the user name or something like that ;P
Thanks for reviewing this! I'll start hacking on code changes to go
with these docs.
Thanks for writing the code :)
Álvaro
--
Álvaro Hernández Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Apr 11, 2017 at 4:41 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 04/10/2017 09:33 PM, Álvaro Hernández Tortosa wrote:
- If the username used is the one sent in the startup message, rather
than leaving it empty in the client-first-message, I would force it to
be the same as the used in the startuo message.The problem with that is that the SCRAM spec dictates that the username must
be encoded in UTF-8, but PostgreSQL supports non-UTF-8 usernames.Or did you mean that, if the username is sent, it must match the one in the
startup packet, but an empty string would always be allowed? That would be
reasonable.
That sounds sensible from here.
Otherwise we may confuse
some client implementations which would probably consider that as an
error; for one, my implementation would currently throw an error if
username is empty, and I think that's correct.I'm not sure I follow. The username is sent from client to server, and
currently, the server will ignore it. If you're writing a client library, it
can send whatever it wants. (Although again I would recommend an empty
string, to avoid confusion. Sending the same username as in the startup
packet, as long as it's in UTF-8, seems reasonable too.)Thanks for reviewing this! I'll start hacking on code changes to go with
these docs.
Sounds good to me, thanks! I looked at the doc patch for now, and here
are some nits.
+ <para>
+ SCRAM-SHA-256 is the only implemented SASL mechanism, at the moment. It is
+ described in detail in [RFC7677] and [RFC5741]. (called just
SCRAM from now on)
+ </para>
Perhaps those should be links to the RFCs.
+<para>
+ Client sends a SASLResponse messsage, with SCRAM
+ <literal>client-final-message</literal> as the content.
+</para>
Typo. Message needs two 's'.
+ <literal>server-final-message</> in the "additional data" field.
+</para>
+
+</sect2>
+
I think that you are missing a </sect1> markup here.
+<listitem>
+<para>
+ Authentication method specific "additional data". If this
+ message contains no additional data, this field is omitted.
+</para>
+</listitem>
So that's for the first message generated by the client in the
exchange. Better than my previous idea. Shouldn't double quotes be
replaced by proper markups?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/10/2017 11:03 PM, Álvaro Hernández Tortosa wrote:
Channel binding needs to specify actually three things:
- The mechanism, which is indeed suffixed "-PLUS".
- The channel binding name, which is described here:
https://tools.ietf.org/html/rfc5056. Types are also IANA-registered (see
https://www.iana.org/assignments/channel-binding-types/channel-binding-types.xhtml).
SCRAM mandates to implement 'tls-unique', but other channel binding
types could be supported (like 'tls-server-end-point' for example).
- The channel binding data, which is channel binding mechanism
dependent, and is sent as part of the client last message.What I'm talking about here is the second one, the channel binding
type (name).
Oh, I see. According to the SCRAM RFC, "tls-unique" is used by default.
I don't see us implementing anything else any time soon. PostgreSQL
doesn't support any other "channel type" than TLS, and tls-unique is the
only one that makes sense for TLS.
If we do need it after all, the server could advertise the additional
channel binding types as additional SASL mechanisms in the
AuthenticationSASL message, maybe something like:
"SCRAM-SHA-256"
"SCRAM-SHA-256-PLUS" (for tls-unique)
"SCRAM-SHA-256-PLUS ssh-unique" (for hypothetical ssh channel binding)
The same trick can be used to advertise any other SASL mechanism
specific options, if needed in the future.
I'm not sure I follow. The username is sent from client to server, and
currently, the server will ignore it. If you're writing a client
library, it can send whatever it wants. (Although again I would
recommend an empty string, to avoid confusion. Sending the same
username as in the startup packet, as long as it's in UTF-8, seems
reasonable too.)OK, understood. I will not let then the SCRAM implementation I'm
writing to allow for empty string as the user name, but in the pgjdbc
glue code send "ignore" as the user name or something like that ;P
Hmm, so the SASL library you're using doesn't like sending an empty
string as the username? Now that I look at RFC5802 more closely, it says:
If the preparation of the username fails or results in an empty
string, the server SHOULD abort the authentication exchange.
Perhaps we should reserve a magic user name to mean "same as startup
message", in addition or instead of the empty string. We actually
discussed that already at [1]/messages/by-id/551A8C2F.7050409@iki.fi, but we forgot about it since.
[1]: /messages/by-id/551A8C2F.7050409@iki.fi
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/04/17 08:50, Heikki Linnakangas wrote:
On 04/10/2017 11:03 PM, �lvaro Hern�ndez Tortosa wrote:
Channel binding needs to specify actually three things:
- The mechanism, which is indeed suffixed "-PLUS".
- The channel binding name, which is described here:
https://tools.ietf.org/html/rfc5056. Types are also IANA-registered (see
https://www.iana.org/assignments/channel-binding-types/channel-binding-types.xhtml).SCRAM mandates to implement 'tls-unique', but other channel binding
types could be supported (like 'tls-server-end-point' for example).
- The channel binding data, which is channel binding mechanism
dependent, and is sent as part of the client last message.What I'm talking about here is the second one, the channel binding
type (name).Oh, I see. According to the SCRAM RFC, "tls-unique" is used by
default. I don't see us implementing anything else any time soon.
PostgreSQL doesn't support any other "channel type" than TLS, and
tls-unique is the only one that makes sense for TLS.If we do need it after all, the server could advertise the additional
channel binding types as additional SASL mechanisms in the
AuthenticationSASL message, maybe something like:"SCRAM-SHA-256"
"SCRAM-SHA-256-PLUS" (for tls-unique)
"SCRAM-SHA-256-PLUS ssh-unique" (for hypothetical ssh channel binding)The same trick can be used to advertise any other SASL mechanism
specific options, if needed in the future.
Why not add an extra field to the message? This scheme has in my
opinion some disadvantages:
- You assume no extensibility. Maybe Postgres will implement other
mechanisms for channel binding. Maybe not. But why limit ourselves?
- Apart from tls-unique there are others today, like
tls-server-end-point and who knows if in the future TLS 1.x comes with
something like 'tls-unique-1.x'
- Why have to parse the string (separated by spaces) when you could use
different fields and have no parsing at all?
- How do you advertise different SCRAM mechanisms with different channel
binding types? And a mix of SCRAM mechanisms with and without channel
binding? If I got it right, with your proposal it would be something like:
Field 1: SCRAM-SHA-256,SCRAM-SHA-256-PLUS tls-unique,SCRAM-SHA-1-PLUS
tls-unique,SCRAM-SHA-512-PLUS tls-unique
(which is basically a CSV of pairs where the right part of the pair
might be empty; too much IMHO for a single field)
vs
Field 1:
SCRAM-SHA-256,SCRAM-SHA-256-PLUS,SCRAM-SHA-1-PLUS,SCRAM-SHA-512-PLUS
(simple CSV)
Field 2: tls-unique (String)
Is there any reason not to use two fields? AuthenticationSASL is a
new message, I guess we can freely choose its format, right?
I'm not sure I follow. The username is sent from client to server, and
currently, the server will ignore it. If you're writing a client
library, it can send whatever it wants. (Although again I would
recommend an empty string, to avoid confusion. Sending the same
username as in the startup packet, as long as it's in UTF-8, seems
reasonable too.)OK, understood. I will not let then the SCRAM implementation I'm
writing to allow for empty string as the user name, but in the pgjdbc
glue code send "ignore" as the user name or something like that ;PHmm, so the SASL library you're using doesn't like sending an empty
string as the username? Now that I look at RFC5802 more closely, it says:If the preparation of the username fails or results in an empty
string, the server SHOULD abort the authentication exchange.Perhaps we should reserve a magic user name to mean "same as startup
message", in addition or instead of the empty string. We actually
discussed that already at [1], but we forgot about it since.
That works. Please let me know what is the "magic constant" chosen ;P
Thanks,
�lvaro
--
�lvaro Hern�ndez Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/11/2017 11:55 AM, �lvaro Hern�ndez Tortosa wrote:
On 11/04/17 08:50, Heikki Linnakangas wrote:
Oh, I see. According to the SCRAM RFC, "tls-unique" is used by
default. I don't see us implementing anything else any time soon.
PostgreSQL doesn't support any other "channel type" than TLS, and
tls-unique is the only one that makes sense for TLS.If we do need it after all, the server could advertise the additional
channel binding types as additional SASL mechanisms in the
AuthenticationSASL message, maybe something like:"SCRAM-SHA-256"
"SCRAM-SHA-256-PLUS" (for tls-unique)
"SCRAM-SHA-256-PLUS ssh-unique" (for hypothetical ssh channel binding)The same trick can be used to advertise any other SASL mechanism
specific options, if needed in the future.Why not add an extra field to the message? This scheme has in my
opinion some disadvantages:- You assume no extensibility. Maybe Postgres will implement other
mechanisms for channel binding. Maybe not. But why limit ourselves?
- Apart from tls-unique there are others today, like
tls-server-end-point and who knows if in the future TLS 1.x comes with
something like 'tls-unique-1.x'
- Why have to parse the string (separated by spaces) when you could use
different fields and have no parsing at all?
I don't think an option separated by space is any more difficult to
parse than a separate field. I'm envisioning that the "parsing" would be
simply:
if (strcmp(sasl_mechanism, "SCRAM-SHA-256") == 0) { ... }
else if (strcmp(sasl_mechanism, "SCRAM-SHA-256-PLUS") == 0) { ... }
else if (strcmp(sasl_mechanism, "SCRAM-SHA-256-PLUS tls-awesome") == 0)
{ ... }
This can be extended for more complicated options, if necessary.
Although if we find that we need a dozen different options, I think
we've done something wrong.
- How do you advertise different SCRAM mechanisms with different channel
binding types? And a mix of SCRAM mechanisms with and without channel
binding? If I got it right, with your proposal it would be something like:Field 1: SCRAM-SHA-256,SCRAM-SHA-256-PLUS tls-unique,SCRAM-SHA-1-PLUS
tls-unique,SCRAM-SHA-512-PLUS tls-unique(which is basically a CSV of pairs where the right part of the pair
might be empty; too much IMHO for a single field)
Yes, except that in my proposal, the list is not a comma-separated
string, but a list of null-terminated strings, similar to how some other
lists of options in the FE/BE protocol are transmitted.
vs
Field 1:
SCRAM-SHA-256,SCRAM-SHA-256-PLUS,SCRAM-SHA-1-PLUS,SCRAM-SHA-512-PLUS
(simple CSV)
Field 2: tls-unique (String)
What if tls-unique is only supported with SCRAM-SHA-256-PLUS, while
SCRAM-SHA-512-PLUS requires tls-awesome? What about possible other
options you might need to tack on to mechanisms? This seems less
flexible. I'm not saying that either of those cases is very likely, but
I don't think it's very likely we'll need extra options or different
channel binding types any time soon, anyway.
Is there any reason not to use two fields? AuthenticationSASL is a
new message, I guess we can freely choose its format, right?
Yes, we can choose the format freely.
In summary, I think the simple list of mechanism names is best, because:
* It's simple, and doesn't have any extra fields or options that are not
needed right now.
* It's flexible, for future extension. We can add new mechanism entries
with extra options, not just new channel binding types, if needed, and
existing clients (i.e. v10 clients) will ignore them.
Perhaps we should reserve a magic user name to mean "same as startup
message", in addition or instead of the empty string. We actually
discussed that already at [1], but we forgot about it since.That works. Please let me know what is the "magic constant" chosen ;P
I was hoping you'd have some good suggestions :-). Unfortunately there
is no reserved username prefix or anything like that, so whatever we
choose might also be a real username. That's OK, but it could be
confusing, if we ever tried to do something different with the SASL
username. How about "pg_same_as_startup_message"?
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/04/17 12:23, Heikki Linnakangas wrote:
On 04/11/2017 11:55 AM, �lvaro Hern�ndez Tortosa wrote:
On 11/04/17 08:50, Heikki Linnakangas wrote:
Oh, I see. According to the SCRAM RFC, "tls-unique" is used by
default. I don't see us implementing anything else any time soon.
PostgreSQL doesn't support any other "channel type" than TLS, and
tls-unique is the only one that makes sense for TLS.If we do need it after all, the server could advertise the additional
channel binding types as additional SASL mechanisms in the
AuthenticationSASL message, maybe something like:"SCRAM-SHA-256"
"SCRAM-SHA-256-PLUS" (for tls-unique)
"SCRAM-SHA-256-PLUS ssh-unique" (for hypothetical ssh channel binding)The same trick can be used to advertise any other SASL mechanism
specific options, if needed in the future.Why not add an extra field to the message? This scheme has in my
opinion some disadvantages:- You assume no extensibility. Maybe Postgres will implement other
mechanisms for channel binding. Maybe not. But why limit ourselves?
- Apart from tls-unique there are others today, like
tls-server-end-point and who knows if in the future TLS 1.x comes with
something like 'tls-unique-1.x'
- Why have to parse the string (separated by spaces) when you could use
different fields and have no parsing at all?I don't think an option separated by space is any more difficult to
parse than a separate field. I'm envisioning that the "parsing" would
be simply:if (strcmp(sasl_mechanism, "SCRAM-SHA-256") == 0) { ... }
else if (strcmp(sasl_mechanism, "SCRAM-SHA-256-PLUS") == 0) { ... }
else if (strcmp(sasl_mechanism, "SCRAM-SHA-256-PLUS tls-awesome") ==
0) { ... }This can be extended for more complicated options, if necessary.
Although if we find that we need a dozen different options, I think
we've done something wrong.- How do you advertise different SCRAM mechanisms with different channel
binding types? And a mix of SCRAM mechanisms with and without channel
binding? If I got it right, with your proposal it would be something
like:Field 1: SCRAM-SHA-256,SCRAM-SHA-256-PLUS tls-unique,SCRAM-SHA-1-PLUS
tls-unique,SCRAM-SHA-512-PLUS tls-unique(which is basically a CSV of pairs where the right part of the pair
might be empty; too much IMHO for a single field)Yes, except that in my proposal, the list is not a comma-separated
string, but a list of null-terminated strings, similar to how some
other lists of options in the FE/BE protocol are transmitted.
The fact that you null terminate them (fine with me) doesn't change
my reasoning. How do you separate multiple channel binding methods? And
do you realize that you will be repeating the channel binding methods
without reason? A contrived but legal, possible example:
Field1:
SCRAM-SHA-256\0
SCRAM-SHA-512\0
SCRAM-SHA-999\0
SCRAM-SHA-256-PLUS tls-unique tls-awesome yet-another-tls\0
SCRAM-SHA-512-PLUS tls-unique tls-awesome yet-another-tls\0
SCRAM-SHA-999-PLUS tls-unique tls-awesome yet-another-tls\0
vs
Field 1:
SCRAM-SHA-256 SCRAM-SHA-512 SCRAM-SHA-999 SCRAM-SHA-256-PLUS
SCRAM-SHA-512-PLUS SCRAM-SHA-999-PLUS\0
Field 2:
tls-unique tls-awesome yet-another-tls\0
Parsing and validation of the first option is significantly more
complex than the second option.
vs
Field 1:
SCRAM-SHA-256,SCRAM-SHA-256-PLUS,SCRAM-SHA-1-PLUS,SCRAM-SHA-512-PLUS
(simple CSV)
Field 2: tls-unique (String)What if tls-unique is only supported with SCRAM-SHA-256-PLUS, while
SCRAM-SHA-512-PLUS requires tls-awesome?
It can't happen. The RFC clearly states that they are orthogonal.
It is left to the implementations support one or the other, but no
reason to limit applicability of a given binding method to a given SCRAM
mechanisms (or viceversa).
What about possible other options you might need to tack on to
mechanisms? This seems less flexible. I'm not saying that either of
those cases is very likely, but I don't think it's very likely we'll
need extra options or different channel binding types any time soon,
anyway.
As I said, channel binding and SCRAM mechanisms are orthogonal.
On the flip side, a contrived parsing mechanism with optional
fields and many that would be repeated all the time seems far from
ideal. And far less clear.
Is there any reason not to use two fields? AuthenticationSASL is a
new message, I guess we can freely choose its format, right?Yes, we can choose the format freely.
Cool.
In summary, I think the simple list of mechanism names is best, because:
* It's simple, and doesn't have any extra fields or options that are
not needed right now.
I think it is too complicated and mixing things that are
orthogonal. Since protocol is hard to extend, adding an extra field for
the channel binding mechanisms -even if unused as of PG 10- is a good
thing. If you need to change the protocol later, that's a very bad thing
(realistically, we all know it won't be changed and some hack would need
to be implemented).
* It's flexible, for future extension. We can add new mechanism
entries with extra options, not just new channel binding types, if
needed, and existing clients (i.e. v10 clients) will ignore them.
I think the extra field allows for much more extension possibilities.
Having said all this, choice is yours :)
Perhaps we should reserve a magic user name to mean "same as startup
message", in addition or instead of the empty string. We actually
discussed that already at [1], but we forgot about it since.That works. Please let me know what is the "magic constant" chosen ;P
I was hoping you'd have some good suggestions :-). Unfortunately there
is no reserved username prefix or anything like that, so whatever we
choose might also be a real username. That's OK, but it could be
confusing, if we ever tried to do something different with the SASL
username. How about "pg_same_as_startup_message"?
It doesn't matter if it conflicts with a real name, since it will
be ignored anyway ^_^ So I'd either pick a short string for saving a few
bytes (something like "*") or something that looks cool like a function
name like "pg.get_from_startup_message()" ^_^ (I prefer the former)
�lvaro
--
�lvaro Hern�ndez Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/11/2017 01:39 PM, �lvaro Hern�ndez Tortosa wrote:
The fact that you null terminate them (fine with me) doesn't change
my reasoning. How do you separate multiple channel binding methods? And
do you realize that you will be repeating the channel binding methods
without reason? A contrived but legal, possible example:Field1:
SCRAM-SHA-256\0
SCRAM-SHA-512\0
SCRAM-SHA-999\0
SCRAM-SHA-256-PLUS tls-unique tls-awesome yet-another-tls\0
SCRAM-SHA-512-PLUS tls-unique tls-awesome yet-another-tls\0
SCRAM-SHA-999-PLUS tls-unique tls-awesome yet-another-tls\0
I was actually thinking of:
SCRAM-SHA-256\0
SCRAM-SHA-512\0
SCRAM-SHA-999\0
SCRAM-SHA-256-PLUS\0
SCRAM-SHA-256-PLUS tls-awesome\0
SCRAM-SHA-256-PLUS yet-another-tls\0
SCRAM-SHA-512-PLUS\0
SCRAM-SHA-512-PLUS tls-awesome\0
SCRAM-SHA-512-PLUS yet-another-tls\0
SCRAM-SHA-999-PLUS\0
SCRAM-SHA-999-PLUS tls-awesome\0
SCRAM-SHA-999-PLUS yet-another-tls\0
In practice, I don't foresee us having this many options, so the
verbosity won't be an issue. Parsing this is very straightforward.
vs
Field 1:
SCRAM-SHA-256,SCRAM-SHA-256-PLUS,SCRAM-SHA-1-PLUS,SCRAM-SHA-512-PLUS
(simple CSV)
Field 2: tls-unique (String)What if tls-unique is only supported with SCRAM-SHA-256-PLUS, while
SCRAM-SHA-512-PLUS requires tls-awesome?It can't happen. The RFC clearly states that they are orthogonal.
It is left to the implementations support one or the other, but no
reason to limit applicability of a given binding method to a given SCRAM
mechanisms (or viceversa).
Well, if tls-unique is found to be insecure, a future SCRAM-SHA-512-PLUS
spec might well define that the default for that mechanism is
tls-unique-new-and-secure rather than tls-unique. Maybe even forbid
using tls-unique altogether. I don't think that's likely, but this is
all about future-proofing, so I'd rather keep it flexible.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/04/17 13:21, Heikki Linnakangas wrote:
On 04/11/2017 01:39 PM, �lvaro Hern�ndez Tortosa wrote:
The fact that you null terminate them (fine with me) doesn't change
my reasoning. How do you separate multiple channel binding methods? And
do you realize that you will be repeating the channel binding methods
without reason? A contrived but legal, possible example:Field1:
SCRAM-SHA-256\0
SCRAM-SHA-512\0
SCRAM-SHA-999\0
SCRAM-SHA-256-PLUS tls-unique tls-awesome yet-another-tls\0
SCRAM-SHA-512-PLUS tls-unique tls-awesome yet-another-tls\0
SCRAM-SHA-999-PLUS tls-unique tls-awesome yet-another-tls\0I was actually thinking of:
SCRAM-SHA-256\0
SCRAM-SHA-512\0
SCRAM-SHA-999\0
SCRAM-SHA-256-PLUS\0
SCRAM-SHA-256-PLUS tls-awesome\0
SCRAM-SHA-256-PLUS yet-another-tls\0
SCRAM-SHA-512-PLUS\0
SCRAM-SHA-512-PLUS tls-awesome\0
SCRAM-SHA-512-PLUS yet-another-tls\0
SCRAM-SHA-999-PLUS\0
SCRAM-SHA-999-PLUS tls-awesome\0
SCRAM-SHA-999-PLUS yet-another-tls\0In practice, I don't foresee us having this many options, so the
verbosity won't be an issue. Parsing this is very straightforward.
That's maybe slightly better, since -I agree- verbosity is not an
issue. But parsing (parsers, and validators) are still more complex (you
need to check that if suffix is -PLUS you need to split by space and
find another field with another format based on another lookup table of
IANA registry names and so forth). Vs: this field is for SCRAM names,
this field is for channel binding names. Done.
Let me exemplify. In Java-ish syntax, your type would be something
like:
List<Pair<ScramMechanism,ChannelBindingType>> from where you need to
extract individually ScramMechanisms and unique(ChannelBindingType)
My proposal would have two lists:
List<ScramMechanism>
List<ChannelBindingType>
which is exactly what you need.
So I still see your proposal more awkward and less clear, mixing
things that are separate. But again, your choice :)
vs
Field 1:
SCRAM-SHA-256,SCRAM-SHA-256-PLUS,SCRAM-SHA-1-PLUS,SCRAM-SHA-512-PLUS
(simple CSV)
Field 2: tls-unique (String)What if tls-unique is only supported with SCRAM-SHA-256-PLUS, while
SCRAM-SHA-512-PLUS requires tls-awesome?It can't happen. The RFC clearly states that they are orthogonal.
It is left to the implementations support one or the other, but no
reason to limit applicability of a given binding method to a given SCRAM
mechanisms (or viceversa).Well, if tls-unique is found to be insecure, a future
SCRAM-SHA-512-PLUS spec might well define that the default for that
mechanism is tls-unique-new-and-secure rather than tls-unique. Maybe
even forbid using tls-unique altogether. I don't think that's likely,
but this is all about future-proofing, so I'd rather keep it flexible.
If it would be insecure, I'd immediately stop it from being
advertised, and problem solved. Nothing would change (under my proposal).
--
�lvaro Hern�ndez Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/11/2017 02:32 PM, �lvaro Hern�ndez Tortosa wrote:
So I still see your proposal more awkward and less clear, mixing
things that are separate. But again, your choice :)
So, here's my more full-fledged proposal.
The first patch refactors libpq code, by moving the responsibility of
reading the GSS/SSPI/SASL/MD5 specific data from the authentication
request packet, from the enormous switch-case construct in
PQConnectPoll(), into pg_fe_sendauth(). This isn't strictly necessary,
but I think it's useful cleanup anyway, and now that there's a bit more
structure in the AuthenticationSASL message, the old way was getting
awkward.
The second patch contains the protocol changes, and adds the
documentation for it.
- Heikki
Attachments:
0001-Refactor-libpq-authentication-request-processing.patchtext/x-patch; name=0001-Refactor-libpq-authentication-request-processing.patchDownload
From 29c958dab9f8ece5d855b335c09cc9125606774a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 12 Apr 2017 16:01:18 +0300
Subject: [PATCH 1/2] Refactor libpq authentication request processing.
Move the responsibility of reading the data from the authentication request
message from PQconnectPoll() to pg_fe_sendauth(). This way, PQconnectPoll()
doesn't need to know about the different authentication request types,
and we don't need the extra fields in the pg_conn struct to pass the data
from PQconnectPoll() to pg_fe_sendauth() anymore.
In the backend, free each SASL message after sending it. It's not a lot
of wasted memory, but a leak nevertheless. Adding the pfree() revealed
a little bug in build_server_first_message(): It attempts to keeps a copy
of the sent message, but it was missing a pstrdup(), so the pointer
started to dangle, after adding the pfree() into CheckSCRAMAuth().
---
src/backend/libpq/auth-scram.c | 10 +-
src/backend/libpq/auth.c | 10 +-
src/interfaces/libpq/fe-auth.c | 214 +++++++++++++++++++++++++++++---------
src/interfaces/libpq/fe-auth.h | 2 +-
src/interfaces/libpq/fe-connect.c | 113 +++-----------------
src/interfaces/libpq/fe-misc.c | 13 +++
src/interfaces/libpq/libpq-int.h | 11 +-
7 files changed, 204 insertions(+), 169 deletions(-)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 5077ff33b1..a47c48d980 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -161,10 +161,10 @@ static char *scram_MockSalt(const char *username);
* needs to be called before doing any exchange. It will be filled later
* after the beginning of the exchange with verifier data.
*
- * 'username' is the provided by the client. 'shadow_pass' is the role's
- * password verifier, from pg_authid.rolpassword. If 'shadow_pass' is NULL, we
- * still perform an authentication exchange, but it will fail, as if an
- * incorrect password was given.
+ * 'username' is the username provided by the client in the startup message.
+ * 'shadow_pass' is the role's password verifier, from pg_authid.rolpassword.
+ * If 'shadow_pass' is NULL, we still perform an authentication exchange, but
+ * it will fail, as if an incorrect password was given.
*/
void *
pg_be_scram_init(const char *username, const char *shadow_pass)
@@ -984,7 +984,7 @@ build_server_first_message(scram_state *state)
state->client_nonce, state->server_nonce,
state->salt, state->iterations);
- return state->server_first_message;
+ return pstrdup(state->server_first_message);
}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 66ead9381d..681b93855f 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -872,6 +872,8 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
strlen(SCRAM_SHA256_NAME) + 1);
/*
+ * Initialize the status tracker for message exchanges.
+ *
* If the user doesn't exist, or doesn't have a valid password, or it's
* expired, we still go through the motions of SASL authentication, but
* tell the authentication method that the authentication is "doomed".
@@ -880,8 +882,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
* This is because we don't want to reveal to an attacker what usernames
* are valid, nor which users have a valid password.
*/
-
- /* Initialize the status tracker for message exchanges */
scram_opaq = pg_be_scram_init(port->user_name, shadow_pass);
/*
@@ -918,7 +918,7 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
return STATUS_ERROR;
}
- elog(DEBUG4, "Processing received SASL token of length %d", buf.len);
+ elog(DEBUG4, "Processing received SASL response of length %d", buf.len);
/*
* we pass 'logdetail' as NULL when doing a mock authentication,
@@ -936,9 +936,11 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
/*
* Negotiation generated data to be sent to the client.
*/
- elog(DEBUG4, "sending SASL response token of length %u", outputlen);
+ elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+
+ pfree(output);
}
} while (result == SASL_EXCHANGE_CONTINUE);
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 74c8739633..06a7140a2b 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -100,11 +100,34 @@ pg_GSS_error(const char *mprefix, PGconn *conn,
* Continue GSS authentication with next token as needed.
*/
static int
-pg_GSS_continue(PGconn *conn)
+pg_GSS_continue(PGconn *conn, int payloadLen)
{
OM_uint32 maj_stat,
min_stat,
lmin_s;
+ gss_buffer_desc ginbuf;
+
+ if (conn->gctx != GSS_C_NO_CONTEXT)
+ {
+ ginbuf.length = payloadLen;
+ ginbuf.value = malloc(payloadLen);
+ if (!ginbuf.value)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory allocating GSSAPI buffer (%d)\n"),
+ payloadLen);
+ return STATUS_ERROR;
+ }
+ if (pqGetnchar(ginbuf.value, payloadLen, conn))
+ {
+ /*
+ * Shouldn't happen, because the caller should've ensured that the
+ * whole message is already in the input buffer.
+ */
+ free(ginbuf.value);
+ return STATUS_ERROR;
+ }
+ }
maj_stat = gss_init_sec_context(&min_stat,
GSS_C_NO_CREDENTIAL,
@@ -114,18 +137,14 @@ pg_GSS_continue(PGconn *conn)
GSS_C_MUTUAL_FLAG,
0,
GSS_C_NO_CHANNEL_BINDINGS,
- (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf,
+ (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &ginbuf,
NULL,
&conn->goutbuf,
NULL,
NULL);
if (conn->gctx != GSS_C_NO_CONTEXT)
- {
- free(conn->ginbuf.value);
- conn->ginbuf.value = NULL;
- conn->ginbuf.length = 0;
- }
+ free(ginbuf.value);
if (conn->goutbuf.length != 0)
{
@@ -165,7 +184,7 @@ pg_GSS_continue(PGconn *conn)
* Send initial GSS authentication token
*/
static int
-pg_GSS_startup(PGconn *conn)
+pg_GSS_startup(PGconn *conn, int payloadLen)
{
OM_uint32 maj_stat,
min_stat;
@@ -221,7 +240,7 @@ pg_GSS_startup(PGconn *conn)
*/
conn->gctx = GSS_C_NO_CONTEXT;
- return pg_GSS_continue(conn);
+ return pg_GSS_continue(conn, payloadLen);
}
#endif /* ENABLE_GSS */
@@ -251,7 +270,7 @@ pg_SSPI_error(PGconn *conn, const char *mprefix, SECURITY_STATUS r)
* Continue SSPI authentication with next token as needed.
*/
static int
-pg_SSPI_continue(PGconn *conn)
+pg_SSPI_continue(PGconn *conn, int payloadLen)
{
SECURITY_STATUS r;
CtxtHandle newContext;
@@ -260,6 +279,7 @@ pg_SSPI_continue(PGconn *conn)
SecBufferDesc outbuf;
SecBuffer OutBuffers[1];
SecBuffer InBuffers[1];
+ char *inputbuf = NULL;
if (conn->sspictx != NULL)
{
@@ -267,11 +287,29 @@ pg_SSPI_continue(PGconn *conn)
* On runs other than the first we have some data to send. Put this
* data in a SecBuffer type structure.
*/
+ inputbuf = malloc(payloadLen);
+ if (inputbuf)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory allocating SSPI buffer (%d)\n"),
+ payloadLen);
+ return STATUS_ERROR;
+ }
+ if (pqGetnchar(inbuf, payloadLen, conn))
+ {
+ /*
+ * Shouldn't happen, because the caller should've ensured that the
+ * whole message is already in the input buffer.
+ */
+ free(inputbuf);
+ return STATUS_ERROR;
+ }
+
inbuf.ulVersion = SECBUFFER_VERSION;
inbuf.cBuffers = 1;
inbuf.pBuffers = InBuffers;
- InBuffers[0].pvBuffer = conn->ginbuf.value;
- InBuffers[0].cbBuffer = conn->ginbuf.length;
+ InBuffers[0].pvBuffer = inputbuf;
+ InBuffers[0].cbBuffer = payloadLen;
InBuffers[0].BufferType = SECBUFFER_TOKEN;
}
@@ -295,6 +333,10 @@ pg_SSPI_continue(PGconn *conn)
&contextAttr,
NULL);
+ /* we don't need the input anymore */
+ if (inputbuf)
+ free(inputbuf);
+
if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED)
{
pg_SSPI_error(conn, libpq_gettext("SSPI continuation error"), r);
@@ -313,16 +355,6 @@ pg_SSPI_continue(PGconn *conn)
}
memcpy(conn->sspictx, &newContext, sizeof(CtxtHandle));
}
- else
- {
- /*
- * On subsequent runs when we had data to send, free buffers that
- * contained this data.
- */
- free(conn->ginbuf.value);
- conn->ginbuf.value = NULL;
- conn->ginbuf.length = 0;
- }
/*
* If SSPI returned any data to be sent to the server (as it normally
@@ -369,7 +401,7 @@ pg_SSPI_continue(PGconn *conn)
* which supports both kerberos and NTLM, but is not compatible with Unix.
*/
static int
-pg_SSPI_startup(PGconn *conn, int use_negotiate)
+pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadLen)
{
SECURITY_STATUS r;
TimeStamp expire;
@@ -429,16 +461,38 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
*/
conn->usesspi = 1;
- return pg_SSPI_continue(conn);
+ return pg_SSPI_continue(conn, payloadLen);
}
#endif /* ENABLE_SSPI */
/*
* Initialize SASL authentication exchange.
*/
-static bool
-pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+static int
+pg_SASL_init(PGconn *conn, int payloadLen)
{
+ char auth_mechanism[21];
+ char *initialresponse;
+ int initialresponselen;
+ bool done;
+ bool success;
+ int res;
+
+ /*
+ * Read the authentication mechanism the server told us to use.
+ */
+ if (payloadLen > sizeof(auth_mechanism) - 1)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SASL authentication mechanism not supported\n"));
+ if (pqGetnchar(auth_mechanism, payloadLen, conn))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ "fe_sendauth: invalid authentication request from server: invalid authentication mechanism\n");
+
+ return STATUS_ERROR;
+ }
+ auth_mechanism[payloadLen] = '\0';
+
/*
* Check the authentication mechanism (only SCRAM-SHA-256 is supported at
* the moment.)
@@ -465,16 +519,40 @@ pg_SASL_init(PGconn *conn, const char *auth_mechanism)
libpq_gettext("out of memory\n"));
return STATUS_ERROR;
}
-
- return STATUS_OK;
}
else
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SASL authentication mechanism %s not supported\n"),
- (char *) conn->auth_req_inbuf);
+ auth_mechanism);
return STATUS_ERROR;
}
+
+ /* Send the initial client response */
+ pg_fe_scram_exchange(conn->sasl_state,
+ NULL, -1,
+ &initialresponse, &initialresponselen,
+ &done, &success, &conn->errorMessage);
+
+ if (initialresponselen != 0)
+ {
+ res = pqPacketSend(conn, 'p', initialresponse, initialresponselen);
+ free(initialresponse);
+
+ if (res != STATUS_OK)
+ return STATUS_ERROR;
+ }
+
+ if (done && !success)
+ {
+ /* Use error message, if set already */
+ if (conn->errorMessage.len == 0)
+ printfPQExpBuffer(&conn->errorMessage,
+ "fe_sendauth: error in SASL authentication\n");
+ return STATUS_ERROR;
+ }
+
+ return STATUS_OK;
}
/*
@@ -483,25 +561,42 @@ pg_SASL_init(PGconn *conn, const char *auth_mechanism)
* the protocol.
*/
static int
-pg_SASL_exchange(PGconn *conn)
+pg_SASL_exchange(PGconn *conn, int payloadLen)
{
char *output;
int outputlen;
bool done;
bool success;
int res;
+ char *challenge;
+
+ /* Read the SASL payload from the AuthenticationSASLContinue message. */
+ challenge = malloc(payloadLen + 1);
+ if (!challenge)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory allocating SASL buffer (%d)\n"),
+ payloadLen);
+ return STATUS_ERROR;
+ }
+
+ if (pqGetnchar(challenge, payloadLen, conn))
+ {
+ free(challenge);
+ return STATUS_ERROR;
+ }
+ /* For safety and convenience, ensure the buffer is NULL-terminated. */
+ challenge[payloadLen] = '\0';
pg_fe_scram_exchange(conn->sasl_state,
- conn->auth_req_inbuf, conn->auth_req_inlen,
+ challenge, payloadLen,
&output, &outputlen,
&done, &success, &conn->errorMessage);
+ free(challenge); /* don't need the input anymore */
+
+ /* Send the SASL response to the server, if any. */
if (outputlen != 0)
{
- /*
- * Send the SASL response to the server. We don't care if it's the
- * first or subsequent packet, just send the same kind of password
- * packet.
- */
res = pqPacketSend(conn, 'p', output, outputlen);
free(output);
@@ -582,6 +677,14 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
int ret;
char *crypt_pwd = NULL;
const char *pwd_to_send;
+ char md5Salt[4];
+
+ /* Read the salt from the AuthenticationMD5 message. */
+ if (areq == AUTH_REQ_MD5)
+ {
+ if (pqGetnchar(md5Salt, 4, conn))
+ return STATUS_ERROR; /* shouldn't happen */
+ }
/* Encrypt the password if needed. */
@@ -607,8 +710,8 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
free(crypt_pwd);
return STATUS_ERROR;
}
- if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), conn->md5Salt,
- sizeof(conn->md5Salt), crypt_pwd))
+ if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), md5Salt,
+ 4, crypt_pwd))
{
free(crypt_pwd);
return STATUS_ERROR;
@@ -635,10 +738,17 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
/*
* pg_fe_sendauth
- * client demux routine for outgoing authentication information
+ * client demux routine for processing an authentication request
+ *
+ * The server has sent us an authentication challenge (or OK). Send an
+ * appropriate response. The caller has ensured that the whole message is
+ * now in the input buffer, and has already consumed the type and length of
+ * the message. We are responsible for reading any remaining extra data,
+ * specific to the authentication method. 'payloadLen' is the remaining
+ * length in the message.
*/
int
-pg_fe_sendauth(AuthRequest areq, PGconn *conn)
+pg_fe_sendauth(AuthRequest areq, int payloadLen, PGconn *conn)
{
switch (areq)
{
@@ -676,13 +786,13 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
*/
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->gsslib && (pg_strcasecmp(conn->gsslib, "gssapi") == 0))
- r = pg_GSS_startup(conn);
+ r = pg_GSS_startup(conn, payloadLen);
else
- r = pg_SSPI_startup(conn, 0);
+ r = pg_SSPI_startup(conn, 0, payloadLen);
#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI)
- r = pg_GSS_startup(conn);
+ r = pg_GSS_startup(conn, payloadLen);
#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI)
- r = pg_SSPI_startup(conn, 0);
+ r = pg_SSPI_startup(conn, 0, payloadLen);
#endif
if (r != STATUS_OK)
{
@@ -701,13 +811,13 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
pglock_thread();
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->usesspi)
- r = pg_SSPI_continue(conn);
+ r = pg_SSPI_continue(conn, payloadLen);
else
- r = pg_GSS_continue(conn);
+ r = pg_GSS_continue(conn, payloadLen);
#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI)
- r = pg_GSS_continue(conn);
+ r = pg_GSS_continue(conn, payloadLen);
#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI)
- r = pg_SSPI_continue(conn);
+ r = pg_SSPI_continue(conn, payloadLen);
#endif
if (r != STATUS_OK)
{
@@ -736,7 +846,7 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
* negotiation instead of Kerberos.
*/
pglock_thread();
- if (pg_SSPI_startup(conn, 1) != STATUS_OK)
+ if (pg_SSPI_startup(conn, 1, payloadLen) != STATUS_OK)
{
/* Error message already filled in. */
pgunlock_thread();
@@ -796,12 +906,12 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
* The request contains the name (as assigned by IANA) of the
* authentication mechanism.
*/
- if (pg_SASL_init(conn, conn->auth_req_inbuf) != STATUS_OK)
+ if (pg_SASL_init(conn, payloadLen) != STATUS_OK)
{
/* pg_SASL_init already set the error message */
return STATUS_ERROR;
}
- /* fall through */
+ break;
case AUTH_REQ_SASL_CONT:
if (conn->sasl_state == NULL)
@@ -810,7 +920,7 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
"fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
return STATUS_ERROR;
}
- if (pg_SASL_exchange(conn) != STATUS_OK)
+ if (pg_SASL_exchange(conn, payloadLen) != STATUS_OK)
{
/* Use error message, if set already */
if (conn->errorMessage.len == 0)
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 204790cea4..3d2b9f31f0 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -19,7 +19,7 @@
/* Prototypes for functions in fe-auth.c */
-extern int pg_fe_sendauth(AuthRequest areq, PGconn *conn);
+extern int pg_fe_sendauth(AuthRequest areq, int payloadLen, PGconn *conn);
extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
/* Prototypes for functions in fe-auth-scram.c */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 1739855c63..8537ac9812 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2673,116 +2673,39 @@ keep_going: /* We will come back to here until there is
return PGRES_POLLING_READING;
}
- /* Get the password salt if there is one. */
- if (areq == AUTH_REQ_MD5)
- {
- if (pqGetnchar(conn->md5Salt,
- sizeof(conn->md5Salt), conn))
- {
- /* We'll come back when there are more data */
- return PGRES_POLLING_READING;
- }
- }
-#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
-
/*
- * Continue GSSAPI/SSPI authentication
+ * Ensure the password salt is in the input buffer, if it's
+ * an MD5 request. All the other authentication methods
+ * that contain extra data in the authentication request are
+ * only supported in protocol version 3, in which case we
+ * already read the whole message above.
*/
- if (areq == AUTH_REQ_GSS_CONT)
+ if (areq == AUTH_REQ_MD5)
{
- int llen = msgLength - 4;
-
- /*
- * We can be called repeatedly for the same buffer. Avoid
- * re-allocating the buffer in this case - just re-use the
- * old buffer.
- */
- if (llen != conn->ginbuf.length)
+ if (pqEnsurenchar(4, conn))
{
- if (conn->ginbuf.value)
- free(conn->ginbuf.value);
-
- conn->ginbuf.length = llen;
- conn->ginbuf.value = malloc(llen);
- if (!conn->ginbuf.value)
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("out of memory allocating GSSAPI buffer (%d)"),
- llen);
- goto error_return;
- }
- }
-
- if (pqGetnchar(conn->ginbuf.value, llen, conn))
- {
- /* We'll come back when there is more data. */
- return PGRES_POLLING_READING;
- }
- }
-#endif
- /* Get additional payload for SASL, if any */
- if ((areq == AUTH_REQ_SASL ||
- areq == AUTH_REQ_SASL_CONT) &&
- msgLength > 4)
- {
- int llen = msgLength - 4;
-
- /*
- * We can be called repeatedly for the same buffer. Avoid
- * re-allocating the buffer in this case - just re-use the
- * old buffer.
- */
- if (llen != conn->auth_req_inlen)
- {
- if (conn->auth_req_inbuf)
- {
- free(conn->auth_req_inbuf);
- conn->auth_req_inbuf = NULL;
- }
-
- conn->auth_req_inlen = llen;
- conn->auth_req_inbuf = malloc(llen + 1);
- if (!conn->auth_req_inbuf)
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("out of memory allocating SASL buffer (%d)"),
- llen);
- goto error_return;
- }
- }
-
- if (pqGetnchar(conn->auth_req_inbuf, llen, conn))
- {
- /* We'll come back when there is more data. */
+ /* We'll come back when there are more data */
return PGRES_POLLING_READING;
}
-
- /*
- * For safety and convenience, always ensure the in-buffer
- * is NULL-terminated.
- */
- conn->auth_req_inbuf[llen] = '\0';
}
/*
- * OK, we successfully read the message; mark data consumed
- */
- conn->inStart = conn->inCursor;
-
- /* Respond to the request if necessary. */
-
- /*
+ * Process the rest of the authentication request message,
+ * and respond to it if necessary.
+ *
* Note that conn->pghost must be non-NULL if we are going to
* avoid the Kerberos code doing a hostname look-up.
*/
-
- if (pg_fe_sendauth(areq, conn) != STATUS_OK)
+ if (pg_fe_sendauth(areq, msgLength - 4, conn) != STATUS_OK)
{
conn->errorMessage.len = strlen(conn->errorMessage.data);
goto error_return;
}
conn->errorMessage.len = strlen(conn->errorMessage.data);
+ /* OK, we successfully read the message; mark data consumed */
+ conn->inStart = conn->inCursor;
+
/*
* Just make sure that any data sent by pg_fe_sendauth is
* flushed out. Although this theoretically could block, it
@@ -3522,17 +3445,11 @@ closePGconn(PGconn *conn)
gss_delete_sec_context(&min_s, &conn->gctx, GSS_C_NO_BUFFER);
if (conn->gtarg_nam)
gss_release_name(&min_s, &conn->gtarg_nam);
- if (conn->ginbuf.length)
- gss_release_buffer(&min_s, &conn->ginbuf);
if (conn->goutbuf.length)
gss_release_buffer(&min_s, &conn->goutbuf);
}
#endif
#ifdef ENABLE_SSPI
- if (conn->ginbuf.length)
- free(conn->ginbuf.value);
- conn->ginbuf.length = 0;
- conn->ginbuf.value = NULL;
if (conn->sspitarget)
free(conn->sspitarget);
conn->sspitarget = NULL;
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index ba7400b425..4004fdb7bb 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -218,6 +218,19 @@ pqGetnchar(char *s, size_t len, PGconn *conn)
}
/*
+ * pqEnsurenchar:
+ * check that there is at least len bytes in the input buffer.
+ */
+int
+pqEnsurenchar(size_t len, PGconn *conn)
+{
+ if (len > (size_t) (conn->inEnd - conn->inCursor))
+ return EOF;
+
+ return 0;
+}
+
+/*
* pqSkipnchar:
* skip over len bytes in input buffer.
*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 494804e74f..537a8902e5 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -419,7 +419,6 @@ struct pg_conn
/* Miscellaneous stuff */
int be_pid; /* PID of backend --- needed for cancels */
int be_key; /* key of backend --- needed for cancels */
- char md5Salt[4]; /* password salt received from backend */
pgParameterStatus *pstatus; /* ParameterStatus data */
int client_encoding; /* encoding id */
bool std_strings; /* standard_conforming_strings */
@@ -452,10 +451,6 @@ struct pg_conn
PGresult *result; /* result being constructed */
PGresult *next_result; /* next result (used in single-row mode) */
- /* Buffer to hold incoming authentication request data */
- char *auth_req_inbuf;
- int auth_req_inlen;
-
/* Assorted state for SASL, SSL, GSS, etc */
void *sasl_state;
@@ -479,14 +474,11 @@ struct pg_conn
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
gss_name_t gtarg_nam; /* GSS target name */
- gss_buffer_desc ginbuf; /* GSS input token */
gss_buffer_desc goutbuf; /* GSS output token */
#endif
#ifdef ENABLE_SSPI
-#ifndef ENABLE_GSS
- gss_buffer_desc ginbuf; /* GSS input token */
-#else
+#ifdef ENABLE_GSS
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
#endif
@@ -637,6 +629,7 @@ extern int pqGets(PQExpBuffer buf, PGconn *conn);
extern int pqGets_append(PQExpBuffer buf, PGconn *conn);
extern int pqPuts(const char *s, PGconn *conn);
extern int pqGetnchar(char *s, size_t len, PGconn *conn);
+extern int pqEnsurenchar(size_t len, PGconn *conn);
extern int pqSkipnchar(size_t len, PGconn *conn);
extern int pqPutnchar(const char *s, size_t len, PGconn *conn);
extern int pqGetInt(int *result, size_t bytes, PGconn *conn);
--
2.11.0
0002-Improve-the-SASL-authentication-protocol.patchtext/x-patch; name=0002-Improve-the-SASL-authentication-protocol.patchDownload
From a9db2e3e018d8a6b083089e20be14743b872f937 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 12 Apr 2017 20:14:13 +0300
Subject: [PATCH 2/2] Improve the SASL authentication protocol.
This contains some protocol changes to SASL authentiation (which is new
in v10):
* For future-proofing, in the AuthenticationSASL message that begins SASL
authentication, provide a list of SASL mechanisms that the server
supports, for the client to choose from. Currently, it's always just
SCRAM-SHA-256.
* Add a separate authentication message type for the final server->client
SASL message, which the client doesn't need to respond to. This makes
it unambiguous whether the client is supposed to send a response or not.
The SASL mechanism should know that anyway, but better to be explicit.
Also, in the server, support clients that don't send an Initial Client
response in the first SASLInitialResponse message. The server is supposed
to first send an empty request in that case, to which the client will
respond with the data that usually comes in the Initial Client Response.
libpq uses the Initial Client Response field and doesn't need this, and I
would assume any other sensible implementation to use Initial Client
Response, too, but let's follow the SASL spec.
Improve the documentation on SASL authentication in protocol. Add a
section describing the SASL message flow, some details on our SCRAM-SHA-256
implementation.
Document the different kinds of PasswordMessages that the frontend sends
in different phases of SASL authentication, as well as GSS/SSPI
authentication as separate message formats. Even though they're all 'p'
messages, and the exact format depends on the context, describing them as
separate message formats makes the documentation more clear.
Discussion: https://www.postgresql.org/message-id/CAB7nPqS-aFg0iM3AQOJwKDv_0WkAedRjs1W2X8EixSz+sKBXCQ@mail.gmail.com
---
doc/src/sgml/protocol.sgml | 350 ++++++++++++++++++++++++++++++++++++++---
src/backend/libpq/auth-scram.c | 27 +++-
src/backend/libpq/auth.c | 66 +++++++-
src/include/libpq/pqcomm.h | 5 +-
src/interfaces/libpq/fe-auth.c | 159 ++++++++++++-------
5 files changed, 521 insertions(+), 86 deletions(-)
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 4e8bb32d33..6e271fe898 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -330,7 +330,7 @@
<listitem>
<para>
The frontend must now initiate a GSSAPI negotiation. The frontend
- will send a PasswordMessage with the first part of the GSSAPI
+ will send a GSSResponse message with the first part of the GSSAPI
data stream in response to this. If further messages are needed,
the server will respond with AuthenticationGSSContinue.
</para>
@@ -342,7 +342,7 @@
<listitem>
<para>
The frontend must now initiate a SSPI negotiation. The frontend
- will send a PasswordMessage with the first part of the SSPI
+ will send a GSSResponse with the first part of the SSPI
data stream in response to this. If further messages are needed,
the server will respond with AuthenticationGSSContinue.
</para>
@@ -358,7 +358,7 @@
or a previous AuthenticationGSSContinue). If the GSSAPI
or SSPI data in this message
indicates more data is needed to complete the authentication,
- the frontend must send that data as another PasswordMessage. If
+ the frontend must send that data as another GSSResponse message. If
GSSAPI or SSPI authentication is completed by this message, the server
will next send AuthenticationOk to indicate successful authentication
or ErrorResponse to indicate failure.
@@ -370,27 +370,38 @@
<term>AuthenticationSASL</term>
<listitem>
<para>
- The frontend must now initiate a SASL negotiation, using the SASL
- mechanism specified in the message. The frontend will send a
- PasswordMessage with the first part of the SASL data stream in
- response to this. If further messages are needed, the server will
- respond with AuthenticationSASLContinue.
+ The frontend must now initiate a SASL negotiation, using one of the
+ SASL mechanisms listed in the message. The frontend will send a
+ SASLInitialResponse with the name of the selected mechanism, and the
+ first part of the SASL data stream in response to this. If further
+ messages are needed, the server will respond with
+ AuthenticationSASLContinue. See <xref linkend="sasl-authentication">
+ for details.
</para>
</listitem>
-
</varlistentry>
+
<varlistentry>
<term>AuthenticationSASLContinue</term>
<listitem>
<para>
- This message contains the response data from the previous step
- of SASL negotiation (AuthenticationSASL, or a previous
- AuthenticationSASLContinue). If the SASL data in this message
- indicates more data is needed to complete the authentication,
- the frontend must send that data as another PasswordMessage. If
- SASL authentication is completed by this message, the server
- will next send AuthenticationOk to indicate successful authentication
- or ErrorResponse to indicate failure.
+ This message contains challenge data from the previous step of SASL
+ negotiation (AuthenticationSASL, or a previous
+ AuthenticationSASLContinue). The frontend must respond with a
+ SASLResponse message.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>AuthenticationSASLFinal</term>
+ <listitem>
+ <para>
+ SASL authentication has completed with additional mechanism-specific
+ data for the client. The server will next send AuthenticationOk to
+ indicate successful authentication, or an ErrorResponse to indicate
+ failure. This message is sent only if the SASL mechanism specifies
+ additional data to be sent from server to client at completion.
</para>
</listitem>
</varlistentry>
@@ -1326,6 +1337,141 @@
</sect2>
</sect1>
+<sect1 id="sasl-authentication">
+<title>SASL Authentication</title>
+
+<para>
+<firstterm>SASL</> is a framework for authentication in connection-oriented
+protocols. At the moment, <productname>PostgreSQL</> implements only one SASL
+authentication mechanism, SCRAM-SHA-256, but more might be added in the
+future. The below steps illustrate how SASL authentication is performed in
+general, while the next subsection gives more details on SCRAM-SHA-256.
+</para>
+
+<procedure>
+<title>SASL Authentication Message Flow</title>
+
+<step id="sasl-auth-begin">
+<para>
+ To begin a SASL authentication exchange, the server an AuthenticationSASL
+ message. It includes a list of SASL authentication mechanisms that the
+ server can accept, in the server's preferred order.
+</para>
+</step>
+
+<step id="sasl-auth-initial-response">
+<para>
+ The client selects one of the supported mechanisms from the list, and sends
+ a SASLInitialResponse message to the server. The message includes the name
+ of the selected mechanism, and an optional Initial Client Response, if the
+ selected mechanism uses that.
+</para>
+</step>
+
+<step id="sasl-auth-continue">
+<para>
+ One or more server-challenge and client-response message will follow. Each
+ server-challenge is sent in an AuthenticationSASLContinue message, followed
+ by a response from client in an SASLResponse message. The particulars of
+ the messages are mechanism specific.
+</para>
+</step>
+
+<step id="sasl-auth-end">
+<para>
+ Finally, when the authentication exchange is completed successfully, the
+ server sends an AuthenticationSASLFinal message, followed
+ immediately by an AuthenticationOk message. The AuthenticationSASLFinal
+ contains additional server-to-client data, whose content is particular to the
+ selected authentication mechanism. If the authentication mechanism doesn't
+ use additional data that's sent at completion, the AuthenticationSASLFinal
+ message is not sent.
+</para>
+</step>
+</procedure>
+
+<para>
+On error, the server can abort the authentication at any stage, and send an
+ErrorMessage.
+</para>
+
+ <sect2 id="sasl-scram-sha256">
+ <title>SCRAM-SHA-256 authentication</title>
+
+ <para>
+ <firstterm>SCRAM-SHA-256</> (called just <firstterm>SCRAM</> from now on) is
+ the only implemented SASL mechanism, at the moment. It is described in detail
+ in RFC 7677 and RFC 5741.
+ </para>
+
+ <para>
+When SCRAM-SHA-256 is used in PostgreSQL, the server will ignore the username
+that the client sends in the <structname>client-first-message</>. The username
+that was already sent in the startup message is used instead.
+<productname>PostgreSQL</> supports multiple character encodings, while SCRAM
+dictates UTF-8 to be used for the username, so it might be impossible to
+represent the PostgreSQL username in UTF-8. To avoid confusion, the client
+should use <literal>pg_same_as_startup_message</literal> as the username in the
+<structname>client-first-message</>.
+ </para>
+
+ <para>
+The SCRAM specification dictates that the password is also in UTF-8, and is
+processed with the <firstterm>SASLprep</> algorithm.
+<productname>PostgreSQL</>, however, does not require UTF-8 to be used for
+the password. When a user's password is set, it is processed with SASLprep
+as if it was in UTF-8, regardless of the actual encoding used. However, if
+it is not a legal UTF-8 byte sequence, or it contains UTF-8 byte sequences
+that are prohibited by the SASLprep algorithm, the raw password will be used
+without SASLprep processing, instead of throwing an error. This allows the
+password to be normalized when it is in UTF-8, but still allows a non-UTF-8
+password to be used, and doesn't require the system to know which encoding
+the password is in.
+ </para>
+
+ <para>
+<firstterm>Channel binding</> has not been implemented yet.
+ </para>
+
+<procedure>
+<title>Example</title>
+ <step id="scram-begin">
+<para>
+ The server sends an AuthenticationSASL message. It includes a list of
+ SASL authentication mechanisms that the server can accept.
+</para>
+</step>
+<step id="scram-client-first">
+<para>
+ The client responds by sending a SASLInitialResponse message, which
+ indicates the chosen mechanism, <literal>SCRAM-SHA-256</>. In the Initial
+ Client response field, the message contains the SCRAM
+ <structname>client-first-message</>.
+</para>
+</step>
+<step id="scram-server-first">
+<para>
+ Server sends an AuthenticationSASLContinue message, with a SCRAM
+ <structname>server-first message</> as the content.
+</para>
+</step>
+<step id="scram-client-final">
+<para>
+ Client sends a SASLResponse message, with SCRAM
+ <structname>client-final-message</> as the content.
+</para>
+</step>
+<step id="scram-server-final">
+<para>
+ Server sends an AuthenticationSASLFinal message, with the SCRAM
+ <structname>server-final-message</>, followed immediately by
+ an AuthenticationOk message.
+</para>
+</step>
+</procedure>
+</sect2>
+
+
<sect1 id="protocol-replication">
<title>Streaming Replication Protocol</title>
@@ -2894,6 +3040,12 @@ AuthenticationSASL (B)
</para>
</listitem>
</varlistentry>
+</variablelist>
+The message body is a list of SASL authentication mechanisms, in the
+server's order of preference. A zero byte is required as terminator after
+the last authentication mechanism name. For each mechanism, there is the
+following:
+<variablelist>
<varlistentry>
<term>
String
@@ -4313,6 +4465,50 @@ FunctionCallResponse (B)
</listitem>
</varlistentry>
+<varlistentry>
+<term>
+GSSResponse (F)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+ Byte1('p')
+</term>
+<listitem>
+<para>
+ Identifies the message as a GSSAPI or SSPI response. Note that
+ this is also used for SASL and password response messages.
+ The exact message type can be deduced from the context.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+ GSSAPI/SSPI specific message data.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
<varlistentry>
<term>
@@ -4726,10 +4922,8 @@ PasswordMessage (F)
<listitem>
<para>
Identifies the message as a password response. Note that
- this is also used for GSSAPI, SSPI and SASL response messages
- (which is really a design error, since the contained data
- is not a null-terminated string in that case, but can be
- arbitrary binary data).
+ this is also used for GSSAPI, SSPI and SASL response messages.
+ The exact message type can be deduced from the context.
</para>
</listitem>
</varlistentry>
@@ -4761,6 +4955,120 @@ PasswordMessage (F)
<varlistentry>
<term>
+SASLInitialresponse (F)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+ Byte1('p')
+</term>
+<listitem>
+<para>
+ Identifies the message as an initial SASL response. Note that
+ this is also used for GSSAPI, SSPI and password response messages.
+ The exact message type is deduced from the context.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ String
+</term>
+<listitem>
+<para>
+ Name of the SASL authentication mechanism that the client
+ selected.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of SASL mechanism specific "Initial Client Response" that
+ follows, or -1 if there is no Initial Response.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+ SASL mechanism specific "Initial Response".
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+
+<varlistentry>
+<term>
+SASLResponse (F)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+ Byte1('p')
+</term>
+<listitem>
+<para>
+ Identifies the message as a SASL response. Note that
+ this is also used for GSSAPI, SSPI and password response messages.
+ The exact message type can be deduced from the context.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+ SASL mechanism specific message data.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+
+<varlistentry>
+<term>
PortalSuspended (B)
</term>
<listitem>
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index a47c48d980..59e997f96d 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -254,8 +254,16 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
/*
* Continue a SCRAM authentication exchange.
*
- * The next message to send to client is saved in "output", for a length
- * of "outputlen". In the case of an error, optionally store a palloc'd
+ * 'input' is the SCRAM payload sent by the client. On the first call,
+ * 'input' contains the "Initial Client Response" that the client sent as
+ * part of the SASLInitialResponse message, or NULL if no Initial Client
+ * Response was given. (The SASL specification distinguishes between an
+ * empty response and non-existing one.) On subsequent calls, 'input'
+ * cannot be NULL. For convenience in this function, the caller must
+ * ensure that there is a null terminator at input[inputlen].
+ *
+ * The next message to send to client is saved in 'output', for a length
+ * of 'outputlen'. In the case of an error, optionally store a palloc'd
* string at *logdetail that will be sent to the postmaster log (but not
* the client).
*/
@@ -269,6 +277,21 @@ pg_be_scram_exchange(void *opaq, char *input, int inputlen,
*output = NULL;
/*
+ * If the client didn't send an "Initial Client Response" in the
+ * SASLInitialResponse message, send an empty challenge, to which the
+ * client will respond with the same data that usually comes in the
+ * Initial Client Response.
+ */
+ if (input == NULL)
+ {
+ Assert(state->state == SCRAM_AUTH_INIT);
+
+ *output = pstrdup("");
+ *outputlen = 0;
+ return SASL_EXCHANGE_CONTINUE;
+ }
+
+ /*
* Check that the input length agrees with the string length of the input.
* We can ignore inputlen after this.
*/
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 681b93855f..dbb9e8dcc1 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -620,10 +620,10 @@ sendAuthRequest(Port *port, AuthRequest areq, char *extradata, int extralen)
pq_endmessage(&buf);
/*
- * Flush message so client will see it, except for AUTH_REQ_OK, which need
- * not be sent until we are ready for queries.
+ * Flush message so client will see it, except for AUTH_REQ_OK and
+ * AUTH_REQ_SASL_FIN, which need not be sent until we are ready for queries.
*/
- if (areq != AUTH_REQ_OK)
+ if (areq != AUTH_REQ_OK && areq != AUTH_REQ_SASL_FIN)
pq_flush();
CHECK_FOR_INTERRUPTS();
@@ -850,7 +850,10 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
void *scram_opaq;
char *output = NULL;
int outputlen = 0;
+ char *input;
+ int inputlen;
int result;
+ bool initial;
/*
* SASL auth is not supported for protocol versions before 3, because it
@@ -866,10 +869,13 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
errmsg("SASL authentication is not supported in protocol version 2")));
/*
- * Send first the authentication request to user.
+ * Send the SASL authentication request to user. It includes the list of
+ * authentication mechanisms (which is trivial, because we only support
+ * SCRAM-SHA-256 at the moment). The extra "\0" is for an empty string to
+ * terminate the list.
*/
- sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
- strlen(SCRAM_SHA256_NAME) + 1);
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME "\0",
+ strlen(SCRAM_SHA256_NAME) + 2);
/*
* Initialize the status tracker for message exchanges.
@@ -890,6 +896,7 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
* from the client. All messages from client to server are password
* packets (type 'p').
*/
+ initial = true;
do
{
pq_startmsgread();
@@ -921,10 +928,50 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
elog(DEBUG4, "Processing received SASL response of length %d", buf.len);
/*
+ * The first SASLInitialResponse message is different from the
+ * others. It indicates which SASL mechanism the selected, and
+ * contains an optional Initial Client Response payload. The
+ * subsequent SASLResponse messages contain just the SASL payload.
+ */
+ if (initial)
+ {
+ const char *selected_mech;
+
+ /*
+ * We only support SCRAM-SHA-256 at the moment, so anything else
+ * is an error.
+ */
+ selected_mech = pq_getmsgrawstring(&buf);
+ if (strcmp(selected_mech, SCRAM_SHA256_NAME) != 0)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("client selected an invalid SASL authentication mechanism")));
+
+ inputlen = pq_getmsgint(&buf, 4);
+ if (inputlen == -1)
+ input = NULL;
+ else
+ input = (char *) pq_getmsgbytes(&buf, inputlen);
+
+ initial = false;
+ }
+ else
+ {
+ inputlen = buf.len;
+ input = (char *) pq_getmsgbytes(&buf, buf.len);
+ }
+ /*
+ * The StringInfo guarantees that there's a \0 byte after the
+ * response.
+ */
+ Assert(input[inputlen] == '\0');
+ pq_getmsgend(&buf);
+
+ /*
* we pass 'logdetail' as NULL when doing a mock authentication,
* because we should already have a better error message in that case
*/
- result = pg_be_scram_exchange(scram_opaq, buf.data, buf.len,
+ result = pg_be_scram_exchange(scram_opaq, input, inputlen,
&output, &outputlen,
logdetail);
@@ -938,7 +985,10 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
*/
elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
- sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+ if (result == SASL_EXCHANGE_SUCCESS)
+ sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
+ else
+ sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
pfree(output);
}
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 5441aaa93a..b6de569c5c 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -172,8 +172,9 @@ extern bool Db_user_namespace;
#define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */
#define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */
#define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */
-#define AUTH_REQ_SASL 10 /* SASL */
-#define AUTH_REQ_SASL_CONT 11 /* continue SASL exchange */
+#define AUTH_REQ_SASL 10 /* Begin SASL authentication */
+#define AUTH_REQ_SASL_CONT 11 /* Continue SASL authentication */
+#define AUTH_REQ_SASL_FIN 12 /* Final SASL message */
typedef uint32 AuthRequest;
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 06a7140a2b..7ff01cdc52 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -471,88 +471,128 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadLen)
static int
pg_SASL_init(PGconn *conn, int payloadLen)
{
- char auth_mechanism[21];
- char *initialresponse;
+ char *initialresponse = NULL;
int initialresponselen;
bool done;
bool success;
- int res;
+ const char *selected_mechanism;
+ PQExpBufferData mechanism_buf;
- /*
- * Read the authentication mechanism the server told us to use.
- */
- if (payloadLen > sizeof(auth_mechanism) - 1)
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("SASL authentication mechanism not supported\n"));
- if (pqGetnchar(auth_mechanism, payloadLen, conn))
+ initPQExpBuffer(&mechanism_buf);
+
+ if (conn->sasl_state)
{
printfPQExpBuffer(&conn->errorMessage,
- "fe_sendauth: invalid authentication request from server: invalid authentication mechanism\n");
-
- return STATUS_ERROR;
+ libpq_gettext("duplicate SASL authentication request\n"));
+ goto error;
}
- auth_mechanism[payloadLen] = '\0';
/*
- * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
- * the moment.)
+ * Parse the list of SASL authentication mechanisms in the
+ * AuthenticationSASL message, and select the best mechanism that we
+ * support. (Only SCRAM-SHA-256 is supported at the moment.)
*/
- if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 0)
+ for (;;)
{
- char *password;
-
- conn->password_needed = true;
- password = conn->connhost[conn->whichhost].password;
- if (password == NULL)
- password = conn->pgpass;
- if (password == NULL || password[0] == '\0')
+ if (pqGets(&mechanism_buf, conn))
{
printfPQExpBuffer(&conn->errorMessage,
- PQnoPasswordSupplied);
- return STATUS_ERROR;
+ "fe_sendauth: invalid authentication request from server: invalid list of authentication mechanisms\n");
+ goto error;
}
+ if (PQExpBufferDataBroken(mechanism_buf))
+ goto oom_error;
+
+ /* An empty string indicates end of list */
+ if (mechanism_buf.data[0] == '\0')
+ break;
+
+ /*
+ * If we have already selected a mechanism, just skip through the
+ * rest of the list.
+ */
+ if (selected_mechanism)
+ continue;
- conn->sasl_state = pg_fe_scram_init(conn->pguser, password);
- if (!conn->sasl_state)
+ /*
+ * Do we support this mechanism?
+ */
+ if (strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0)
{
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("out of memory\n"));
- return STATUS_ERROR;
+ char *password;
+
+ conn->password_needed = true;
+ password = conn->connhost[conn->whichhost].password;
+ if (password == NULL)
+ password = conn->pgpass;
+ if (password == NULL || password[0] == '\0')
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ PQnoPasswordSupplied);
+ goto error;
+ }
+
+ conn->sasl_state = pg_fe_scram_init(conn->pguser, password);
+ if (!conn->sasl_state)
+ goto oom_error;
+ selected_mechanism = SCRAM_SHA256_NAME;
}
}
- else
+
+ if (!selected_mechanism)
{
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("SASL authentication mechanism %s not supported\n"),
- auth_mechanism);
- return STATUS_ERROR;
+ libpq_gettext("none of the server's SASL authentication mechanisms are supported\n"));
+ goto error;
}
- /* Send the initial client response */
+ /* Get the mechanism-specific Initial Client Response, if any */
pg_fe_scram_exchange(conn->sasl_state,
NULL, -1,
&initialresponse, &initialresponselen,
&done, &success, &conn->errorMessage);
- if (initialresponselen != 0)
- {
- res = pqPacketSend(conn, 'p', initialresponse, initialresponselen);
- free(initialresponse);
-
- if (res != STATUS_OK)
- return STATUS_ERROR;
- }
-
if (done && !success)
+ goto error;
+
+ /*
+ * Build a SASLInitialResponse message, and send it.
+ */
+ if (pqPutMsgStart('p', true, conn))
+ goto error;
+ if (pqPuts(selected_mechanism, conn))
+ goto error;
+ if (initialresponselen >= 0)
{
- /* Use error message, if set already */
- if (conn->errorMessage.len == 0)
- printfPQExpBuffer(&conn->errorMessage,
- "fe_sendauth: error in SASL authentication\n");
- return STATUS_ERROR;
+ if (pqPutInt(initialresponselen, 4, conn))
+ goto error;
+ if (pqPutnchar(initialresponse, initialresponselen, conn))
+ goto error;
}
+ if (pqPutMsgEnd(conn))
+ goto error;
+ if (pqFlush(conn))
+ goto error;
+
+ termPQExpBuffer(&mechanism_buf);
+ if (initialresponse)
+ free(initialresponse);
return STATUS_OK;
+
+error:
+ termPQExpBuffer(&mechanism_buf);
+ if (initialresponse)
+ free(initialresponse);
+ return STATUS_ERROR;
+
+oom_error:
+ termPQExpBuffer(&mechanism_buf);
+ if (initialresponse)
+ free(initialresponse);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return STATUS_ERROR;
}
/*
@@ -561,7 +601,7 @@ pg_SASL_init(PGconn *conn, int payloadLen)
* the protocol.
*/
static int
-pg_SASL_exchange(PGconn *conn, int payloadLen)
+pg_SASL_exchange(PGconn *conn, int payloadLen, bool final)
{
char *output;
int outputlen;
@@ -594,9 +634,20 @@ pg_SASL_exchange(PGconn *conn, int payloadLen)
&done, &success, &conn->errorMessage);
free(challenge); /* don't need the input anymore */
- /* Send the SASL response to the server, if any. */
+ if (final && !done)
+ {
+ if (outputlen != 0)
+ free(output);
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("AuthenticationSASLFinal received from server, but SASL authentication was not completed\n"));
+ return STATUS_ERROR;
+ }
if (outputlen != 0)
{
+ /*
+ * Send the SASL response to the server.
+ */
res = pqPacketSend(conn, 'p', output, outputlen);
free(output);
@@ -914,13 +965,15 @@ pg_fe_sendauth(AuthRequest areq, int payloadLen, PGconn *conn)
break;
case AUTH_REQ_SASL_CONT:
+ case AUTH_REQ_SASL_FIN:
if (conn->sasl_state == NULL)
{
printfPQExpBuffer(&conn->errorMessage,
"fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
return STATUS_ERROR;
}
- if (pg_SASL_exchange(conn, payloadLen) != STATUS_OK)
+ if (pg_SASL_exchange(conn, payloadLen,
+ (areq == AUTH_REQ_SASL_FIN)) != STATUS_OK)
{
/* Use error message, if set already */
if (conn->errorMessage.len == 0)
--
2.11.0
On 12/04/17 19:34, Heikki Linnakangas wrote:
On 04/11/2017 02:32 PM, �lvaro Hern�ndez Tortosa wrote:
So I still see your proposal more awkward and less clear, mixing
things that are separate. But again, your choice :)So, here's my more full-fledged proposal.
The first patch refactors libpq code, by moving the responsibility of
reading the GSS/SSPI/SASL/MD5 specific data from the authentication
request packet, from the enormous switch-case construct in
PQConnectPoll(), into pg_fe_sendauth(). This isn't strictly necessary,
but I think it's useful cleanup anyway, and now that there's a bit
more structure in the AuthenticationSASL message, the old way was
getting awkward.The second patch contains the protocol changes, and adds the
documentation for it.- Heikki
Hi Heikki.
Thanks for the patches :)
By looking at the them, and unless I'm missing something, I don't
see how the extra information for the future implementation of channel
binding would be added (without changing the protocol). Relevant part is:
The message body is a list of SASL authentication mechanisms, in the
server's order of preference. A zero byte is required as terminator after
the last authentication mechanism name. For each mechanism, there is the
following:
<variablelist>
<varlistentry>
<term>
String
</term>
<listitem>
<para>
Name of a SASL authentication mechanism.
</para>
</listitem>
</varlistentry>
</variablelist>
How do you plan to implement it, in future versions, without
modifying the AuthenticationSASL message? Or is it OK to add new fields
to a message in future PostgreSQL versions, without considering that a
protocol change?
On a side note, I'd mention that the list of SASL authentication
mechanisms contains valid IANA Registry SCRAM names
(https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml#scram)for
SCRAM authentication messages (making it more clear what values would be
expected there from the client).
I hope to start testing this from Java the coming weekend. I will
keep you posted.
�lvaro
--
�lvaro Hern�ndez Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Apr 13, 2017 at 2:34 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 04/11/2017 02:32 PM, Álvaro Hernández Tortosa wrote:
So I still see your proposal more awkward and less clear, mixing
things that are separate. But again, your choice :)So, here's my more full-fledged proposal.
The first patch refactors libpq code, by moving the responsibility of
reading the GSS/SSPI/SASL/MD5 specific data from the authentication request
packet, from the enormous switch-case construct in PQConnectPoll(), into
pg_fe_sendauth(). This isn't strictly necessary, but I think it's useful
cleanup anyway, and now that there's a bit more structure in the
AuthenticationSASL message, the old way was getting awkward.The second patch contains the protocol changes, and adds the documentation
for it.
Thanks for the updated patches! I had a close look at them.
Let's begin with 0001...
/*
* Negotiation generated data to be sent to the client.
*/
- elog(DEBUG4, "sending SASL response token of length %u", outputlen);
+ elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+
+ pfree(output);
}
This will leak one byte if output is of length 0. I think that the
pfree call should be moved out of this if condition and only called if
output is not NULL. That's a nit, and in the code this scenario cannot
happen, but I would recommend to be correct.
+static int
+pg_SASL_init(PGconn *conn, int payloadLen)
{
+ char auth_mechanism[21];
So this assumes that any SASL mechanism name will never be longer than
20 characters. Looking at the link of IANA that Alvaro is providing
upthread this is true, could you add a comment that this relies on
such an assumption?
+ if (initialresponselen != 0)
+ {
+ res = pqPacketSend(conn, 'p', initialresponse, initialresponselen);
+ free(initialresponse);
+
+ if (res != STATUS_OK)
+ return STATUS_ERROR;
+ }
Here also initialresponse could be free'd only if it is not NULL.
And now for 0002...
No much changes in the docs I like the split done for GSS and SSPI messages.
+ /*
+ * The StringInfo guarantees that there's a \0 byte after the
+ * response.
+ */
+ Assert(input[inputlen] == '\0');
+ pq_getmsgend(&buf);
Shouldn't this also check input == NULL? This could crash the
assertion and pg_be_scram_exchange is able to handle a NULL input
message.
+ * Parse the list of SASL authentication mechanisms in the
+ * AuthenticationSASL message, and select the best mechanism that we
+ * support. (Only SCRAM-SHA-256 is supported at the moment.)
*/
- if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 0)
+ for (;;)
Just an idea here: being able to enforce the selection with an
environment variable (useful for testing as well in the future).
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Apr 13, 2017 at 6:37 AM, Álvaro Hernández Tortosa
<aht@8kdata.com> wrote:
By looking at the them, and unless I'm missing something, I don't see
how the extra information for the future implementation of channel binding
would be added (without changing the protocol). Relevant part is:The message body is a list of SASL authentication mechanisms, in the
server's order of preference. A zero byte is required as terminator after
the last authentication mechanism name. For each mechanism, there is the
following:
<variablelist>
<varlistentry>
<term>
String
</term>
<listitem>
<para>
Name of a SASL authentication mechanism.
</para>
</listitem>
</varlistentry>
</variablelist>
How do you plan to implement it, in future versions, without modifying
the AuthenticationSASL message? Or is it OK to add new fields to a message
in future PostgreSQL versions, without considering that a protocol change?
I don't quite understand the complain here, it is perfectly fine to
add as many null-terminated names as you want with this model. The
patches would make the server just send one mechanism name now, but
nothing prevents the addition of more.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/13/2017 05:54 AM, Michael Paquier wrote:
On Thu, Apr 13, 2017 at 6:37 AM, Álvaro Hernández Tortosa
<aht@8kdata.com> wrote:By looking at the them, and unless I'm missing something, I don't see
how the extra information for the future implementation of channel binding
would be added (without changing the protocol). Relevant part is:The message body is a list of SASL authentication mechanisms, in the
server's order of preference. A zero byte is required as terminator after
the last authentication mechanism name. For each mechanism, there is the
following:
<variablelist>
<varlistentry>
<term>
String
</term>
<listitem>
<para>
Name of a SASL authentication mechanism.
</para>
</listitem>
</varlistentry>
</variablelist>
How do you plan to implement it, in future versions, without modifying
the AuthenticationSASL message? Or is it OK to add new fields to a message
in future PostgreSQL versions, without considering that a protocol change?I don't quite understand the complain here, it is perfectly fine to
add as many null-terminated names as you want with this model. The
patches would make the server just send one mechanism name now, but
nothing prevents the addition of more.
Right, when we get channel binding, the server will list "SCRAM-SHA-256"
and "SCRAM-SHA-256-PLUS" as the list of mechanisms. And if we get
channel binding using something else than tls-unique, then those will be
added as extra mechanisms, too, e.g. "SCRAM-SHA-256-PLUS tls-awesome".
I don't think that needs to be discussed in the docs yet, because a
client will simply ignore any mechanisms it doesn't understand. Although
perhaps it would be good to mention explicitly that even though SASL
defines that mechanism names have a particular format - all ASCII
upper-case, max 20 chars - the client should accept and ignore arbitrary
strings, not limited to that format.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 13/04/17 13:24, Heikki Linnakangas wrote:
On 04/13/2017 05:54 AM, Michael Paquier wrote:
On Thu, Apr 13, 2017 at 6:37 AM, Álvaro Hernández Tortosa
<aht@8kdata.com> wrote:By looking at the them, and unless I'm missing something, I
don't see
how the extra information for the future implementation of channel
binding
would be added (without changing the protocol). Relevant part is:The message body is a list of SASL authentication mechanisms, in the
server's order of preference. A zero byte is required as terminator
after
the last authentication mechanism name. For each mechanism, there is
the
following:
<variablelist>
<varlistentry>
<term>
String
</term>
<listitem>
<para>
Name of a SASL authentication mechanism.
</para>
</listitem>
</varlistentry>
</variablelist>
How do you plan to implement it, in future versions, without
modifying
the AuthenticationSASL message? Or is it OK to add new fields to a
message
in future PostgreSQL versions, without considering that a protocol
change?I don't quite understand the complain here, it is perfectly fine to
add as many null-terminated names as you want with this model. The
patches would make the server just send one mechanism name now, but
nothing prevents the addition of more.Right, when we get channel binding, the server will list
"SCRAM-SHA-256" and "SCRAM-SHA-256-PLUS" as the list of mechanisms.
And if we get channel binding using something else than tls-unique,
then those will be added as extra mechanisms, too, e.g.
"SCRAM-SHA-256-PLUS tls-awesome".
And how about supporting different SCRAM mechanisms with different
possible channel bindings? Separate by space too? So given a field, is
the first item the SCRAM mechanism, and all the remaning the channel
binding methods? I.e.:
SCRAM-SHA-256-PLUS tls-awesome tls-awesome2 tls-awesome3\0...
Please note that if this is the solution chosen:
- A lot of parsing and convention is required (first arg is the SCRAM
mechanism, succesive are channel binding; tls-unique is always
"implied", etc)
- Channel binding names will be repeated for every SCRAM mechanism with
"-PLUS". This is not needed, SCRAM mechanisms and channel binding are
separate things.
- Channel binding names will not be present on others, making the parser
even more complex.
All this vs, again, stating SCRAM mechanisms on one list and
channel binding on another list, which is to my much more KISS. But...
anyway, if this is the decision made, at least I think this should be
documented now, because client parsers need to be designed one way or
another.
Thanks,
Álvaro
--
Álvaro Hernández Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 13/04/17 04:54, Michael Paquier wrote:
On Thu, Apr 13, 2017 at 6:37 AM, Álvaro Hernández Tortosa
<aht@8kdata.com> wrote:By looking at the them, and unless I'm missing something, I don't see
how the extra information for the future implementation of channel binding
would be added (without changing the protocol). Relevant part is:The message body is a list of SASL authentication mechanisms, in the
server's order of preference. A zero byte is required as terminator after
the last authentication mechanism name. For each mechanism, there is the
following:
<variablelist>
<varlistentry>
<term>
String
</term>
<listitem>
<para>
Name of a SASL authentication mechanism.
</para>
</listitem>
</varlistentry>
</variablelist>
How do you plan to implement it, in future versions, without modifying
the AuthenticationSASL message? Or is it OK to add new fields to a message
in future PostgreSQL versions, without considering that a protocol change?I don't quite understand the complain here, it is perfectly fine to
add as many null-terminated names as you want with this model. The
patches would make the server just send one mechanism name now, but
nothing prevents the addition of more.
I think I explained in my previous reply, but just in case: there
are two lists here: SCRAM mechanism and channel binding mechanisms. They
are orthogonal, you could pick them separately (only with the -PLUS
variants, of course). All two (both SCRAM and channel binding
mechanisms) have to be advertised by the server.
Álvaro
--
Álvaro Hernández Tortosa
-----------
<8K>data
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 04/13/2017 02:35 PM, Álvaro Hernández Tortosa wrote:
On 13/04/17 13:24, Heikki Linnakangas wrote:
Right, when we get channel binding, the server will list
"SCRAM-SHA-256" and "SCRAM-SHA-256-PLUS" as the list of mechanisms.
And if we get channel binding using something else than tls-unique,
then those will be added as extra mechanisms, too, e.g.
"SCRAM-SHA-256-PLUS tls-awesome".And how about supporting different SCRAM mechanisms with different
possible channel bindings? Separate by space too? So given a field, is
the first item the SCRAM mechanism, and all the remaning the channel
binding methods? I.e.:SCRAM-SHA-256-PLUS tls-awesome tls-awesome2 tls-awesome3\0...
I think we're going in circles.. Yeah, we could do that. Or they could
be listed as separate mechanisms:
SCRAM-SHA-256-PLUS\0
SCRAM-SHA-256-PLUS tls-awesome\0
SCRAM-SHA-256-PLUS tls-awesome2\0
SCRAM-SHA-256-PLUS tls-awesome3\0
\0
One alternative is that you could list extra channel bindings that are
supported by all the mechanisms, as separate entries. So the list could be:
binding tls-unique\0
binding tls-awesome\0
binding tls-awesome2\0
binding tls-awesome3\0
SCRAM-SHA-256-PLUS\0
SCRAM-SHA-512-PLUS\0
\0
which would mean that those bindings are supported by all the mechanisms
that follow. I think this would achieve the same thing as your proposed
separate field, with the current proposed protocol.
But again, I'm 99% sure we won't need it, and we don't need to decide
the exact syntax for channel bindings yet. We have the flexibility now,
so we can cross the bridge when we get there.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
My only desire would be to have a final spec and implement the full parser
now, not have to change it in the future. We already know today all the
requirements, so please pick one and I will follow it :)
On Apr 13, 2017 13:47, "Heikki Linnakangas" <hlinnaka@iki.fi> wrote:
Show quoted text
On 04/13/2017 02:35 PM, Álvaro Hernández Tortosa wrote:
On 13/04/17 13:24, Heikki Linnakangas wrote:
Right, when we get channel binding, the server will list
"SCRAM-SHA-256" and "SCRAM-SHA-256-PLUS" as the list of mechanisms.
And if we get channel binding using something else than tls-unique,
then those will be added as extra mechanisms, too, e.g.
"SCRAM-SHA-256-PLUS tls-awesome".And how about supporting different SCRAM mechanisms with different
possible channel bindings? Separate by space too? So given a field, is
the first item the SCRAM mechanism, and all the remaning the channel
binding methods? I.e.:SCRAM-SHA-256-PLUS tls-awesome tls-awesome2 tls-awesome3\0...
I think we're going in circles.. Yeah, we could do that. Or they could be
listed as separate mechanisms:SCRAM-SHA-256-PLUS\0
SCRAM-SHA-256-PLUS tls-awesome\0
SCRAM-SHA-256-PLUS tls-awesome2\0
SCRAM-SHA-256-PLUS tls-awesome3\0
\0One alternative is that you could list extra channel bindings that are
supported by all the mechanisms, as separate entries. So the list could be:binding tls-unique\0
binding tls-awesome\0
binding tls-awesome2\0
binding tls-awesome3\0
SCRAM-SHA-256-PLUS\0
SCRAM-SHA-512-PLUS\0
\0which would mean that those bindings are supported by all the mechanisms
that follow. I think this would achieve the same thing as your proposed
separate field, with the current proposed protocol.But again, I'm 99% sure we won't need it, and we don't need to decide the
exact syntax for channel bindings yet. We have the flexibility now, so we
can cross the bridge when we get there.- Heikki
On 04/13/2017 05:53 AM, Michael Paquier wrote:
Thanks for the updated patches! I had a close look at them.
Let's begin with 0001...
/* * Negotiation generated data to be sent to the client. */ - elog(DEBUG4, "sending SASL response token of length %u", outputlen); + elog(DEBUG4, "sending SASL challenge of length %u", outputlen);sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+
+ pfree(output);
}
This will leak one byte if output is of length 0. I think that the
pfree call should be moved out of this if condition and only called if
output is not NULL. That's a nit, and in the code this scenario cannot
happen, but I would recommend to be correct.
Fixed.
+static int +pg_SASL_init(PGconn *conn, int payloadLen) { + char auth_mechanism[21]; So this assumes that any SASL mechanism name will never be longer than 20 characters. Looking at the link of IANA that Alvaro is providing upthread this is true, could you add a comment that this relies on such an assumption?
I picked 20 characters for the buffer, because that's what the SASL spec
says is the maximum, but note that the code doesn't actually rely on
that. It checks the length of the incoming string, and throws a "SASL
mechanism not supported" error if it doesn't fit in the buffer. 20 is
enough to hold "SCRAM-SHA-256", which is the only supported mechanism.
Also note that the second patch replaces this code, anyway..
+ if (initialresponselen != 0) + { + res = pqPacketSend(conn, 'p', initialresponse, initialresponselen); + free(initialresponse); + + if (res != STATUS_OK) + return STATUS_ERROR; + } Here also initialresponse could be free'd only if it is not NULL.
Fixed.
And now for 0002...
No much changes in the docs I like the split done for GSS and SSPI messages.
+ /* + * The StringInfo guarantees that there's a \0 byte after the + * response. + */ + Assert(input[inputlen] == '\0'); + pq_getmsgend(&buf); Shouldn't this also check input == NULL? This could crash the assertion and pg_be_scram_exchange is able to handle a NULL input message.
Yep, fixed!
+ * Parse the list of SASL authentication mechanisms in the + * AuthenticationSASL message, and select the best mechanism that we + * support. (Only SCRAM-SHA-256 is supported at the moment.) */ - if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 0) + for (;;) Just an idea here: being able to enforce the selection with an environment variable (useful for testing as well in the future).
Hmm. It wouldn't do much, as long as SCRAM-SHA-256 is the only supported
mechanism. In general, there is no way to tell libpq to e.g. not do
plain password authentication, which is more pressing than choosing a
particular SASL mechanism. So I think we should have libpq options to
control that, but it's a bigger feature than just adding a debug
environment variable here.
Thanks for the review! I've pushed these patches, after a bunch of
little cleanups here and there, and fixing a few garden-variety bugs in
the GSS/SSPI changes.
Álvaro, you're good to go and implement the JDBC support based on this.
Thanks! Please keep me informed on how it goes, and let me know if you
run into any trouble, or if there's any discrepancies or ambiguity in
the docs that we should fix.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Apr 14, 2017 at 1:37 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 04/13/2017 05:53 AM, Michael Paquier wrote:
+ * Parse the list of SASL authentication mechanisms in the + * AuthenticationSASL message, and select the best mechanism that we + * support. (Only SCRAM-SHA-256 is supported at the moment.) */ - if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 0) + for (;;) Just an idea here: being able to enforce the selection with an environment variable (useful for testing as well in the future).Hmm. It wouldn't do much, as long as SCRAM-SHA-256 is the only supported
mechanism. In general, there is no way to tell libpq to e.g. not do plain
password authentication, which is more pressing than choosing a particular
SASL mechanism. So I think we should have libpq options to control that, but
it's a bigger feature than just adding a debug environment variable here.
Of course, my last sentence implied that this may be useful once more
than 1 mechanism is added. This definitely cannot be a connection
parameter. Your last sentence makes me guess that we agree on that.
But those are thoughts for later..
Thanks for the review! I've pushed these patches, after a bunch of little
cleanups here and there, and fixing a few garden-variety bugs in the
GSS/SSPI changes.
Committed patches look good to me after a second lookup. Thanks!
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 14 Apr. 2017 10:44, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Fri, Apr 14, 2017 at 1:37 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 04/13/2017 05:53 AM, Michael Paquier wrote:
+ * Parse the list of SASL authentication mechanisms in the + * AuthenticationSASL message, and select the best mechanism that we + * support. (Only SCRAM-SHA-256 is supported at the moment.) */ - if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 0) + for (;;) Just an idea here: being able to enforce the selection with an environment variable (useful for testing as well in the future).Hmm. It wouldn't do much, as long as SCRAM-SHA-256 is the only supported
mechanism. In general, there is no way to tell libpq to e.g. not do plain
password authentication, which is more pressing than choosing a particular
SASL mechanism. So I think we should have libpq options to control that,
but
it's a bigger feature than just adding a debug environment variable here.
Of course, my last sentence implied that this may be useful once more
than 1 mechanism is added. This definitely cannot be a connection
parameter. Your last sentence makes me guess that we agree on that.
But those are thoughts for later..
Are we going to have issues with with mech negotiation re the ability to
store auth data for >1 mech and access it early enough?
Presumably we'll need multiple digests for a user. For example if we want
to allow the choice of mechs scram-256 and scram-512 we need different
stored hashes for the same user in pg_authid. And we'll possibly need to be
able to tell at the time we advertise mechs which users have creds for
which mechs otherwise we'll advertise mechs they can never succeed. The
client has no way to make a sensible choice of mech if some arbitrary
subset (maybe just 1) will work for a given user.
There's no point advertising scram-512 if only -256 can work for 'bob'
because that's what we have in pg_authid.
Yes, filtering the advertised mechs exposes info. But not being able to log
in if you're the legitimate user without configuring the client with your
password hash format would suck too.
Thanks for the review! I've pushed these patches, after a bunch of little
cleanups here and there, and fixing a few garden-variety bugs in the
GSS/SSPI changes.
Committed patches look good to me after a second lookup. Thanks!
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Apr 14, 2017 at 8:28 PM, Craig Ringer
<craig.ringer@2ndquadrant.com> wrote:
There's no point advertising scram-512 if only -256 can work for 'bob'
because that's what we have in pg_authid.
The possibility to have multiple verifiers has other benefits than
that, password rolling being one. We may want to revisit that once
there is a need to have a pg_auth_verifiers, my intuition on the
matter is that we are years away from it, but we'll very likely need
it for more reasons than the one you are raising here.
Yes, filtering the advertised mechs exposes info. But not being able to log
in if you're the legitimate user without configuring the client with your
password hash format would suck too.
Yup.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers