Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-13 Thread Michael Paquier
On Tue, Jul 13, 2021 at 10:41:01PM +, Jacob Champion wrote:
> Just to make sure -- do we want to export the fe-auth-sasl.h header as
> opposed to forward-declaring the pg_fe_sasl_mech struct?

Installing fe-auth-sasl.h has the advantage to make the internals of
the callbacks available to applications playing with the internals.
For SASL, it makes things easier to define new mechanisms out of
core.

> Is the use
> case for libpq-int.h just "here, have at the internals, and if you
> break it then it's on you"?

Yes, it can be useful for applications willing to use the internals of
libpq, like in forks.  There is no guarantee that this will not break
across major version upgrades, so that's up to the user to fix things
once they play with the internals.
--
Michael


signature.asc
Description: PGP signature


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-13 Thread Jacob Champion
On Tue, 2021-07-13 at 22:41 +, Jacob Champion wrote:
> On Tue, 2021-07-13 at 19:31 +0900, Michael Paquier wrote:
> > On Tue, Jul 13, 2021 at 12:41:27PM +0300, Mikhail Kulagin wrote:
> > > 
> > > I think the new fe-auth-sasl.h file should be installed too.
> > > Correction proposal in the attached file (but I'm not sure that fix
> > > of Install.pm is correct). 
> > 
> > That looks correct to me.  I'll check that tomorrow.
> 
> Looks right to me too. I'm currently rebuilding my Windows dev
> environment so I haven't been able to double-check that piece of it.

(Confirmed that this patch works for me on Windows.)

Thanks,
--Jacob


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-13 Thread Jacob Champion
On Tue, 2021-07-13 at 19:31 +0900, Michael Paquier wrote:
> On Tue, Jul 13, 2021 at 12:41:27PM +0300, Mikhail Kulagin wrote:
> > I got an error while building one of the extensions.
> > /home/mkulagin/pg-install/postgresql-master/include/internal/libpq-int.h:44:10:
> >  fatal error: fe-auth-sasl.h: No such file or directory
> >   #include "fe-auth-sasl.h"
> >   ^~~~
> 
> Right.  I overlooked the fact that libpq-int.h is installed.

Thanks for catching that Mikhail.

> > I think the new fe-auth-sasl.h file should be installed too.
> > Correction proposal in the attached file (but I'm not sure that fix
> > of Install.pm is correct). 
> 
> That looks correct to me.  I'll check that tomorrow.

Looks right to me too. I'm currently rebuilding my Windows dev
environment so I haven't been able to double-check that piece of it.

Just to make sure -- do we want to export the fe-auth-sasl.h header as
opposed to forward-declaring the pg_fe_sasl_mech struct? Is the use
case for libpq-int.h just "here, have at the internals, and if you
break it then it's on you"?

--Jacob


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-13 Thread Michael Paquier
On Tue, Jul 13, 2021 at 12:41:27PM +0300, Mikhail Kulagin wrote:
> I got an error while building one of the extensions.
> /home/mkulagin/pg-install/postgresql-master/include/internal/libpq-int.h:44:10:
>  fatal error: fe-auth-sasl.h: No such file or directory
>   #include "fe-auth-sasl.h"
>   ^~~~

Right.  I overlooked the fact that libpq-int.h is installed.

> I think the new fe-auth-sasl.h file should be installed too.
> Correction proposal in the attached file (but I'm not sure that fix
> of Install.pm is correct). 

That looks correct to me.  I'll check that tomorrow.
--
Michael


signature.asc
Description: PGP signature


RE: [PATCH] Pull general SASL framework out of SCRAM

2021-07-13 Thread Mikhail Kulagin
Hello, hackers!

I got an error while building one of the extensions.
/home/mkulagin/pg-install/postgresql-master/include/internal/libpq-int.h:44:10: 
fatal error: fe-auth-sasl.h: No such file or directory
  #include "fe-auth-sasl.h"
  ^~~~

I think the new fe-auth-sasl.h file should be installed too.
Correction proposal in the attached file (but I'm not sure that fix of 
Install.pm is correct).

Regards, Mikhail A. Kulagin
PostgresPro

 


missing_fe-auth-sasl.h.patch
Description: Binary data


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-12 Thread Michael Paquier
On Tue, Jul 13, 2021 at 12:01:46AM +, Jacob Champion wrote:
> Ah, right. I think the (!done && !success) case is probably indicative
> of an API smell, but that's probably something to clean up in a future
> pass.

Yeah, agreed.  I feel that it would should be cleaner to replace those
two booleans with a status enum or a bitmask.
--
Michael


signature.asc
Description: PGP signature


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-12 Thread Jacob Champion
On Sun, 2021-07-11 at 13:16 +0900, Michael Paquier wrote:
> On Fri, Jul 09, 2021 at 11:31:48PM +, Jacob Champion wrote:
> > On Thu, 2021-07-08 at 16:27 +0900, Michael Paquier wrote:
> > > +  *  outputlen: The length (0 or higher) of the client response 
> > > buffer,
> > > +  * invalid if output is NULL.
> > 
> > nitpick: maybe "ignored" instead of "invalid"?
> 
> Thanks, applied as 44bd012 after using your suggestion.

Thanks!

> Another thing I noticed after more review is that the check in
> fe-auth.c to make sure that a message needs to be generated if the
> exchange is not completed yet has no need to depend on "success", only
> "done".

Ah, right. I think the (!done && !success) case is probably indicative
of an API smell, but that's probably something to clean up in a future
pass.

--Jacob


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-10 Thread Michael Paquier
On Fri, Jul 09, 2021 at 11:31:48PM +, Jacob Champion wrote:
> On Thu, 2021-07-08 at 16:27 +0900, Michael Paquier wrote:
>> + *  outputlen: The length (0 or higher) of the client response 
>> buffer,
>> + * invalid if output is NULL.
> 
> nitpick: maybe "ignored" instead of "invalid"?

Thanks, applied as 44bd012 after using your suggestion.

Another thing I noticed after more review is that the check in
fe-auth.c to make sure that a message needs to be generated if the
exchange is not completed yet has no need to depend on "success", only
"done".
--
Michael


signature.asc
Description: PGP signature


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-09 Thread Jacob Champion
On Thu, 2021-07-08 at 16:27 +0900, Michael Paquier wrote:
> I agree that this looks like an improvement in terms of the
> expectations behind a SASL mechanism, so I have done the attached to
> strengthen a bit all those checks.  However, I don't really see a
> point in back-patching any of that, as SCRAM satisfies with its
> implementation already all those conditions AFAIK.

Agreed.

> Thoughts?

LGTM, thanks!

> +  *  outputlen: The length (0 or higher) of the client response 
> buffer,
> +  * invalid if output is NULL.

nitpick: maybe "ignored" instead of "invalid"?

--Jacob


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-08 Thread Michael Paquier
On Wed, Jul 07, 2021 at 03:07:14PM +, Jacob Champion wrote:
> That's correct. But the client may not simply ignore the challenge and
> keep the exchange open waiting for a new one, as pg_SASL_continue()
> currently allows. That's what my TODO is referring to.

I have been looking more at your three points from upthread and
feasted on the SASL RFC, as of:
- Detection that no output is generated on PG_SASL_EXCHANGE_FAILURE
for the backend.
- Handling of zero-length messages in the frontend.  The backend
handles that already, and SCRAM would complain if sending such
messages, but I can see why you'd want to allow that for other
mechanisms.
- Making sure that a mechanism generates a message in the middle of
the exchange in the frontend.

I agree that this looks like an improvement in terms of the
expectations behind a SASL mechanism, so I have done the attached to
strengthen a bit all those checks.  However, I don't really see a
point in back-patching any of that, as SCRAM satisfies with its
implementation already all those conditions AFAIK.  So that's an
improvement of the current code, and it fits nicely with the SASL
refactoring for the documentation of the callbacks.

Thoughts?
--
Michael
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 3e4f763b60..6d25997079 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -171,6 +171,13 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
 
 		if (output)
 		{
+			/*
+			 * PG_SASL_EXCHANGE_FAILURE with some output is forbidden by
+			 * SASL.  Make sure here that the mechanism used got that right.
+			 */
+			if (result == PG_SASL_EXCHANGE_FAILURE)
+elog(ERROR, "output message found after SASL exchange failure");
+
 			/*
 			 * Negotiation generated data to be sent to the client.
 			 */
diff --git a/src/interfaces/libpq/fe-auth-sasl.h b/src/interfaces/libpq/fe-auth-sasl.h
index 0aec588a9e..180f4642aa 100644
--- a/src/interfaces/libpq/fe-auth-sasl.h
+++ b/src/interfaces/libpq/fe-auth-sasl.h
@@ -78,11 +78,12 @@ typedef struct pg_fe_sasl_mech
 	 * Output parameters, to be set by the callback function:
 	 *
 	 *	output:	   A malloc'd buffer containing the client's response to
-	 *			   the server, or NULL if the exchange should be aborted.
-	 *			   (*success should be set to false in the latter case.)
+	 *			   the server (can be empty), or NULL if the exchange should
+	 *			   be aborted.  (*success should be set to false in the
+	 *			   latter case.)
 	 *
-	 *	outputlen: The length of the client response buffer, or zero if no
-	 *			   data should be sent due to an exchange failure
+	 *	outputlen: The length (0 or higher) of the client response buffer,
+	 *			   invalid if output is NULL.
 	 *
 	 *	done:  Set to true if the SASL exchange should not continue,
 	 *			   because the exchange is either complete or failed
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index eaba0ba56d..b383a575d3 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -674,7 +674,22 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
 		return STATUS_ERROR;
 	}
 
-	if (outputlen != 0)
+	/*
+	 * If the exchange is a success but not yet finished, we need to make sure
+	 * that the SASL mechanism has generated a message to send back.
+	 */
+	if (output == NULL && success && !done)
+	{
+		appendPQExpBufferStr(>errorMessage,
+			 libpq_gettext("no client response found after SASL exchange success\n"));
+		return STATUS_ERROR;
+	}
+
+	/*
+	 * SASL allows zero-length responses, so this check uses "output" and
+	 * not "outputlen" to allow the case of an empty message.
+	 */
+	if (output)
 	{
 		/*
 		 * Send the SASL response to the server.


signature.asc
Description: PGP signature


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-07 Thread Jacob Champion
On Wed, 2021-07-07 at 14:08 +0900, Michael Paquier wrote:
> On Tue, Jul 06, 2021 at 06:20:49PM +, Jacob Champion wrote:
> > On Mon, 2021-07-05 at 17:17 +0900, Michael Paquier wrote:
> > 
> > > Hmm.  Does the RFCs tell us anything about that?
> > 
> > Just in general terms:
> > 
> > >Each authentication exchange consists of a message from the client to
> > >the server requesting authentication via a particular mechanism,
> > >followed by one or more pairs of challenges from the server and
> > >responses from the client, followed by a message from the server
> > >indicating the outcome of the authentication exchange.  (Note:
> > >exchanges may also be aborted as discussed in Section 3.5.)
> > 
> > So a challenge must be met with a response, or the exchange must be
> > aborted. (And I don't think our protocol implementation provides a
> > client abort message; if something goes wrong, we just tear down the
> > connection.)
> 
> Thanks.  At the same time, section 3.5 also says that the client may
> send a message to abort.  So one can interpret that the client has
> also the choice to abort without sending a response back to the
> server?  Or I am just interpreting incorrectly the use of "may" in
> this context?

That's correct. But the client may not simply ignore the challenge and
keep the exchange open waiting for a new one, as pg_SASL_continue()
currently allows. That's what my TODO is referring to.

--Jacob



Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-06 Thread Michael Paquier
On Tue, Jul 06, 2021 at 06:20:49PM +, Jacob Champion wrote:
> On Mon, 2021-07-05 at 17:17 +0900, Michael Paquier wrote:
> Each name must be null-terminated, not just null-separated. That way
> the list of names ends with an empty string:
> 
> name-one\0  <- added by the mechanism
>   name-two\0<- added by the mechanism
> \0  <- added by the framework
> 
> The way it's worded now, I could see some implementers failing to
> terminate the final name because the framework adds a trailing null
> already -- but the framework is terminating the list, not the final
> name.

Good point.  I have used ending with '\0' bytes instead.

>> + * init()
>> + *
>> + * Initializes mechanism-specific state for a connection.  This
>> + * callback must return a pointer to its allocated state, which will
>> + * be passed as-is as the first argument to the other callbacks.
>> + * free() is called to release any state resources.
> 
> Maybe say "The free() callback is called" to differentiate it from
> standard free()?

Yes, that could be confusing.  Switched to your wording instead.

> It's possible for conn->sasl to be NULL here, say if the client has
> channel_binding=require but connects as a user with an MD5 secret. The
> SCRAM TAP tests have one such case.

Indeed.

>> Hmm.  Does the RFCs tell us anything about that?
> 
> Just in general terms:
> 
>>Each authentication exchange consists of a message from the client to
>>the server requesting authentication via a particular mechanism,
>>followed by one or more pairs of challenges from the server and
>>responses from the client, followed by a message from the server
>>indicating the outcome of the authentication exchange.  (Note:
>>exchanges may also be aborted as discussed in Section 3.5.)
> 
> So a challenge must be met with a response, or the exchange must be
> aborted. (And I don't think our protocol implementation provides a
> client abort message; if something goes wrong, we just tear down the
> connection.)

Thanks.  At the same time, section 3.5 also says that the client may
send a message to abort.  So one can interpret that the client has
also the choice to abort without sending a response back to the
server?  Or I am just interpreting incorrectly the use of "may" in
this context?
--
Michael


signature.asc
Description: PGP signature


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-06 Thread Jacob Champion
On Mon, 2021-07-05 at 17:17 +0900, Michael Paquier wrote:
> On Wed, Jun 30, 2021 at 10:30:12PM +, Jacob Champion wrote:
> > Done in v3, with a second patch for the code motion.
> 
> I have gone through that, tweaking the documentation you have added as
> that's the meat of the patch, reworking a bit the declarations of the
> callbacks (no need for several typedef gere) and doing some small
> format changes to make the indentation happy.  And that looks pretty
> good.

Looks very good, thanks! A few comments on the docs changes:

> +  * Output parameters:
> +  *
> +  *  buf:  A StringInfo buffer that the callback should populate with
> +  *supported mechanism names.  The names are appended 
> into this
> +  *StringInfo, separated by '\0' bytes.

Each name must be null-terminated, not just null-separated. That way
the list of names ends with an empty string:

name-one\0  <- added by the mechanism
  name-two\0<- added by the mechanism
\0  <- added by the framework

The way it's worded now, I could see some implementers failing to
terminate the final name because the framework adds a trailing null
already -- but the framework is terminating the list, not the final
name.

> +  * init()
> +  *
> +  * Initializes mechanism-specific state for a connection.  This
> +  * callback must return a pointer to its allocated state, which will
> +  * be passed as-is as the first argument to the other callbacks.
> +  * free() is called to release any state resources.

Maybe say "The free() callback is called" to differentiate it from
standard free()?

> It is a bit sad that the SCRAM part cannot be completely
> unplugged from the auth part, because of the call to the free function
> and the HBA checks, but adding more wrappers to accomodate with that
> is not really worth it.

Yeah. I think that additional improvements/refactoring here will come
naturally if clients are ever allowed to negotiate SASL mechanisms in
the future. Doesn't need to happen now.

> -   if (!pg_fe_scram_channel_bound(conn->sasl_state))
> +   if (!conn->sasl || 
> !conn->sasl->channel_bound(conn->sasl_state))
> conn->sasl should be set in this code path.  This style is safer.

It's possible for conn->sasl to be NULL here, say if the client has
channel_binding=require but connects as a user with an MD5 secret. The
SCRAM TAP tests have one such case.

> The top comment of scram_init() still mentioned
> pg_be_scram_get_mechanisms(), while it should be
> scram_get_mechanisms().
> 
> PG_MAX_SASL_MESSAGE_LENGTH can stay within auth-sasl.c.

Looks good to me.

> > - I don't think it's legal for a client to refuse a challenge from the
> > server without aborting the exchange, so we should probably check to
> > make sure that client responses are non-NULL in the success case.
> 
> Hmm.  Does the RFCs tell us anything about that?

Just in general terms:

>Each authentication exchange consists of a message from the client to
>the server requesting authentication via a particular mechanism,
>followed by one or more pairs of challenges from the server and
>responses from the client, followed by a message from the server
>indicating the outcome of the authentication exchange.  (Note:
>exchanges may also be aborted as discussed in Section 3.5.)

So a challenge must be met with a response, or the exchange must be
aborted. (And I don't think our protocol implementation provides a
client abort message; if something goes wrong, we just tear down the
connection.)

Thanks,
--Jacob


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-07-05 Thread Michael Paquier
On Wed, Jun 30, 2021 at 10:30:12PM +, Jacob Champion wrote:
> Done in v3, with a second patch for the code motion.

I have gone through that, tweaking the documentation you have added as
that's the meat of the patch, reworking a bit the declarations of the
callbacks (no need for several typedef gere) and doing some small
format changes to make the indentation happy.  And that looks pretty
good.  It is a bit sad that the SCRAM part cannot be completely
unplugged from the auth part, because of the call to the free function
and the HBA checks, but adding more wrappers to accomodate with that
is not really worth it.  So I'd like to apply that to clarify this
code layer, without the TODOs.

-   pg_be_scram_get_mechanisms(port, _mechs);
-   /* Put another '\0' to mark that list is finished. */
-   appendStringInfoChar(_mechs, '\0');
I was wondering for a couple of seconds if it would not be better to
let the last '\0' being set within the callback, but what you have
here looks better.

-   if (!pg_fe_scram_channel_bound(conn->sasl_state))
+   if (!conn->sasl || !conn->sasl->channel_bound(conn->sasl_state))
conn->sasl should be set in this code path.  This style is safer.

The top comment of scram_init() still mentioned
pg_be_scram_get_mechanisms(), while it should be
scram_get_mechanisms().

PG_MAX_SASL_MESSAGE_LENGTH can stay within auth-sasl.c.

> I added a first pass at API documentation as well. This exposed some
> additional front-end TODOs that I added inline, but they should
> probably be dealt with independently of the refactor:
> 
> - Zero-length client responses are legal in the SASL framework;
> currently we use zero as a sentinel for "don't send a response".

Check.

> - I don't think it's legal for a client to refuse a challenge from the
> server without aborting the exchange, so we should probably check to
> make sure that client responses are non-NULL in the success case.

Hmm.  Does the RFCs tell us anything about that?
--
Michael
From 3bfae6e6f563acfe63f2ae44feb5799f4e49324b Mon Sep 17 00:00:00 2001
From: Michael Paquier 
Date: Mon, 5 Jul 2021 17:16:15 +0900
Subject: [PATCH v4] Generalize SASL exchange code for the backend and the
 frontend

---
 src/include/libpq/auth.h |   2 +
 src/include/libpq/sasl.h | 136 +++
 src/include/libpq/scram.h|  13 +-
 src/backend/libpq/Makefile   |   1 +
 src/backend/libpq/auth-sasl.c| 196 +++
 src/backend/libpq/auth-scram.c   |  51 ---
 src/backend/libpq/auth.c | 167 +--
 src/interfaces/libpq/fe-auth-sasl.h  | 130 ++
 src/interfaces/libpq/fe-auth-scram.c |  40 --
 src/interfaces/libpq/fe-auth.c   |  23 +++-
 src/interfaces/libpq/fe-auth.h   |  11 +-
 src/interfaces/libpq/fe-connect.c|   6 +-
 src/interfaces/libpq/libpq-int.h |   2 +
 src/tools/pgindent/typedefs.list |   2 +
 14 files changed, 558 insertions(+), 222 deletions(-)
 create mode 100644 src/include/libpq/sasl.h
 create mode 100644 src/backend/libpq/auth-sasl.c
 create mode 100644 src/interfaces/libpq/fe-auth-sasl.h

diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 3610fae3ff..3d6734f253 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -21,6 +21,8 @@ extern bool pg_krb_caseins_users;
 extern char *pg_krb_realm;
 
 extern void ClientAuthentication(Port *port);
+extern void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
+			int extralen);
 
 /* Hook for plugins to get control in ClientAuthentication() */
 typedef void (*ClientAuthentication_hook_type) (Port *, int);
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
new file mode 100644
index 00..f119a62d68
--- /dev/null
+++ b/src/include/libpq/sasl.h
@@ -0,0 +1,136 @@
+/*-
+ *
+ * sasl.h
+ *	  Defines the SASL mechanism interface for the backend.
+ *
+ * Each SASL mechanism defines a frontend and a backend callback structure.
+ *
+ * See src/interfaces/libpq/fe-auth-sasl.h for the frontend counterpart.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/sasl.h
+ *
+ *-
+ */
+
+#ifndef PG_SASL_H
+#define PG_SASL_H
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq-be.h"
+
+/* Status codes for message exchange */
+#define PG_SASL_EXCHANGE_CONTINUE		0
+#define PG_SASL_EXCHANGE_SUCCESS		1
+#define PG_SASL_EXCHANGE_FAILURE		2
+
+/*
+ * Backend SASL mechanism callbacks.
+ *
+ * To implement a backend mechanism, declare a pg_be_sasl_mech struct with
+ * appropriate callback implementations.  Then pass the mechanism to
+ * CheckSASLAuth() during ClientAuthentication(), once the server has decided
+ * which 

Re: [PATCH] Pull general SASL framework out of SCRAM

2021-06-30 Thread Jacob Champion
On Sat, 2021-06-26 at 09:47 +0900, Michael Paquier wrote:
> On Fri, Jun 25, 2021 at 11:40:33PM +, Jacob Champion wrote:
> > I can definitely move it (into, say, auth-sasl.c?). I'll probably do
> > that in a second commit, though, since keeping it in place during the
> > refactor makes the review easier IMO.
> 
> auth-sasl.c is a name consistent with the existing practice.
> 
> > Can do. Does libpq-int-sasl.h work as a filename? This should not be
> > exported to applications.
> 
> I would still with the existing naming used by fe-gssapi-common.h, so
> that would be fe-auth-sasl.c and fe-auth-sasl.h, with the header
> remaining internal.  Not strongly wedded to this name, of course, that
> just seems consistent.

Done in v3, with a second patch for the code motion.

I added a first pass at API documentation as well. This exposed some
additional front-end TODOs that I added inline, but they should
probably be dealt with independently of the refactor:

- Zero-length client responses are legal in the SASL framework;
currently we use zero as a sentinel for "don't send a response".

- I don't think it's legal for a client to refuse a challenge from the
server without aborting the exchange, so we should probably check to
make sure that client responses are non-NULL in the success case.

--Jacob
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..6d385fd6a4 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
 # be-fsstubs is here for historical reasons, probably belongs elsewhere
 
 OBJS = \
+   auth-sasl.o \
auth-scram.o \
auth.o \
be-fsstubs.o \
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
new file mode 100644
index 00..b7cdb2ecf6
--- /dev/null
+++ b/src/backend/libpq/auth-sasl.c
@@ -0,0 +1,187 @@
+/*-
+ *
+ * auth-sasl.c
+ *   Routines to handle network authentication via SASL
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *   src/backend/libpq/auth-sasl.c
+ *
+ *-
+ */
+
+#include "postgres.h"
+
+#include "libpq/auth.h"
+#include "libpq/libpq.h"
+#include "libpq/pqformat.h"
+#include "libpq/sasl.h"
+
+/*
+ * Perform a SASL exchange with a libpq client, using a specific mechanism
+ * implementation.
+ *
+ * shadow_pass is an optional pointer to the shadow entry for the client's
+ * presented user name. For mechanisms that use shadowed passwords, a NULL
+ * pointer here means that an entry could not be found for the user (or the 
user
+ * does not exist), and the mechanism should fail the authentication exchange.
+ *
+ * Mechanisms must take care not to reveal to the client that a user entry does
+ * not exist; ideally, the external failure mode is identical to that of an
+ * incorrect password. Mechanisms may instead use the logdetail output 
parameter
+ * to internally differentiate between failure cases and assist debugging by 
the
+ * server admin.
+ *
+ * A mechanism is not required to utilize a shadow entry, or even a password
+ * system at all; for these cases, shadow_pass may be ignored and the caller
+ * should just pass NULL.
+ */
+int
+CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+ char **logdetail)
+{
+   StringInfoData sasl_mechs;
+   int mtype;
+   StringInfoData buf;
+   void   *opaq = NULL;
+   char   *output = NULL;
+   int outputlen = 0;
+   const char *input;
+   int inputlen;
+   int result;
+   boolinitial;
+
+   /*
+* Send the SASL authentication request to user.  It includes the list 
of
+* authentication mechanisms that are supported.
+*/
+   initStringInfo(_mechs);
+
+   mech->get_mechanisms(port, _mechs);
+   /* Put another '\0' to mark that list is finished. */
+   appendStringInfoChar(_mechs, '\0');
+
+   sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
+   pfree(sasl_mechs.data);
+
+   /*
+* Loop through SASL message exchange.  This exchange can consist of
+* multiple messages sent in both directions.  First message is always
+* from the client.  All messages from client to server are password
+* packets (type 'p').
+*/
+   initial = true;
+   do
+   {
+   pq_startmsgread();
+   mtype = pq_getbyte();
+   if (mtype != 'p')
+   {
+   /* Only log error if client didn't disconnect. */
+   if (mtype != EOF)
+ 

Re: [PATCH] Pull general SASL framework out of SCRAM

2021-06-25 Thread Michael Paquier
On Fri, Jun 25, 2021 at 11:40:33PM +, Jacob Champion wrote:
> I can definitely move it (into, say, auth-sasl.c?). I'll probably do
> that in a second commit, though, since keeping it in place during the
> refactor makes the review easier IMO.

auth-sasl.c is a name consistent with the existing practice.

> Can do. Does libpq-int-sasl.h work as a filename? This should not be
> exported to applications.

I would still with the existing naming used by fe-gssapi-common.h, so
that would be fe-auth-sasl.c and fe-auth-sasl.h, with the header
remaining internal.  Not strongly wedded to this name, of course, that
just seems consistent.
--
Michael


signature.asc
Description: PGP signature


Re: [PATCH] Pull general SASL framework out of SCRAM

2021-06-25 Thread Jacob Champion
On Wed, 2021-06-23 at 16:38 +0900, Michael Paquier wrote:
> On Tue, Jun 22, 2021 at 10:37:29PM +, Jacob Champion wrote:
> > Currently, the SASL logic is tightly coupled to the SCRAM
> > implementation. This patch untangles the two, by introducing callback
> > structs for both the frontend and backend.
> 
> The approach to define and have a set callbacks feels natural.

Good, thanks!

> +/* Status codes for message exchange */
> +#define SASL_EXCHANGE_CONTINUE 0
> +#define SASL_EXCHANGE_SUCCESS  1
> +#define SASL_EXCHANGE_FAILURE  2
> 
> It may be better to prefix those with PG_ as they begin to be
> published.

Added in v2.

> +/* Backend mechanism API */
> +typedef void  (*pg_be_sasl_mechanism_func)(Port *, StringInfo);
> +typedef void *(*pg_be_sasl_init_func)(Port *, const char *, const
> char *);
> +typedef int   (*pg_be_sasl_exchange_func)(void *, const char *, int,
> char **, int *, char **);
> +
> +typedef struct
> +{
> +   pg_be_sasl_mechanism_func   get_mechanisms;
> +   pg_be_sasl_init_funcinit;
> +   pg_be_sasl_exchange_funcexchange;
> +} pg_be_sasl_mech;
> 
> All this is going to require much more documentation to explain what
> is the role of those callbacks and what they are here for.

Yes, definitely. If the current approach seems generally workable, I'll
get started on that.

> Another thing that is not tackled by this patch is the format of the
> messages exchanged which is something only in SCRAM now.  Perhaps it
> would be better to extract the small-ish routines currently in
> fe-auth-scram.c and auth-scram.c that we use to grab values associated
> to an attribute in an exchange message and put them in a central place
> like an auth-sasl.c and fe-auth-sasl.c.  This move could also make
> sense for the exising init and continue routines for SASL in
> fe-auth.c.

We can. I recommend waiting for another GS2 mechanism implementation,
though.

The attribute/value encoding is not part of core SASL (see [1] for that
RFC), and OAUTHBEARER is not technically a GS2 mechanism -- though it
makes use of a vestigal GS2 header block, apparently in the hopes that
one day it might become one. So we could pull out the similarities now,
but I'd hate to extract the wrong abstractions and make someone else
untangle it later.

> +static int
> +CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
> +{
> +   return SASL_exchange(_be_scram_mech, port, shadow_pass, logdetail);
> +}
> It may be cleaner to live without this thin wrapper.  It is a bit
> strange to have a SCRAM API in a file where we want mostly SASL things
> (Okay, uaScram does not count as this is assigned after the HBA
> lookup).  Moving any SASL-related things into a separate file may be a
> cleaner option, especially considering that we have a bit more than
> the exchange itself, like message handling.

Heh, I figured that at ~3500 lines, you all just really wanted the
Check* implementations to live in auth.c. :D

I can definitely move it (into, say, auth-sasl.c?). I'll probably do
that in a second commit, though, since keeping it in place during the
refactor makes the review easier IMO.

> +typedef void *(*pg_sasl_init_func)(PGconn *, const char *, const char
> *);
> +typedef void  (*pg_sasl_exchange_func)(void *, char *, int, char **,
> int *, bool *, bool *);
> +typedef bool  (*pg_sasl_channel_bound_func)(void *);
> +typedef void  (*pg_sasl_free_func)(void *);
> +
> +typedef struct
> +{
> +   pg_sasl_init_func   init;
> +   pg_sasl_exchange_func   exchange;
> +   pg_sasl_channel_bound_func  channel_bound;
> +   pg_sasl_free_func   free;
> +} pg_sasl_mech;
> These would be better into a separate header, with more
> documentation.

Can do. Does libpq-int-sasl.h work as a filename? This should not be
exported to applications.

> It may be more consistent with the backend to name
> that pg_fe_sasl_mech?

Done in v2.

> It looks like there is enough material for a callback able to handle
> channel binding.  In the main patch for OAUTHBEARER, I can see for
> example that the handling of OAUTHBEARER-PLUS copied from its SCRAM
> sibling.  That does not need to be tackled in the same patch.  Just
> noting it on the way.

OAUTHBEARER doesn't support channel binding -- there's no OAUTHBEARER-
PLUS, and there probably won't ever be, given the mechanism's
simplicity -- so I'd recommend that this wait for a second GS2
mechanism implementation, as well.

> > (Note that our protocol implementation provides an "additional data"
> > field for the initial client response, but *not* for the authentication
> > outcome. That seems odd to me, but it is what it is, I suppose.)
> 
> You are referring to the protocol implementation as of
> AuthenticationSASLFinal, right?

Yes, but I misremembered. My statement was wrong -- we do allow for
additional data in the authentication outcome from the server.

For AuthenticationSASLFinal, we don't distinguish between "no
additional data" and "additional data of length 

Re: [PATCH] Pull general SASL framework out of SCRAM

2021-06-23 Thread Michael Paquier
On Tue, Jun 22, 2021 at 10:37:29PM +, Jacob Champion wrote:
> Currently, the SASL logic is tightly coupled to the SCRAM
> implementation. This patch untangles the two, by introducing callback
> structs for both the frontend and backend.

The approach to define and have a set callbacks feels natural.

+/* Status codes for message exchange */
+#define SASL_EXCHANGE_CONTINUE 0
+#define SASL_EXCHANGE_SUCCESS  1
+#define SASL_EXCHANGE_FAILURE  2

It may be better to prefix those with PG_ as they begin to be
published.

+/* Backend mechanism API */
+typedef void  (*pg_be_sasl_mechanism_func)(Port *, StringInfo);
+typedef void *(*pg_be_sasl_init_func)(Port *, const char *, const
char *);
+typedef int   (*pg_be_sasl_exchange_func)(void *, const char *, int,
char **, int *, char **);
+
+typedef struct
+{
+   pg_be_sasl_mechanism_func   get_mechanisms;
+   pg_be_sasl_init_funcinit;
+   pg_be_sasl_exchange_funcexchange;
+} pg_be_sasl_mech;

All this is going to require much more documentation to explain what
is the role of those callbacks and what they are here for.

Another thing that is not tackled by this patch is the format of the
messages exchanged which is something only in SCRAM now.  Perhaps it
would be better to extract the small-ish routines currently in
fe-auth-scram.c and auth-scram.c that we use to grab values associated
to an attribute in an exchange message and put them in a central place
like an auth-sasl.c and fe-auth-sasl.c.  This move could also make
sense for the exising init and continue routines for SASL in
fe-auth.c.

+static int
+CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
+{
+   return SASL_exchange(_be_scram_mech, port, shadow_pass, logdetail);
+}
It may be cleaner to live without this thin wrapper.  It is a bit
strange to have a SCRAM API in a file where we want mostly SASL things
(Okay, uaScram does not count as this is assigned after the HBA
lookup).  Moving any SASL-related things into a separate file may be a
cleaner option, especially considering that we have a bit more than
the exchange itself, like message handling.

+typedef void *(*pg_sasl_init_func)(PGconn *, const char *, const char
*);
+typedef void  (*pg_sasl_exchange_func)(void *, char *, int, char **,
int *, bool *, bool *);
+typedef bool  (*pg_sasl_channel_bound_func)(void *);
+typedef void  (*pg_sasl_free_func)(void *);
+
+typedef struct
+{
+   pg_sasl_init_func   init;
+   pg_sasl_exchange_func   exchange;
+   pg_sasl_channel_bound_func  channel_bound;
+   pg_sasl_free_func   free;
+} pg_sasl_mech;
These would be better into a separate header, with more
documentation.  It may be more consistent with the backend to name
that pg_fe_sasl_mech?

It looks like there is enough material for a callback able to handle
channel binding.  In the main patch for OAUTHBEARER, I can see for
example that the handling of OAUTHBEARER-PLUS copied from its SCRAM
sibling.  That does not need to be tackled in the same patch.  Just
noting it on the way.

> (Note that our protocol implementation provides an "additional data"
> field for the initial client response, but *not* for the authentication
> outcome. That seems odd to me, but it is what it is, I suppose.)

You are referring to the protocol implementation as of
AuthenticationSASLFinal, right?

> Regarding that specific TODO -- I think it'd be good for the framework
> to fail hard if a mechanism tries to send data during a failure
> outcome, as it probably means the mechanism isn't implemented to spec.

Agreed.  That would mean patching libpq to add more safeguards in
pg_SASL_continue() if I am following correctly.
--
Michael


signature.asc
Description: PGP signature


[PATCH] Pull general SASL framework out of SCRAM

2021-06-22 Thread Jacob Champion
(This is split off from my work on OAUTHBEARER [1].)

Currently, the SASL logic is tightly coupled to the SCRAM
implementation. This patch untangles the two, by introducing callback
structs for both the frontend and backend.

In the original thread, Michael Paquier commented:

> +   /* TODO: SASL_EXCHANGE_FAILURE with output is forbidden in SASL */
> if (result == SASL_EXCHANGE_SUCCESS)
> sendAuthRequest(port,
> AUTH_REQ_SASL_FIN,
> output,
> outputlen);
> Perhaps that's an issue we need to worry on its own?  I didn't recall
> this part..

Yeah, it was non-obvious to me on the first read too. It's a
consequence of a couple parts of the SASL spec [2]:

>The protocol may include an optional additional data field in this
>outcome message.  This field can only include additional data when
>the outcome is successful.

and

>SASL mechanism specifications MUST supply the following information:
> 
>[...]
> 
>   b) An indication of whether the server is expected to provide
>  additional data when indicating a successful outcome.  If so,
>  if the server sends the additional data as a challenge, the
>  specification MUST indicate that the response to this challenge
>  is an empty response.

There isn't a corresponding provision for data for a *failed* outcome,
so any such data would have to be sent as a separate, mechanism-
specific, challenge. This is what OAUTHBEARER has to do, with an
annoying "failure handshake".

(Note that our protocol implementation provides an "additional data"
field for the initial client response, but *not* for the authentication
outcome. That seems odd to me, but it is what it is, I suppose.)

Regarding that specific TODO -- I think it'd be good for the framework
to fail hard if a mechanism tries to send data during a failure
outcome, as it probably means the mechanism isn't implemented to spec.

--Jacob

[1] 
https://www.postgresql.org/message-id/d1b467a78e0e36ed85a09adf979d04cf124a9d4b.ca...@vmware.com
[2] https://datatracker.ietf.org/doc/html/rfc4422
From a6a65b66cc3dc5da7219378dbadb090ff10fd42b Mon Sep 17 00:00:00 2001
From: Jacob Champion 
Date: Tue, 13 Apr 2021 10:25:48 -0700
Subject: [PATCH 1/7] auth: generalize SASL mechanisms

Split the SASL logic out from the SCRAM implementation, so that it can
be reused by other mechanisms.  New implementations will implement both
a pg_sasl_mech and a pg_be_sasl_mech.
---
 src/backend/libpq/auth-scram.c   | 34 ++-
 src/backend/libpq/auth.c | 34 ---
 src/include/libpq/sasl.h | 34 +++
 src/include/libpq/scram.h| 13 +++--
 src/interfaces/libpq/fe-auth-scram.c | 40 +++-
 src/interfaces/libpq/fe-auth.c   | 16 ---
 src/interfaces/libpq/fe-auth.h   | 11 ++--
 src/interfaces/libpq/fe-connect.c|  6 +
 src/interfaces/libpq/libpq-int.h | 14 ++
 9 files changed, 139 insertions(+), 63 deletions(-)
 create mode 100644 src/include/libpq/sasl.h

diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index f9e1026a12..db3ca75a60 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -101,11 +101,25 @@
 #include "common/sha2.h"
 #include "libpq/auth.h"
 #include "libpq/crypt.h"
+#include "libpq/sasl.h"
 #include "libpq/scram.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
 
+static void  scram_get_mechanisms(Port *port, StringInfo buf);
+static void *scram_init(Port *port, const char *selected_mech,
+		const char *shadow_pass);
+static int   scram_exchange(void *opaq, const char *input, int inputlen,
+			char **output, int *outputlen, char **logdetail);
+
+/* Mechanism declaration */
+const pg_be_sasl_mech pg_be_scram_mech = {
+	scram_get_mechanisms,
+	scram_init,
+	scram_exchange,
+};
+
 /*
  * Status data for a SCRAM authentication exchange.  This should be kept
  * internal to this file.
@@ -170,16 +184,14 @@ static char *sanitize_str(const char *s);
 static char *scram_mock_salt(const char *username);
 
 /*
- * pg_be_scram_get_mechanisms
- *
  * Get a list of SASL mechanisms that this module supports.
  *
  * For the convenience of building the FE/BE packet that lists the
  * mechanisms, the names are appended to the given StringInfo buffer,
  * separated by '\0' bytes.
  */
-void
-pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
+static void
+scram_get_mechanisms(Port *port, StringInfo buf)
 {
 	/*
 	 * Advertise the mechanisms in decreasing order of importance.  So the
@@ -199,8 +211,6 @@ pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
 }
 
 /*
- * pg_be_scram_init
- *
  * Initialize a new SCRAM authentication exchange status tracker.  This
  * needs to be called before doing any exchange.  It will be filled