WIP: SCRAM authentication

Started by Heikki Linnakangasalmost 11 years ago77 messages
#1Heikki Linnakangas
hlinnaka@iki.fi
4 attachment(s)

There have been numerous threads on replacing our MD5 authentication
method, so I started hacking on that to see what it might look like.
Just to be clear, this is 9.6 material. Attached is a WIP patch series
that adds support for SCRAM. There's no need to look at the details yet,
but it demonstrates what the protocol changes and the code structure
would be like.

I'm not wedded to SCRAM - SRP or JPAKE or something else might be
better. But replacing the algorithm, or adding more of them, should be
straightforward with this.

There is no negotiation of the authentication mechanism. SCRAM is just
added as a new one, alongside all the existing ones. If the server
requests SCRAM authentication, but the client doesn't support it, the
attempt will fail. We might want to do something about that, to make the
transition easier, but it's an orthogonal feature and not absolutely
required.

There are four patches in the series. The first two are just
refactoring: moving the SHA-1 implementation from pgcrypto to
src/common, and some refactoring in src/backend/auth.c that IMHO would
make sense anyway.

Patches three and four are the interesting ones:

3. Allow storing multiple verifiers in pg_authid
------------------------------------------------

Replace the pg_authid.rolpassword text field with an array, and rename
it to 'rolverifiers'. This allows storing multiple password hashes: an
MD5 hash for MD5 authentication, and a SCRAM salt and stored key for
SCRAM authentication, etc. Each element in the array is a string that
begins with the method's name. For example "md5:<MD5 hash>", or
"password:<plaintext>".

For dump/reload, and for clients that wish to create the hashes in the
client-side, there is a new option to CREATE/ALTER USER commands:
PASSWORD VERIFIERS '{ ... }', that allows replacing the array.

The old "ENCRYPTED/UNENCRYPTED PASSWORD 'foo'" options are still
supported for backwards-compatibility, but it's not clear what it should
mean now.

TODO:

* Password-checking hook needs to be redesigned, to allow for more kinds
of hashes.

* With "CREATE USER PASSWORD 'foo'", which hashes/verifiers should be
generated by default? We currently have a boolean password_encryption
setting for that. Needs to be a list.

4. Implement SCRAM
------------------

The protocol and the code is structured so that it would be fairly easy
to add more built-in SASL mechanisms, or to use a SASL library to
provide more. But for now I'm focusing on adding exactly one new
built-in mechanism, to replace MD5 in the long term.

In the protocol, there is a new AuthenticationSASL message, alongside
the existing AuthenticationMD5, AuthenticationSSPI etc. The
AuthenticationSASL message contains the name of the SASL mechanism used
("SCRAM-SHA-1"). Just like in the GSSAPI/SSPI authentication, a number
of PasswordMessage and AuthenticationSASLContinue messages are exchanged
after that, carrying the data specified by the SCRAM spec, until the
authentication succeeds (or not).

TODO:

* Per the SCRAM specification, the client sends the username in the
handshake. But in the FE/BE protocol, we've already sent it in the
startup packet. In the patch, libpq always sends an empty username in
the SCRAM exchange, and the username from the startup packet is what
matters. We could also require it to be the same, but in SCRAM the
username to be UTF-8 encoded, while in PostgreSQL the username can be in
any encoding. That is a source of annoyance in itself, as it's not
well-defined in PostgreSQL which encoding to use when sending a username
to the server. But I don't want to try fixing that in this patch, so it
seems easiest to just require the username to be empty.

* Need a source of randomness in client, to generate random nonces used
in the handshake. The SCRAM specification is not explicit about it, but
I believe it doesn't need to be unpredictable, as long as a different
nonce is used for each authentication.

* The client does not authenticate the server, even though the SCRAM
protocol allows that. The client does verify the proof the server sends,
but nothing stops a malicious server that's impersonating the real
server from not requesting SCRAM authentication in the first place. It
could just send AuthenticationOK without any authentication at all. To
take advantage of the server authentication, we'll need to add something
similar to the "sslmode=verify-ca" option in the client. In that mode,
the client should refuse the connection if the server doesn't request
SCRAM authentication (or some other future authentication mechanism that
authenticates the server to the client).

* Channel binding is not implemented. Not essential, but would be nice
to have. Together with the new client option mentioned in the previous
point, it would allow the client to know that there is no
man-in-the-middle, without having to verify the server's SSL certificate.

- Heikki

Attachments:

0001-Move-sha1.c-to-src-common.patchapplication/x-patch; name=0001-Move-sha1.c-to-src-common.patchDownload
From 115ab59aba9c7b427834907dea85ef026fd06c13 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 5 Mar 2015 20:37:32 +0200
Subject: [PATCH 1/4] Move sha1.c to src/common

---
 contrib/pgcrypto/Makefile   |   4 +-
 contrib/pgcrypto/internal.c |   2 +-
 contrib/pgcrypto/sha1.c     | 341 --------------------------------------------
 contrib/pgcrypto/sha1.h     |  75 ----------
 src/common/Makefile         |   2 +-
 src/common/sha1.c           | 341 ++++++++++++++++++++++++++++++++++++++++++++
 src/include/common/sha1.h   |  75 ++++++++++
 7 files changed, 420 insertions(+), 420 deletions(-)
 delete mode 100644 contrib/pgcrypto/sha1.c
 delete mode 100644 contrib/pgcrypto/sha1.h
 create mode 100644 src/common/sha1.c
 create mode 100644 src/include/common/sha1.h

diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 18bad1a..bb5118e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,6 +1,6 @@
 # contrib/pgcrypto/Makefile
 
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
+INT_SRCS = md5.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
 		fortuna.c random.c pgp-mpi-internal.c imath.c
 INT_TESTS = sha2
 
@@ -30,7 +30,7 @@ DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
 	pgcrypto--unpackaged--1.0.sql
 PGFILEDESC = "pgcrypto - cryptographic functions"
 
-REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
+REGRESS = init md5 hmac-md5 hmac-sha1 blowfish rijndael \
 	$(CF_TESTS) \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
 	pgp-armor pgp-decrypt pgp-encrypt $(CF_PGP_TESTS) \
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..9f42955 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -35,7 +35,7 @@
 
 #include "px.h"
 #include "md5.h"
-#include "sha1.h"
+#include "common/sha1.h"
 #include "blf.h"
 #include "rijndael.h"
 #include "fortuna.h"
diff --git a/contrib/pgcrypto/sha1.c b/contrib/pgcrypto/sha1.c
deleted file mode 100644
index 0e753ce..0000000
--- a/contrib/pgcrypto/sha1.c
+++ /dev/null
@@ -1,341 +0,0 @@
-/*	   $KAME: sha1.c,v 1.3 2000/02/22 14:01:18 itojun Exp $    */
-
-/*
- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *	  notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *	  notice, this list of conditions and the following disclaimer in the
- *	  documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the project nor the names of its contributors
- *	  may be used to endorse or promote products derived from this software
- *	  without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * contrib/pgcrypto/sha1.c
- */
-/*
- * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
- * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
- * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
- */
-
-#include "postgres.h"
-
-#include <sys/param.h>
-
-#include "sha1.h"
-
-/* constant table */
-static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
-
-#define K(t)	_K[(t) / 20]
-
-#define F0(b, c, d) (((b) & (c)) | ((~(b)) & (d)))
-#define F1(b, c, d) (((b) ^ (c)) ^ (d))
-#define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
-#define F3(b, c, d) (((b) ^ (c)) ^ (d))
-
-#define S(n, x)		(((x) << (n)) | ((x) >> (32 - (n))))
-
-#define H(n)	(ctxt->h.b32[(n)])
-#define COUNT	(ctxt->count)
-#define BCOUNT	(ctxt->c.b64[0] / 8)
-#define W(n)	(ctxt->m.b32[(n)])
-
-#define PUTBYTE(x) \
-do { \
-	ctxt->m.b8[(COUNT % 64)] = (x);		\
-	COUNT++;				\
-	COUNT %= 64;				\
-	ctxt->c.b64[0] += 8;			\
-	if (COUNT % 64 == 0)			\
-		sha1_step(ctxt);		\
-} while (0)
-
-#define PUTPAD(x) \
-do { \
-	ctxt->m.b8[(COUNT % 64)] = (x);		\
-	COUNT++;				\
-	COUNT %= 64;				\
-	if (COUNT % 64 == 0)			\
-		sha1_step(ctxt);		\
-} while (0)
-
-static void sha1_step(struct sha1_ctxt *);
-
-static void
-sha1_step(struct sha1_ctxt * ctxt)
-{
-	uint32		a,
-				b,
-				c,
-				d,
-				e;
-	size_t		t,
-				s;
-	uint32		tmp;
-
-#ifndef WORDS_BIGENDIAN
-	struct sha1_ctxt tctxt;
-
-	memmove(&tctxt.m.b8[0], &ctxt->m.b8[0], 64);
-	ctxt->m.b8[0] = tctxt.m.b8[3];
-	ctxt->m.b8[1] = tctxt.m.b8[2];
-	ctxt->m.b8[2] = tctxt.m.b8[1];
-	ctxt->m.b8[3] = tctxt.m.b8[0];
-	ctxt->m.b8[4] = tctxt.m.b8[7];
-	ctxt->m.b8[5] = tctxt.m.b8[6];
-	ctxt->m.b8[6] = tctxt.m.b8[5];
-	ctxt->m.b8[7] = tctxt.m.b8[4];
-	ctxt->m.b8[8] = tctxt.m.b8[11];
-	ctxt->m.b8[9] = tctxt.m.b8[10];
-	ctxt->m.b8[10] = tctxt.m.b8[9];
-	ctxt->m.b8[11] = tctxt.m.b8[8];
-	ctxt->m.b8[12] = tctxt.m.b8[15];
-	ctxt->m.b8[13] = tctxt.m.b8[14];
-	ctxt->m.b8[14] = tctxt.m.b8[13];
-	ctxt->m.b8[15] = tctxt.m.b8[12];
-	ctxt->m.b8[16] = tctxt.m.b8[19];
-	ctxt->m.b8[17] = tctxt.m.b8[18];
-	ctxt->m.b8[18] = tctxt.m.b8[17];
-	ctxt->m.b8[19] = tctxt.m.b8[16];
-	ctxt->m.b8[20] = tctxt.m.b8[23];
-	ctxt->m.b8[21] = tctxt.m.b8[22];
-	ctxt->m.b8[22] = tctxt.m.b8[21];
-	ctxt->m.b8[23] = tctxt.m.b8[20];
-	ctxt->m.b8[24] = tctxt.m.b8[27];
-	ctxt->m.b8[25] = tctxt.m.b8[26];
-	ctxt->m.b8[26] = tctxt.m.b8[25];
-	ctxt->m.b8[27] = tctxt.m.b8[24];
-	ctxt->m.b8[28] = tctxt.m.b8[31];
-	ctxt->m.b8[29] = tctxt.m.b8[30];
-	ctxt->m.b8[30] = tctxt.m.b8[29];
-	ctxt->m.b8[31] = tctxt.m.b8[28];
-	ctxt->m.b8[32] = tctxt.m.b8[35];
-	ctxt->m.b8[33] = tctxt.m.b8[34];
-	ctxt->m.b8[34] = tctxt.m.b8[33];
-	ctxt->m.b8[35] = tctxt.m.b8[32];
-	ctxt->m.b8[36] = tctxt.m.b8[39];
-	ctxt->m.b8[37] = tctxt.m.b8[38];
-	ctxt->m.b8[38] = tctxt.m.b8[37];
-	ctxt->m.b8[39] = tctxt.m.b8[36];
-	ctxt->m.b8[40] = tctxt.m.b8[43];
-	ctxt->m.b8[41] = tctxt.m.b8[42];
-	ctxt->m.b8[42] = tctxt.m.b8[41];
-	ctxt->m.b8[43] = tctxt.m.b8[40];
-	ctxt->m.b8[44] = tctxt.m.b8[47];
-	ctxt->m.b8[45] = tctxt.m.b8[46];
-	ctxt->m.b8[46] = tctxt.m.b8[45];
-	ctxt->m.b8[47] = tctxt.m.b8[44];
-	ctxt->m.b8[48] = tctxt.m.b8[51];
-	ctxt->m.b8[49] = tctxt.m.b8[50];
-	ctxt->m.b8[50] = tctxt.m.b8[49];
-	ctxt->m.b8[51] = tctxt.m.b8[48];
-	ctxt->m.b8[52] = tctxt.m.b8[55];
-	ctxt->m.b8[53] = tctxt.m.b8[54];
-	ctxt->m.b8[54] = tctxt.m.b8[53];
-	ctxt->m.b8[55] = tctxt.m.b8[52];
-	ctxt->m.b8[56] = tctxt.m.b8[59];
-	ctxt->m.b8[57] = tctxt.m.b8[58];
-	ctxt->m.b8[58] = tctxt.m.b8[57];
-	ctxt->m.b8[59] = tctxt.m.b8[56];
-	ctxt->m.b8[60] = tctxt.m.b8[63];
-	ctxt->m.b8[61] = tctxt.m.b8[62];
-	ctxt->m.b8[62] = tctxt.m.b8[61];
-	ctxt->m.b8[63] = tctxt.m.b8[60];
-#endif
-
-	a = H(0);
-	b = H(1);
-	c = H(2);
-	d = H(3);
-	e = H(4);
-
-	for (t = 0; t < 20; t++)
-	{
-		s = t & 0x0f;
-		if (t >= 16)
-			W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
-		tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t);
-		e = d;
-		d = c;
-		c = S(30, b);
-		b = a;
-		a = tmp;
-	}
-	for (t = 20; t < 40; t++)
-	{
-		s = t & 0x0f;
-		W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
-		tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t);
-		e = d;
-		d = c;
-		c = S(30, b);
-		b = a;
-		a = tmp;
-	}
-	for (t = 40; t < 60; t++)
-	{
-		s = t & 0x0f;
-		W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
-		tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t);
-		e = d;
-		d = c;
-		c = S(30, b);
-		b = a;
-		a = tmp;
-	}
-	for (t = 60; t < 80; t++)
-	{
-		s = t & 0x0f;
-		W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
-		tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t);
-		e = d;
-		d = c;
-		c = S(30, b);
-		b = a;
-		a = tmp;
-	}
-
-	H(0) = H(0) + a;
-	H(1) = H(1) + b;
-	H(2) = H(2) + c;
-	H(3) = H(3) + d;
-	H(4) = H(4) + e;
-
-	memset(&ctxt->m.b8[0], 0, 64);
-}
-
-/*------------------------------------------------------------*/
-
-void
-sha1_init(struct sha1_ctxt * ctxt)
-{
-	memset(ctxt, 0, sizeof(struct sha1_ctxt));
-	H(0) = 0x67452301;
-	H(1) = 0xefcdab89;
-	H(2) = 0x98badcfe;
-	H(3) = 0x10325476;
-	H(4) = 0xc3d2e1f0;
-}
-
-void
-sha1_pad(struct sha1_ctxt * ctxt)
-{
-	size_t		padlen;			/* pad length in bytes */
-	size_t		padstart;
-
-	PUTPAD(0x80);
-
-	padstart = COUNT % 64;
-	padlen = 64 - padstart;
-	if (padlen < 8)
-	{
-		memset(&ctxt->m.b8[padstart], 0, padlen);
-		COUNT += padlen;
-		COUNT %= 64;
-		sha1_step(ctxt);
-		padstart = COUNT % 64;	/* should be 0 */
-		padlen = 64 - padstart; /* should be 64 */
-	}
-	memset(&ctxt->m.b8[padstart], 0, padlen - 8);
-	COUNT += (padlen - 8);
-	COUNT %= 64;
-#ifdef WORDS_BIGENDIAN
-	PUTPAD(ctxt->c.b8[0]);
-	PUTPAD(ctxt->c.b8[1]);
-	PUTPAD(ctxt->c.b8[2]);
-	PUTPAD(ctxt->c.b8[3]);
-	PUTPAD(ctxt->c.b8[4]);
-	PUTPAD(ctxt->c.b8[5]);
-	PUTPAD(ctxt->c.b8[6]);
-	PUTPAD(ctxt->c.b8[7]);
-#else
-	PUTPAD(ctxt->c.b8[7]);
-	PUTPAD(ctxt->c.b8[6]);
-	PUTPAD(ctxt->c.b8[5]);
-	PUTPAD(ctxt->c.b8[4]);
-	PUTPAD(ctxt->c.b8[3]);
-	PUTPAD(ctxt->c.b8[2]);
-	PUTPAD(ctxt->c.b8[1]);
-	PUTPAD(ctxt->c.b8[0]);
-#endif
-}
-
-void
-sha1_loop(struct sha1_ctxt * ctxt, const uint8 *input0, size_t len)
-{
-	const uint8 *input;
-	size_t		gaplen;
-	size_t		gapstart;
-	size_t		off;
-	size_t		copysiz;
-
-	input = (const uint8 *) input0;
-	off = 0;
-
-	while (off < len)
-	{
-		gapstart = COUNT % 64;
-		gaplen = 64 - gapstart;
-
-		copysiz = (gaplen < len - off) ? gaplen : len - off;
-		memmove(&ctxt->m.b8[gapstart], &input[off], copysiz);
-		COUNT += copysiz;
-		COUNT %= 64;
-		ctxt->c.b64[0] += copysiz * 8;
-		if (COUNT % 64 == 0)
-			sha1_step(ctxt);
-		off += copysiz;
-	}
-}
-
-void
-sha1_result(struct sha1_ctxt * ctxt, uint8 *digest0)
-{
-	uint8	   *digest;
-
-	digest = (uint8 *) digest0;
-	sha1_pad(ctxt);
-#ifdef WORDS_BIGENDIAN
-	memmove(digest, &ctxt->h.b8[0], 20);
-#else
-	digest[0] = ctxt->h.b8[3];
-	digest[1] = ctxt->h.b8[2];
-	digest[2] = ctxt->h.b8[1];
-	digest[3] = ctxt->h.b8[0];
-	digest[4] = ctxt->h.b8[7];
-	digest[5] = ctxt->h.b8[6];
-	digest[6] = ctxt->h.b8[5];
-	digest[7] = ctxt->h.b8[4];
-	digest[8] = ctxt->h.b8[11];
-	digest[9] = ctxt->h.b8[10];
-	digest[10] = ctxt->h.b8[9];
-	digest[11] = ctxt->h.b8[8];
-	digest[12] = ctxt->h.b8[15];
-	digest[13] = ctxt->h.b8[14];
-	digest[14] = ctxt->h.b8[13];
-	digest[15] = ctxt->h.b8[12];
-	digest[16] = ctxt->h.b8[19];
-	digest[17] = ctxt->h.b8[18];
-	digest[18] = ctxt->h.b8[17];
-	digest[19] = ctxt->h.b8[16];
-#endif
-}
diff --git a/contrib/pgcrypto/sha1.h b/contrib/pgcrypto/sha1.h
deleted file mode 100644
index 5532ca1..0000000
--- a/contrib/pgcrypto/sha1.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*	contrib/pgcrypto/sha1.h */
-/*	   $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $    */
-
-/*
- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *	  notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *	  notice, this list of conditions and the following disclaimer in the
- *	  documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the project nor the names of its contributors
- *	  may be used to endorse or promote products derived from this software
- *	  without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-/*
- * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
- * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
- * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
- */
-
-#ifndef _NETINET6_SHA1_H_
-#define _NETINET6_SHA1_H_
-
-struct sha1_ctxt
-{
-	union
-	{
-		uint8		b8[20];
-		uint32		b32[5];
-	}			h;
-	union
-	{
-		uint8		b8[8];
-		uint64		b64[1];
-	}			c;
-	union
-	{
-		uint8		b8[64];
-		uint32		b32[16];
-	}			m;
-	uint8		count;
-};
-
-extern void sha1_init(struct sha1_ctxt *);
-extern void sha1_pad(struct sha1_ctxt *);
-extern void sha1_loop(struct sha1_ctxt *, const uint8 *, size_t);
-extern void sha1_result(struct sha1_ctxt *, uint8 *);
-
-/* compatibilty with other SHA1 source codes */
-typedef struct sha1_ctxt SHA1_CTX;
-
-#define SHA1Init(x)		sha1_init((x))
-#define SHA1Update(x, y, z) sha1_loop((x), (y), (z))
-#define SHA1Final(x, y)		sha1_result((y), (x))
-
-#define SHA1_RESULTLEN	(160/8)
-
-#endif   /* _NETINET6_SHA1_H_ */
diff --git a/src/common/Makefile b/src/common/Makefile
index c71415e..f526b42 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = exec.o pg_crc.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o string.o username.o wait_error.o
+	rmtree.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o
 
diff --git a/src/common/sha1.c b/src/common/sha1.c
new file mode 100644
index 0000000..4d9a325
--- /dev/null
+++ b/src/common/sha1.c
@@ -0,0 +1,341 @@
+/*	   $KAME: sha1.c,v 1.3 2000/02/22 14:01:18 itojun Exp $    */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *	  notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *	  notice, this list of conditions and the following disclaimer in the
+ *	  documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *	  may be used to endorse or promote products derived from this software
+ *	  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * src/common/sha1.c
+ */
+/*
+ * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
+ * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
+ * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
+ */
+
+#include "postgres.h"
+
+#include <sys/param.h>
+
+#include "common/sha1.h"
+
+/* constant table */
+static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
+
+#define K(t)	_K[(t) / 20]
+
+#define F0(b, c, d) (((b) & (c)) | ((~(b)) & (d)))
+#define F1(b, c, d) (((b) ^ (c)) ^ (d))
+#define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
+#define F3(b, c, d) (((b) ^ (c)) ^ (d))
+
+#define S(n, x)		(((x) << (n)) | ((x) >> (32 - (n))))
+
+#define H(n)	(ctxt->h.b32[(n)])
+#define COUNT	(ctxt->count)
+#define BCOUNT	(ctxt->c.b64[0] / 8)
+#define W(n)	(ctxt->m.b32[(n)])
+
+#define PUTBYTE(x) \
+do { \
+	ctxt->m.b8[(COUNT % 64)] = (x);		\
+	COUNT++;				\
+	COUNT %= 64;				\
+	ctxt->c.b64[0] += 8;			\
+	if (COUNT % 64 == 0)			\
+		sha1_step(ctxt);		\
+} while (0)
+
+#define PUTPAD(x) \
+do { \
+	ctxt->m.b8[(COUNT % 64)] = (x);		\
+	COUNT++;				\
+	COUNT %= 64;				\
+	if (COUNT % 64 == 0)			\
+		sha1_step(ctxt);		\
+} while (0)
+
+static void sha1_step(struct sha1_ctxt *);
+
+static void
+sha1_step(struct sha1_ctxt * ctxt)
+{
+	uint32		a,
+				b,
+				c,
+				d,
+				e;
+	size_t		t,
+				s;
+	uint32		tmp;
+
+#ifndef WORDS_BIGENDIAN
+	struct sha1_ctxt tctxt;
+
+	memmove(&tctxt.m.b8[0], &ctxt->m.b8[0], 64);
+	ctxt->m.b8[0] = tctxt.m.b8[3];
+	ctxt->m.b8[1] = tctxt.m.b8[2];
+	ctxt->m.b8[2] = tctxt.m.b8[1];
+	ctxt->m.b8[3] = tctxt.m.b8[0];
+	ctxt->m.b8[4] = tctxt.m.b8[7];
+	ctxt->m.b8[5] = tctxt.m.b8[6];
+	ctxt->m.b8[6] = tctxt.m.b8[5];
+	ctxt->m.b8[7] = tctxt.m.b8[4];
+	ctxt->m.b8[8] = tctxt.m.b8[11];
+	ctxt->m.b8[9] = tctxt.m.b8[10];
+	ctxt->m.b8[10] = tctxt.m.b8[9];
+	ctxt->m.b8[11] = tctxt.m.b8[8];
+	ctxt->m.b8[12] = tctxt.m.b8[15];
+	ctxt->m.b8[13] = tctxt.m.b8[14];
+	ctxt->m.b8[14] = tctxt.m.b8[13];
+	ctxt->m.b8[15] = tctxt.m.b8[12];
+	ctxt->m.b8[16] = tctxt.m.b8[19];
+	ctxt->m.b8[17] = tctxt.m.b8[18];
+	ctxt->m.b8[18] = tctxt.m.b8[17];
+	ctxt->m.b8[19] = tctxt.m.b8[16];
+	ctxt->m.b8[20] = tctxt.m.b8[23];
+	ctxt->m.b8[21] = tctxt.m.b8[22];
+	ctxt->m.b8[22] = tctxt.m.b8[21];
+	ctxt->m.b8[23] = tctxt.m.b8[20];
+	ctxt->m.b8[24] = tctxt.m.b8[27];
+	ctxt->m.b8[25] = tctxt.m.b8[26];
+	ctxt->m.b8[26] = tctxt.m.b8[25];
+	ctxt->m.b8[27] = tctxt.m.b8[24];
+	ctxt->m.b8[28] = tctxt.m.b8[31];
+	ctxt->m.b8[29] = tctxt.m.b8[30];
+	ctxt->m.b8[30] = tctxt.m.b8[29];
+	ctxt->m.b8[31] = tctxt.m.b8[28];
+	ctxt->m.b8[32] = tctxt.m.b8[35];
+	ctxt->m.b8[33] = tctxt.m.b8[34];
+	ctxt->m.b8[34] = tctxt.m.b8[33];
+	ctxt->m.b8[35] = tctxt.m.b8[32];
+	ctxt->m.b8[36] = tctxt.m.b8[39];
+	ctxt->m.b8[37] = tctxt.m.b8[38];
+	ctxt->m.b8[38] = tctxt.m.b8[37];
+	ctxt->m.b8[39] = tctxt.m.b8[36];
+	ctxt->m.b8[40] = tctxt.m.b8[43];
+	ctxt->m.b8[41] = tctxt.m.b8[42];
+	ctxt->m.b8[42] = tctxt.m.b8[41];
+	ctxt->m.b8[43] = tctxt.m.b8[40];
+	ctxt->m.b8[44] = tctxt.m.b8[47];
+	ctxt->m.b8[45] = tctxt.m.b8[46];
+	ctxt->m.b8[46] = tctxt.m.b8[45];
+	ctxt->m.b8[47] = tctxt.m.b8[44];
+	ctxt->m.b8[48] = tctxt.m.b8[51];
+	ctxt->m.b8[49] = tctxt.m.b8[50];
+	ctxt->m.b8[50] = tctxt.m.b8[49];
+	ctxt->m.b8[51] = tctxt.m.b8[48];
+	ctxt->m.b8[52] = tctxt.m.b8[55];
+	ctxt->m.b8[53] = tctxt.m.b8[54];
+	ctxt->m.b8[54] = tctxt.m.b8[53];
+	ctxt->m.b8[55] = tctxt.m.b8[52];
+	ctxt->m.b8[56] = tctxt.m.b8[59];
+	ctxt->m.b8[57] = tctxt.m.b8[58];
+	ctxt->m.b8[58] = tctxt.m.b8[57];
+	ctxt->m.b8[59] = tctxt.m.b8[56];
+	ctxt->m.b8[60] = tctxt.m.b8[63];
+	ctxt->m.b8[61] = tctxt.m.b8[62];
+	ctxt->m.b8[62] = tctxt.m.b8[61];
+	ctxt->m.b8[63] = tctxt.m.b8[60];
+#endif
+
+	a = H(0);
+	b = H(1);
+	c = H(2);
+	d = H(3);
+	e = H(4);
+
+	for (t = 0; t < 20; t++)
+	{
+		s = t & 0x0f;
+		if (t >= 16)
+			W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+		tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t);
+		e = d;
+		d = c;
+		c = S(30, b);
+		b = a;
+		a = tmp;
+	}
+	for (t = 20; t < 40; t++)
+	{
+		s = t & 0x0f;
+		W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+		tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t);
+		e = d;
+		d = c;
+		c = S(30, b);
+		b = a;
+		a = tmp;
+	}
+	for (t = 40; t < 60; t++)
+	{
+		s = t & 0x0f;
+		W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+		tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t);
+		e = d;
+		d = c;
+		c = S(30, b);
+		b = a;
+		a = tmp;
+	}
+	for (t = 60; t < 80; t++)
+	{
+		s = t & 0x0f;
+		W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+		tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t);
+		e = d;
+		d = c;
+		c = S(30, b);
+		b = a;
+		a = tmp;
+	}
+
+	H(0) = H(0) + a;
+	H(1) = H(1) + b;
+	H(2) = H(2) + c;
+	H(3) = H(3) + d;
+	H(4) = H(4) + e;
+
+	memset(&ctxt->m.b8[0], 0, 64);
+}
+
+/*------------------------------------------------------------*/
+
+void
+sha1_init(struct sha1_ctxt * ctxt)
+{
+	memset(ctxt, 0, sizeof(struct sha1_ctxt));
+	H(0) = 0x67452301;
+	H(1) = 0xefcdab89;
+	H(2) = 0x98badcfe;
+	H(3) = 0x10325476;
+	H(4) = 0xc3d2e1f0;
+}
+
+void
+sha1_pad(struct sha1_ctxt * ctxt)
+{
+	size_t		padlen;			/* pad length in bytes */
+	size_t		padstart;
+
+	PUTPAD(0x80);
+
+	padstart = COUNT % 64;
+	padlen = 64 - padstart;
+	if (padlen < 8)
+	{
+		memset(&ctxt->m.b8[padstart], 0, padlen);
+		COUNT += padlen;
+		COUNT %= 64;
+		sha1_step(ctxt);
+		padstart = COUNT % 64;	/* should be 0 */
+		padlen = 64 - padstart; /* should be 64 */
+	}
+	memset(&ctxt->m.b8[padstart], 0, padlen - 8);
+	COUNT += (padlen - 8);
+	COUNT %= 64;
+#ifdef WORDS_BIGENDIAN
+	PUTPAD(ctxt->c.b8[0]);
+	PUTPAD(ctxt->c.b8[1]);
+	PUTPAD(ctxt->c.b8[2]);
+	PUTPAD(ctxt->c.b8[3]);
+	PUTPAD(ctxt->c.b8[4]);
+	PUTPAD(ctxt->c.b8[5]);
+	PUTPAD(ctxt->c.b8[6]);
+	PUTPAD(ctxt->c.b8[7]);
+#else
+	PUTPAD(ctxt->c.b8[7]);
+	PUTPAD(ctxt->c.b8[6]);
+	PUTPAD(ctxt->c.b8[5]);
+	PUTPAD(ctxt->c.b8[4]);
+	PUTPAD(ctxt->c.b8[3]);
+	PUTPAD(ctxt->c.b8[2]);
+	PUTPAD(ctxt->c.b8[1]);
+	PUTPAD(ctxt->c.b8[0]);
+#endif
+}
+
+void
+sha1_loop(struct sha1_ctxt * ctxt, const uint8 *input0, size_t len)
+{
+	const uint8 *input;
+	size_t		gaplen;
+	size_t		gapstart;
+	size_t		off;
+	size_t		copysiz;
+
+	input = (const uint8 *) input0;
+	off = 0;
+
+	while (off < len)
+	{
+		gapstart = COUNT % 64;
+		gaplen = 64 - gapstart;
+
+		copysiz = (gaplen < len - off) ? gaplen : len - off;
+		memmove(&ctxt->m.b8[gapstart], &input[off], copysiz);
+		COUNT += copysiz;
+		COUNT %= 64;
+		ctxt->c.b64[0] += copysiz * 8;
+		if (COUNT % 64 == 0)
+			sha1_step(ctxt);
+		off += copysiz;
+	}
+}
+
+void
+sha1_result(struct sha1_ctxt * ctxt, uint8 *digest0)
+{
+	uint8	   *digest;
+
+	digest = (uint8 *) digest0;
+	sha1_pad(ctxt);
+#ifdef WORDS_BIGENDIAN
+	memmove(digest, &ctxt->h.b8[0], 20);
+#else
+	digest[0] = ctxt->h.b8[3];
+	digest[1] = ctxt->h.b8[2];
+	digest[2] = ctxt->h.b8[1];
+	digest[3] = ctxt->h.b8[0];
+	digest[4] = ctxt->h.b8[7];
+	digest[5] = ctxt->h.b8[6];
+	digest[6] = ctxt->h.b8[5];
+	digest[7] = ctxt->h.b8[4];
+	digest[8] = ctxt->h.b8[11];
+	digest[9] = ctxt->h.b8[10];
+	digest[10] = ctxt->h.b8[9];
+	digest[11] = ctxt->h.b8[8];
+	digest[12] = ctxt->h.b8[15];
+	digest[13] = ctxt->h.b8[14];
+	digest[14] = ctxt->h.b8[13];
+	digest[15] = ctxt->h.b8[12];
+	digest[16] = ctxt->h.b8[19];
+	digest[17] = ctxt->h.b8[18];
+	digest[18] = ctxt->h.b8[17];
+	digest[19] = ctxt->h.b8[16];
+#endif
+}
diff --git a/src/include/common/sha1.h b/src/include/common/sha1.h
new file mode 100644
index 0000000..d5ff296
--- /dev/null
+++ b/src/include/common/sha1.h
@@ -0,0 +1,75 @@
+/*	src/include/common/sha1.h */
+/*	   $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $    */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *	  notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *	  notice, this list of conditions and the following disclaimer in the
+ *	  documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *	  may be used to endorse or promote products derived from this software
+ *	  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
+ * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
+ * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
+ */
+
+#ifndef _NETINET6_SHA1_H_
+#define _NETINET6_SHA1_H_
+
+struct sha1_ctxt
+{
+	union
+	{
+		uint8		b8[20];
+		uint32		b32[5];
+	}			h;
+	union
+	{
+		uint8		b8[8];
+		uint64		b64[1];
+	}			c;
+	union
+	{
+		uint8		b8[64];
+		uint32		b32[16];
+	}			m;
+	uint8		count;
+};
+
+extern void sha1_init(struct sha1_ctxt *);
+extern void sha1_pad(struct sha1_ctxt *);
+extern void sha1_loop(struct sha1_ctxt *, const uint8 *, size_t);
+extern void sha1_result(struct sha1_ctxt *, uint8 *);
+
+/* compatibilty with other SHA1 source codes */
+typedef struct sha1_ctxt SHA1_CTX;
+
+#define SHA1Init(x)		sha1_init((x))
+#define SHA1Update(x, y, z) sha1_loop((x), (y), (z))
+#define SHA1Final(x, y)		sha1_result((y), (x))
+
+#define SHA1_RESULTLEN	(160/8)
+
+#endif   /* _NETINET6_SHA1_H_ */
-- 
2.1.4

0002-Refactor-sendAuthRequest.patchapplication/x-patch; name=0002-Refactor-sendAuthRequest.patchDownload
From b21202b8fb740f27eaf09acb36282727b923e6b3 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 11 Feb 2015 23:02:30 +0200
Subject: [PATCH 2/4] Refactor sendAuthRequest

---
 src/backend/libpq/auth.c | 65 ++++++++++++++++++++++++------------------------
 1 file changed, 32 insertions(+), 33 deletions(-)

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 28b050a..81226c2 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -36,7 +36,8 @@
  * Global authentication functions
  *----------------------------------------------------------------
  */
-static void sendAuthRequest(Port *port, AuthRequest areq);
+static void sendAuthRequest(Port *port, AuthRequest areq, char *extradata,
+				int extralen);
 static void auth_failed(Port *port, int status, char *logdetail);
 static char *recv_password_packet(Port *port);
 static int	recv_and_check_password_packet(Port *port, char **logdetail);
@@ -479,7 +480,7 @@ ClientAuthentication(Port *port)
 
 		case uaGSS:
 #ifdef ENABLE_GSS
-			sendAuthRequest(port, AUTH_REQ_GSS);
+			sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
 			status = pg_GSS_recvauth(port);
 #else
 			Assert(false);
@@ -488,7 +489,7 @@ ClientAuthentication(Port *port)
 
 		case uaSSPI:
 #ifdef ENABLE_SSPI
-			sendAuthRequest(port, AUTH_REQ_SSPI);
+			sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
 			status = pg_SSPI_recvauth(port);
 #else
 			Assert(false);
@@ -512,12 +513,13 @@ ClientAuthentication(Port *port)
 				ereport(FATAL,
 						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
 						 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
-			sendAuthRequest(port, AUTH_REQ_MD5);
+			/* Add the salt for encrypted passwords. */
+			sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
 		case uaPassword:
-			sendAuthRequest(port, AUTH_REQ_PASSWORD);
+			sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
@@ -556,7 +558,7 @@ ClientAuthentication(Port *port)
 		(*ClientAuthentication_hook) (port, status);
 
 	if (status == STATUS_OK)
-		sendAuthRequest(port, AUTH_REQ_OK);
+		sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
 	else
 		auth_failed(port, status, logdetail);
 }
@@ -566,7 +568,7 @@ ClientAuthentication(Port *port)
  * Send an authentication request packet to the frontend.
  */
 static void
-sendAuthRequest(Port *port, AuthRequest areq)
+sendAuthRequest(Port *port, AuthRequest areq, char *extradata, int extralen)
 {
 	StringInfoData buf;
 
@@ -575,27 +577,8 @@ sendAuthRequest(Port *port, AuthRequest areq)
 	pq_beginmessage(&buf, 'R');
 	pq_sendint(&buf, (int32) areq, sizeof(int32));
 
-	/* Add the salt for encrypted passwords. */
-	if (areq == AUTH_REQ_MD5)
-		pq_sendbytes(&buf, port->md5Salt, 4);
-
-#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
-
-	/*
-	 * Add the authentication data for the next step of the GSSAPI or SSPI
-	 * negotiation.
-	 */
-	else if (areq == AUTH_REQ_GSS_CONT)
-	{
-		if (port->gss->outbuf.length > 0)
-		{
-			elog(DEBUG4, "sending GSS token of length %u",
-				 (unsigned int) port->gss->outbuf.length);
-
-			pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
-		}
-	}
-#endif
+	if (extralen > 0)
+		pq_sendbytes(&buf, extradata, extralen);
 
 	pq_endmessage(&buf);
 
@@ -907,7 +890,15 @@ pg_GSS_recvauth(Port *port)
 			elog(DEBUG4, "sending GSS response token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			gss_release_buffer(&lmin_s, &port->gss->outbuf);
 		}
@@ -1150,7 +1141,15 @@ pg_SSPI_recvauth(Port *port)
 			port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
 			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
 		}
@@ -1671,7 +1670,7 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
 					 * let's go ask the client to send a password, which we
 					 * then stuff into PAM.
 					 */
-					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
+					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
 					passwd = recv_password_packet(pam_port_cludge);
 					if (passwd == NULL)
 					{
@@ -1946,7 +1945,7 @@ CheckLDAPAuth(Port *port)
 	if (port->hba->ldapport == 0)
 		port->hba->ldapport = LDAP_PORT;
 
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
@@ -2302,7 +2301,7 @@ CheckRADIUSAuth(Port *port)
 		identifier = port->hba->radiusidentifier;
 
 	/* Send regular password request to client, and get the response */
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
-- 
2.1.4

0003-Add-support-for-multiple-verifiers.patchapplication/x-patch; name=0003-Add-support-for-multiple-verifiers.patchDownload
From 488b1d547cbf132bd3980971e91bbf8b8c6e442c Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 3 Mar 2015 20:10:10 +0200
Subject: [PATCH 3/4] Add support for multiple verifiers.

pg_authid.rolpassword field has been replaced with an array.

Add CREATE/ALTER ROLE support for password verifiers.
---
 src/backend/catalog/system_views.sql |   2 +-
 src/backend/commands/user.c          | 195 +++++++++++++++++++++++++----------
 src/backend/libpq/crypt.c            | 117 ++++++++++++++++++---
 src/backend/parser/gram.y            |   8 +-
 src/bin/pg_dump/pg_dumpall.c         |  28 ++++-
 src/include/catalog/pg_authid.h      |   4 +-
 src/include/parser/kwlist.h          |   1 +
 src/test/regress/expected/rules.out  |   2 +-
 8 files changed, 281 insertions(+), 76 deletions(-)

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2800f73..cd78061 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -32,7 +32,7 @@ CREATE VIEW pg_shadow AS
         rolsuper AS usesuper,
         rolreplication AS userepl,
         rolbypassrls AS usebypassrls,
-        rolpassword AS passwd,
+        rolverifiers AS verifiers,
         rolvaliduntil::abstime AS valuntil,
         setconfig AS useconfig
     FROM pg_authid LEFT JOIN pg_db_role_setting s
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 75f1b3c..42e473d 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "commands/seclabel.h"
@@ -55,6 +56,9 @@ static void DelRoleMems(const char *rolename, Oid roleid,
 			List *memberSpecs, List *memberIds,
 			bool admin_opt);
 
+static Datum buildPasswordVerifiers(char *rolname, char *password,
+									bool store_encrypted,
+					   char *verifiers, bool *isnull);
 
 /* Check if current user has createrole privileges */
 static bool
@@ -80,7 +84,7 @@ CreateRole(CreateRoleStmt *stmt)
 	ListCell   *option;
 	char	   *password = NULL;	/* user password */
 	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	char	   *passwordVerifiers = NULL;
 	bool		issuper = false;	/* Make the user a superuser? */
 	bool		inherit = true; /* Auto inherit privileges? */
 	bool		createrole = false;		/* Can this user create roles? */
@@ -96,6 +100,7 @@ CreateRole(CreateRoleStmt *stmt)
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
 	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordverifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -141,6 +146,14 @@ CreateRole(CreateRoleStmt *stmt)
 			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
 				encrypt_password = false;
 		}
+		else if (strcmp(defel->defname, "passwordVerifiers") == 0)
+		{
+			if (dpassword || dpasswordverifiers)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dpasswordverifiers = defel;
+		}
 		else if (strcmp(defel->defname, "sysid") == 0)
 		{
 			ereport(NOTICE,
@@ -249,6 +262,8 @@ CreateRole(CreateRoleStmt *stmt)
 
 	if (dpassword && dpassword->arg)
 		password = strVal(dpassword->arg);
+	if (dpasswordverifiers && dpasswordverifiers->arg)
+		passwordVerifiers = strVal(dpasswordverifiers->arg);
 	if (dissuper)
 		issuper = intVal(dissuper->arg) != 0;
 	if (dinherit)
@@ -346,7 +361,10 @@ CreateRole(CreateRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Call the password checking hook if there is one defined. Pass the
+	 * plaintext password to it if we have it, otherwise just the MD5 hash.
+	 *
+	 * XXX: what to do with other verifiers? Need to change the hook API...
 	 */
 	if (check_password_hook && password)
 		(*check_password_hook) (stmt->role,
@@ -371,24 +389,10 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
 	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-	}
-	else
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
+	new_record[Anum_pg_authid_rolverifiers - 1] =
+		buildPasswordVerifiers(stmt->role, password, encrypt_password,
+							   passwordVerifiers,
+							   &new_record_nulls[Anum_pg_authid_rolverifiers - 1]);
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
@@ -487,7 +491,7 @@ AlterRole(AlterRoleStmt *stmt)
 	char	   *rolename = NULL;
 	char	   *password = NULL;	/* user password */
 	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	char	   *passwordVerifiers = NULL;
 	int			issuper = -1;	/* Make the user a superuser? */
 	int			inherit = -1;	/* Auto inherit privileges? */
 	int			createrole = -1;	/* Can this user create roles? */
@@ -501,6 +505,7 @@ AlterRole(AlterRoleStmt *stmt)
 	bool		validUntil_null;
 	bool		bypassrls = -1;
 	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordverifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -519,10 +524,10 @@ AlterRole(AlterRoleStmt *stmt)
 		DefElem    *defel = (DefElem *) lfirst(option);
 
 		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+				 strcmp(defel->defname, "encryptedPassword") == 0 ||
+				 strcmp(defel->defname, "unencryptedPassword") == 0)
 		{
-			if (dpassword)
+			if (dpassword || dpasswordverifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
@@ -532,6 +537,14 @@ AlterRole(AlterRoleStmt *stmt)
 			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
 				encrypt_password = false;
 		}
+		else if (strcmp(defel->defname, "passwordVerifiers") == 0)
+		{
+			if (dpassword || dpasswordverifiers)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dpasswordverifiers = defel;
+		}
 		else if (strcmp(defel->defname, "superuser") == 0)
 		{
 			if (dissuper)
@@ -620,6 +633,8 @@ AlterRole(AlterRoleStmt *stmt)
 
 	if (dpassword && dpassword->arg)
 		password = strVal(dpassword->arg);
+	if (dpasswordverifiers && dpasswordverifiers->arg)
+		passwordVerifiers = strVal(dpasswordverifiers->arg);
 	if (dissuper)
 		issuper = intVal(dissuper->arg);
 	if (dinherit)
@@ -718,7 +733,10 @@ AlterRole(AlterRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Call the password checking hook if there is one defined. Pass the
+	 * plaintext password to it if we have it, otherwise just the MD5 hash.
+	 *
+	 * XXX: what to do with other verifiers? Need to change the hook API...
 	 */
 	if (check_password_hook && password)
 		(*check_password_hook)(rolename ,
@@ -779,28 +797,14 @@ AlterRole(AlterRoleStmt *stmt)
 		new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
 	}
 
-	/* password */
-	if (password)
+	/* set or unset password */
+	if (dpassword)
 	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, rolename, strlen(rolename),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
-	/* unset password */
-	if (dpassword && dpassword->arg == NULL)
-	{
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+		new_record_repl[Anum_pg_authid_rolverifiers - 1] = true;
+		new_record[Anum_pg_authid_rolverifiers - 1] =
+			buildPasswordVerifiers(rolename, password, encrypt_password,
+								   passwordVerifiers,
+								   &new_record_nulls[Anum_pg_authid_rolverifiers - 1]);
 	}
 
 	/* valid until */
@@ -1192,16 +1196,43 @@ RenameRole(const char *oldname, const char *newname)
 												   CStringGetDatum(newname));
 	repl_null[Anum_pg_authid_rolname - 1] = false;
 
-	datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
+	datum = heap_getattr(oldtuple, Anum_pg_authid_rolverifiers, dsc, &isnull);
 
-	if (!isnull && isMD5(TextDatumGetCString(datum)))
+	if (!isnull)
 	{
-		/* MD5 uses the username as salt, so just clear it on a rename */
-		repl_repl[Anum_pg_authid_rolpassword - 1] = true;
-		repl_null[Anum_pg_authid_rolpassword - 1] = true;
+		Datum	   *verifiers;
+		int			nverifiers;
+		bool		modified = false;
 
-		ereport(NOTICE,
-				(errmsg("MD5 password cleared because of role rename")));
+		deconstruct_array(DatumGetArrayTypeP(datum), TEXTOID, -1, false, 'i',
+						  &verifiers, NULL, &nverifiers);
+		for (i = 0; i < nverifiers;)
+		{
+			char *verifier = TextDatumGetCString(verifiers[i]);
+			if (strncmp(verifier, "md5:", 4) == 0)
+			{
+				/* MD5 uses the username as salt, so clear it on a rename */
+				nverifiers--;
+				memmove(&verifiers[i], &verifiers[i + 1], nverifiers - i);
+				modified = true;
+				ereport(NOTICE,
+						(errmsg("MD5 password cleared because of role rename")));
+			}
+			else
+				i++;
+		}
+		if (modified)
+		{
+			repl_repl[Anum_pg_authid_rolverifiers - 1] = true;
+			if (nverifiers == 0)
+				repl_null[Anum_pg_authid_rolverifiers - 1] = true;
+			else
+			{
+				repl_val[Anum_pg_authid_rolverifiers - 1] =
+					PointerGetDatum(construct_array(verifiers, nverifiers, TEXTOID, -1, false, 'i'));
+				repl_null[Anum_pg_authid_rolverifiers - 1] = false;
+			}
+		}
 	}
 
 	newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
@@ -1624,3 +1655,61 @@ DelRoleMems(const char *rolename, Oid roleid,
 	 */
 	heap_close(pg_authmem_rel, NoLock);
 }
+
+static Datum
+buildPasswordVerifiers(char *rolname, char *password, bool store_encrypted,
+					   char *verifiers, bool *isnull)
+{
+	Datum		datums[2];
+	int			nverifiers = 0;
+	Datum		result;
+	char	   *verifier = NULL;
+
+	/* If a verifier is given, use it as is */
+	if (verifiers)
+	{
+		result = OidInputFunctionCall(F_ARRAY_IN, verifiers, TEXTOID, -1);
+		*isnull = false;
+		return result;
+	}
+
+	/* If ENCRYPTED or UNENCRYPTED PASSWORD is given, store it */
+	if (password)
+	{
+		/*
+		 * Check if the password is actually already an MD5 hash.
+		 */
+		if (isMD5(password))
+		{
+			verifier = psprintf("md5:%s", password);
+			datums[nverifiers++] = CStringGetTextDatum(verifier);
+		}
+		else if (!store_encrypted)
+		{
+			verifier = psprintf("password:%s", password);
+			datums[nverifiers++] = CStringGetTextDatum(verifier);
+		}
+		else
+		{
+			char		encrypted_password[MD5_PASSWD_LEN + 1];
+
+			if (!pg_md5_encrypt(password, rolname, strlen(rolname),
+								encrypted_password))
+				elog(ERROR, "password encryption failed");
+			verifier = psprintf("md5:%s", encrypted_password);
+			datums[nverifiers++] = CStringGetTextDatum(verifier);
+		}
+	}
+
+	if (nverifiers == 0)
+	{
+		result = (Datum) 0;
+		*isnull = true;
+	}
+	else
+	{
+		result = PointerGetDatum(construct_array(datums, nverifiers, TEXTOID, -1, false, 'i'));
+		*isnull = false;
+	}
+	return result;
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 97be944..4c94d63 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -21,13 +21,93 @@
 #endif
 
 #include "catalog/pg_authid.h"
+#include "catalog/pg_type.h"
 #include "libpq/crypt.h"
 #include "libpq/md5.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+/*
+ * Get verifier stored in pg_roleid tuple, for given authentication method.
+ */
+static char *
+get_role_verifier_from_tup(HeapTuple roleTup, const char *method)
+{
+	Datum		datum;
+	bool		isnull;
+	Datum	   *elems;
+	int			nelems;
+	char	   *verifier = NULL;
+	int			i;
+
+	datum = SysCacheGetAttr(AUTHNAME, roleTup,
+							Anum_pg_authid_rolverifiers, &isnull);
+	if (isnull)
+		return NULL;
+
+	/* Parse the stored value */
+	deconstruct_array(DatumGetArrayTypeP(datum), TEXTOID, -1, false, 'i',
+					  &elems, NULL, &nelems);
+	for (i = 0; i < nelems; i ++)
+	{
+		char		*s = TextDatumGetCString(elems[i]);
+		char	    *colon;
+
+		colon = strchr(s, ':');
+		if (colon == NULL)
+			continue; /* shouldn't happen, invalid verifier */
+
+		*colon = '\0';
+		verifier = colon + 1;
+
+		if (strcmp(s, method) == 0)
+		{
+			verifier = pstrdup(colon + 1);
+			pfree(s);
+			break;
+		}
+		pfree(s);
+	}
+
+	return verifier;
+}
+
+char *
+get_role_verifier(const char *role, const char *method, char **logdetail)
+{
+	char	   *verifier;
+	HeapTuple	roleTup;
+	Datum		datum;
+	bool		isnull;
+
+	/* Get role info from pg_authid */
+	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
+	if (!HeapTupleIsValid(roleTup))
+		return NULL;	/* no such user */
+
+	datum = SysCacheGetAttr(AUTHNAME, roleTup,
+							Anum_pg_authid_rolvaliduntil, &isnull);
+	if (!isnull)
+	{
+		TimestampTz vuntil = DatumGetTimestampTz(datum);
+		if (vuntil < GetCurrentTimestamp())
+		{
+			*logdetail = psprintf(_("User \"%s\" has an expired password."),
+								  role);
+			ReleaseSysCache(roleTup);
+			return NULL;
+		}
+	}
+
+	verifier = get_role_verifier_from_tup(roleTup, method);
+
+	ReleaseSysCache(roleTup);
+
+	return verifier;
+}
 
 /*
  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,9 +119,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				 char **logdetail)
 {
 	int			retval = STATUS_ERROR;
-	char	   *shadow_pass,
+	char	   *verifier,
 			   *crypt_pwd;
 	TimestampTz vuntil = 0;
+	bool		verifier_is_md5;
 	char	   *crypt_client_pass = client_pass;
 	HeapTuple	roleTup;
 	Datum		datum;
@@ -52,16 +133,22 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 	if (!HeapTupleIsValid(roleTup))
 		return STATUS_ERROR;	/* no such user */
 
-	datum = SysCacheGetAttr(AUTHNAME, roleTup,
-							Anum_pg_authid_rolpassword, &isnull);
-	if (isnull)
+	verifier_is_md5 = true;
+	verifier = get_role_verifier_from_tup(roleTup, "md5");
+	if (verifier == NULL)
 	{
-		ReleaseSysCache(roleTup);
-		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
-							  role);
-		return STATUS_ERROR;	/* user has no password */
+		/* we can also use a plaintext password, by creating the hash from it */
+		verifier_is_md5 = false;
+		verifier = get_role_verifier_from_tup(roleTup, "password");
+
+		if (verifier == NULL)
+		{
+			*logdetail = psprintf(_("User \"%s\" has no password assigned for authentication method \"%s\"."),
+								  role, "md5");
+			ReleaseSysCache(roleTup);
+			return STATUS_ERROR;
+		}
 	}
-	shadow_pass = TextDatumGetCString(datum);
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
 							Anum_pg_authid_rolvaliduntil, &isnull);
@@ -70,7 +157,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 
 	ReleaseSysCache(roleTup);
 
-	if (*shadow_pass == '\0')
+	if (*verifier == '\0')
 		return STATUS_ERROR;	/* empty password */
 
 	CHECK_FOR_INTERRUPTS();
@@ -83,10 +170,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 	{
 		case uaMD5:
 			crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* stored password already encrypted, only do salt */
-				if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+				if (!pg_md5_encrypt(verifier + strlen("md5"),
 									port->md5Salt,
 									sizeof(port->md5Salt), crypt_pwd))
 				{
@@ -99,7 +186,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				/* stored password is plain, double-encrypt */
 				char	   *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
 
-				if (!pg_md5_encrypt(shadow_pass,
+				if (!pg_md5_encrypt(verifier,
 									port->user_name,
 									strlen(port->user_name),
 									crypt_pwd2))
@@ -121,7 +208,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 			}
 			break;
 		default:
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* Encrypt user-supplied password to match stored MD5 */
 				crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
@@ -134,7 +221,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 					return STATUS_ERROR;
 				}
 			}
-			crypt_pwd = shadow_pass;
+			crypt_pwd = verifier;
 			break;
 	}
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3aa9e42..d68e61d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -623,7 +623,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERIFIERS VERSION_P VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -916,6 +916,11 @@ AlterOptRoleElem:
 					$$ = makeDefElem("unencryptedPassword",
 									 (Node *)makeString($3));
 				}
+			| PASSWORD VERIFIERS Sconst
+				{
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)makeString($3));
+				}
 			| INHERIT
 				{
 					$$ = makeDefElem("inherit", (Node *)makeInteger(TRUE));
@@ -13504,6 +13509,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARYING
+			| VERIFIERS
 			| VERSION_P
 			| VIEW
 			| VIEWS
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 6a7a641..32d710c 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -660,7 +660,8 @@ dumpRoles(PGconn *conn)
 				i_rolcreatedb,
 				i_rolcanlogin,
 				i_rolconnlimit,
-				i_rolpassword,
+				i_rolverifiers = -1,
+				i_rolpassword = -1,
 				i_rolvaliduntil,
 				i_rolreplication,
 				i_rolbypassrls,
@@ -669,10 +670,21 @@ dumpRoles(PGconn *conn)
 	int			i;
 
 	/* note: rolconfig is dumped later */
+	/* FIXME: bump this when we branch off */
 	if (server_version >= 90500)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
 						  "rolcreaterole, rolcreatedb, "
+						  "rolcanlogin, rolconnlimit, rolverifiers, "
+						  "rolvaliduntil, rolreplication, rolbypassrls, "
+			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+						  "rolname = current_user AS is_current_user "
+						  "FROM pg_authid "
+						  "ORDER BY 2");
+	else if (server_version >= 90500)
+		printfPQExpBuffer(buf,
+						  "SELECT oid, rolname, rolsuper, rolinherit, "
+						  "rolcreaterole, rolcreatedb, "
 						  "rolcanlogin, rolconnlimit, rolpassword, "
 						  "rolvaliduntil, rolreplication, rolbypassrls, "
 			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
@@ -757,7 +769,11 @@ dumpRoles(PGconn *conn)
 	i_rolcreatedb = PQfnumber(res, "rolcreatedb");
 	i_rolcanlogin = PQfnumber(res, "rolcanlogin");
 	i_rolconnlimit = PQfnumber(res, "rolconnlimit");
-	i_rolpassword = PQfnumber(res, "rolpassword");
+	/* FIXME: version again */
+	if (server_version >= 90500)
+		i_rolverifiers = PQfnumber(res, "rolverifiers");
+	else
+		i_rolpassword = PQfnumber(res, "rolpassword");
 	i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
 	i_rolreplication = PQfnumber(res, "rolreplication");
 	i_rolbypassrls = PQfnumber(res, "rolbypassrls");
@@ -837,12 +853,18 @@ dumpRoles(PGconn *conn)
 			appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
 							  PQgetvalue(res, i, i_rolconnlimit));
 
-		if (!PQgetisnull(res, i, i_rolpassword))
+		if (i_rolpassword != -1 && !PQgetisnull(res, i, i_rolpassword))
 		{
 			appendPQExpBufferStr(buf, " PASSWORD ");
 			appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolpassword), conn);
 		}
 
+		if (i_rolverifiers != -1 && !PQgetisnull(res, i, i_rolverifiers))
+		{
+			appendPQExpBufferStr(buf, " PASSWORD VERIFIERS ");
+			appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolverifiers), conn);
+		}
+
 		if (!PQgetisnull(res, i, i_rolvaliduntil))
 			appendPQExpBuffer(buf, " VALID UNTIL '%s'",
 							  PQgetvalue(res, i, i_rolvaliduntil));
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index d5f19d6..498c18b 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
 
 	/* remaining fields may be null; use heap_getattr to read them! */
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
-	text		rolpassword;	/* password, if any */
+	text		rolverifiers[];	/* password hashes, if any */
 	timestamptz rolvaliduntil;	/* password expiration time, if any */
 #endif
 } FormData_pg_authid;
@@ -85,7 +85,7 @@ typedef FormData_pg_authid *Form_pg_authid;
 #define Anum_pg_authid_rolreplication	7
 #define Anum_pg_authid_rolbypassrls		8
 #define Anum_pg_authid_rolconnlimit		9
-#define Anum_pg_authid_rolpassword		10
+#define Anum_pg_authid_rolverifiers		10
 #define Anum_pg_authid_rolvaliduntil	11
 
 /* ----------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 7c243ec..067112f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -407,6 +407,7 @@ PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("verifiers", VERIFIERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 1788270..052c4b7 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1608,7 +1608,7 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.rolsuper AS usesuper,
     pg_authid.rolreplication AS userepl,
     pg_authid.rolbypassrls AS usebypassrls,
-    pg_authid.rolpassword AS passwd,
+    pg_authid.rolverifiers AS verifiers,
     (pg_authid.rolvaliduntil)::abstime AS valuntil,
     s.setconfig AS useconfig
    FROM (pg_authid
-- 
2.1.4

0004-WIP-Implement-SCRAM-authentication.patchapplication/x-patch; name=0004-WIP-Implement-SCRAM-authentication.patchDownload
From 3b38ec5fd7a2d4fe5f3f89c2f7cb6ea20b9fa0e5 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 5 Mar 2015 09:50:52 +0200
Subject: [PATCH 4/4] WIP: Implement SCRAM authentication.

---
 doc/src/sgml/protocol.sgml           | 148 ++++++++-
 src/backend/commands/user.c          |   6 +
 src/backend/libpq/Makefile           |   2 +-
 src/backend/libpq/auth-scram.c       | 621 +++++++++++++++++++++++++++++++++++
 src/backend/libpq/auth.c             | 110 ++++++-
 src/backend/libpq/hba.c              |  14 +
 src/backend/libpq/pg_hba.conf.sample |   2 +-
 src/backend/utils/adt/encode.c       |   8 +-
 src/common/Makefile                  |   2 +-
 src/common/scram-common.c            | 161 +++++++++
 src/include/common/scram-common.h    |  35 ++
 src/include/libpq/auth.h             |   5 +
 src/include/libpq/crypt.h            |   1 +
 src/include/libpq/hba.h              |   1 +
 src/include/libpq/pqcomm.h           |   2 +
 src/include/libpq/scram.h            |  23 ++
 src/include/utils/builtins.h         |   4 +
 src/interfaces/libpq/Makefile        |   8 +-
 src/interfaces/libpq/fe-auth-scram.c | 476 +++++++++++++++++++++++++++
 src/interfaces/libpq/fe-auth.c       |  95 ++++++
 src/interfaces/libpq/fe-auth.h       |   8 +
 src/interfaces/libpq/fe-connect.c    |  49 +++
 src/interfaces/libpq/libpq-int.h     |   5 +
 23 files changed, 1769 insertions(+), 17 deletions(-)
 create mode 100644 src/backend/libpq/auth-scram.c
 create mode 100644 src/common/scram-common.c
 create mode 100644 src/include/common/scram-common.h
 create mode 100644 src/include/libpq/scram.h
 create mode 100644 src/interfaces/libpq/fe-auth-scram.c

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 3a753a0..aec0e05 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -228,11 +228,11 @@
     The server then sends an appropriate authentication request message,
     to which the frontend must reply with an appropriate authentication
     response message (such as a password).
-    For all authentication methods except GSSAPI and SSPI, there is at most
-    one request and one response. In some methods, no response
+    For all authentication methods except GSSAPI, SSPI and SASL, there is at
+    most one request and one response. In some methods, no response
     at all is needed from the frontend, and so no authentication request
-    occurs. For GSSAPI and SSPI, multiple exchanges of packets may be needed
-    to complete the authentication.
+    occurs. For GSSAPI, SSPI and SASL, multiple exchanges of packets may be
+    needed to complete the authentication.
    </para>
 
    <para>
@@ -366,6 +366,35 @@
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <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.
+       </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.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
 
@@ -2559,6 +2588,115 @@ AuthenticationGSSContinue (B)
 </listitem>
 </varlistentry>
 
+<varlistentry>
+<term>
+AuthenticationSASL (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(10)
+</term>
+<listitem>
+<para>
+                Specifies that SASL authentication is started.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of a SASL authentication mechanism.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+AuthenticationSASLContinue (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(11)
+</term>
+<listitem>
+<para>
+                Specifies that this message contains SASL-mechanism specific
+                data.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+                SASL data, specific to the SASL mechanism being used.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
 
 <varlistentry>
 <term>
@@ -4321,7 +4459,7 @@ PasswordMessage (F)
 <listitem>
 <para>
                 Identifies the message as a password response. Note that
-                this is also used for GSSAPI and SSPI response messages
+                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).
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 42e473d..4347399 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -30,6 +30,7 @@
 #include "commands/seclabel.h"
 #include "commands/user.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
@@ -1698,6 +1699,11 @@ buildPasswordVerifiers(char *rolname, char *password, bool store_encrypted,
 				elog(ERROR, "password encryption failed");
 			verifier = psprintf("md5:%s", encrypted_password);
 			datums[nverifiers++] = CStringGetTextDatum(verifier);
+
+			/* Also create a SCRAM verifier */
+			verifier = scram_build_verifier(rolname, password, 0);
+			verifier = psprintf("scram:%s", verifier);
+			datums[nverifiers++] = CStringGetTextDatum(verifier);
 		}
 	}
 
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..3dd60e1 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
 # be-fsstubs is here for historical reasons, probably belongs elsewhere
 
 OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \
-       pqformat.o pqmq.o pqsignal.o
+       pqformat.o pqmq.o pqsignal.o auth-scram.o
 
 ifeq ($(with_openssl),yes)
 OBJS += be-secure-openssl.o
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
new file mode 100644
index 0000000..060193b
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,621 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ *	  Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See RFC 5802. Some differences:
+ *
+ * - Username from the authentication exchange is not used. The client
+ *   should send an empty string as the username.
+ *
+ * - Password is not processed with the SASLprep algorithm.
+ *
+ * - Channel binding is not supported.
+ *
+ * The verifier stored in pg_authid consists of the salt, iteration count,
+ * StoredKey, and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_authid.h"
+#include "common/scram-common.h"
+#include "common/sha1.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+#define SALT_LEN		10		/* length of salt when generating new
+								 * verifiers */
+#define NONCE_LEN		10		/* length of random nonce generated in the
+								 * authentication exchange */
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		SALT_SENT,
+		FINISHED
+	} state;
+
+	const char *username;	/* username from startup packet */
+	char	   *salt;		/* base64-encoded */
+	int			iterations;
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+
+	/* These come from the client-first message */
+	char	   *client_first_message_bare;
+	char	   *client_username;	/* username from client-first message */
+	char	   *client_authzid;
+	char	   *client_nonce;
+
+	/* These come from the client-final message */
+	char	   *client_final_message_without_proof;
+	char	   *client_final_nonce;
+	char		ClientProof[SCRAM_KEY_LEN];
+
+	char	   *server_first_message;
+	char	   *server_nonce;		/* base64-encoded */
+	char	   *server_signature;
+
+} scram_state;
+
+static void read_client_first_message(scram_state *state, char *input);
+static void read_client_final_message(scram_state *state, char *input);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+
+static void generate_nonce(char *out, int len);
+
+/*
+ * Initialize a new SCRAM authentication exchange, with given username and
+ * its stored verifier.
+ */
+void *
+scram_init(const char *username, const char *verifier)
+{
+	scram_state *state;
+	char	   *v;
+	char	   *p;
+
+	state = (scram_state *) palloc0(sizeof(scram_state));
+	state->state = INIT;
+	state->username = username;
+
+	/*
+	 * The verifier is of form:
+	 *
+	 * salt:iterations:storedkey:serverkey
+	 */
+	v = pstrdup(verifier);
+
+	/* salt */
+	if ((p = strtok(v, ":")) == NULL)
+		goto invalid_verifier;
+	state->salt = pstrdup(p);
+
+	/* iterations */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	errno = 0;
+	state->iterations = strtol(p, &p, 10);
+	if (*p || errno != 0)
+		goto invalid_verifier;
+
+	/* storedkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+	hex_decode(p, SCRAM_KEY_LEN*2, (char *) state->StoredKey);
+
+	/* serverkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+	hex_decode(p, SCRAM_KEY_LEN*2, (char *) state->ServerKey);
+
+	pfree(v);
+
+	return state;
+
+invalid_verifier:
+	/* FIXME: should we keep this secret from the unauthenticated user? */
+	elog(ERROR, "invalid SCRAM verifier: %s", verifier);
+	return NULL;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ */
+int
+scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen)
+{
+	scram_state *state = (scram_state *) opaq;
+	int			result;
+
+	*output = NULL;
+	*outputlen = 0;
+
+	if (inputlen > 0)
+		elog(LOG, "got SCRAM message: %s", input);
+
+	switch (state->state)
+	{
+		case INIT:
+			/* receive username and client nonce, send challenge */
+			read_client_first_message(state, input);
+			*output = build_server_first_message(state);
+			*outputlen = strlen(*output);
+			result = SASL_EXCHANGE_CONTINUE;
+			state->state = SALT_SENT;
+			break;
+
+		case SALT_SENT:
+			/* receive response to challenge and verify it */
+			read_client_final_message(state, input);
+			if (verify_final_nonce(state) && verify_client_proof(state))
+			{
+				*output = build_server_final_message(state);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_SUCCESS;
+			}
+			else
+			{
+				result = SASL_EXCHANGE_FAILURE;
+			}
+			state->state = FINISHED;
+			break;
+
+		default:
+			elog(ERROR, "invalid SCRAM exchange state");
+			result = 0;
+	}
+
+	return result;
+}
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolverifiers.
+ *
+ * If salt is NULL, a random salt is generated. If iterations is 0, default
+ * number of iterations is used;
+ */
+char *
+scram_build_verifier(char *username, char *password, int iterations)
+{
+	uint8		keybuf[SCRAM_KEY_LEN + 1];
+	char		storedkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		serverkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		salt[SALT_LEN];
+	char	   *encoded_salt;
+	int			encoded_len;
+
+	if (iterations <= 0)
+		iterations = 4096;
+
+	generate_nonce(salt, SALT_LEN);
+
+	encoded_salt = palloc(b64_enc_len(salt, SALT_LEN) + 1);
+	encoded_len = b64_encode(salt, SALT_LEN, encoded_salt);
+	encoded_salt[encoded_len] = '\0';
+
+	/* Calculate StoredKey, and encode it in hex */
+	scram_ClientOrServerKey(password, salt, SALT_LEN, iterations, "Client Key", keybuf);
+	scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, storedkey_hex);
+	storedkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	/* And same for ServerKey */
+	scram_ClientOrServerKey(password, salt, SALT_LEN, iterations, "Server Key", keybuf);
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
+	serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	return psprintf("%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
+}
+
+
+static char *
+read_attr_value(char **input, char attr)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		elog(ERROR, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+read_any_attr(char **input, char *attr_p)
+{
+	char *begin = *input;
+	char *end;
+	char attr = *begin;
+
+	if (!((attr >= 'A' && attr <= 'Z') ||
+		  (attr >= 'a' && attr <= 'z')))
+		elog(ERROR, "malformed SCRAM message (invalid attribute char)");
+	if (attr_p)
+		*attr_p = attr;
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static void
+read_client_first_message(scram_state *state, char *input)
+{
+	input = pstrdup(input);
+
+	/*
+	 * saslname        = 1*(value-safe-char / "=2C" / "=3D")
+	 *              ;; Conforms to <value>.
+	 *
+	 * authzid         = "a=" saslname
+	 *              ;; Protocol specific.
+	 *
+	 * username        = "n=" saslname
+	 *               ;; Usernames are prepared using SASLprep.
+	 *
+	 * gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
+	 *               ;; "n" -> client doesn't support channel binding.
+	 *               ;; "y" -> client does support channel binding
+	 *               ;;        but thinks the server does not.
+	 *               ;; "p" -> client requires channel binding.
+	 *               ;; The selected channel binding follows "p=".
+	 *
+	 * gs2-header      = gs2-cbind-flag "," [ authzid ] ","
+	 *               ;; GS2 header for SCRAM
+	 *               ;; (the actual GS2 header includes an optional
+	 *               ;; flag to indicate that the GSS mechanism is not
+	 *               ;; "standard", but since SCRAM is "standard", we
+	 *               ;; don't include that flag).
+	 *
+	 *   client-first-message-bare =
+	 *               [reserved-mext ","]
+	 *               username "," nonce ["," extensions]
+	 *
+	 *   client-first-message =
+	 *                gs2-header client-first-message-bare
+	 *
+	 *
+	 * For example:
+	 * n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+	 */
+
+	/* read gs2-cbind-flag */
+	switch (*input)
+	{
+		case 'n':
+			/* client does not support channel binding */
+			input++;
+			break;
+		case 'y':
+			/* client supports channel binding, but we're not doing it today */
+			input++;
+			break;
+		case 'p':
+			/* client requires channel binding. We don't support it */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("channel binding not supported")));
+	}
+
+	/* any mandatory extensions would go here. */
+	if (*input != ',')
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mandatory extension %c not supported", *input)));
+	input++;
+
+	/* read optional authzid (authorization identity) */
+	if (*input != ',')
+		state->client_authzid = read_attr_value(&input, 'a');
+	else
+		input++;
+
+	state->client_first_message_bare = pstrdup(input);
+
+	/* read username (FIXME: unescape) */
+	state->client_username = read_attr_value(&input, 'n');
+
+	/* read nonce */
+	state->client_nonce = read_attr_value(&input, 'r');
+
+	/*
+	 * There can be any number of optional extensions after this. We don't
+	 * support any extensions, so ignore them.
+	 */
+	while (*input != '\0')
+		read_any_attr(&input, NULL);
+
+	/* success! */
+}
+
+static bool
+verify_final_nonce(scram_state *state)
+{
+	int			client_nonce_len = strlen(state->client_nonce);
+	int			server_nonce_len = strlen(state->server_nonce);
+	int			final_nonce_len = strlen(state->client_final_nonce);
+
+	if (final_nonce_len != client_nonce_len + server_nonce_len)
+		return false;
+	if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0)
+		return false;
+	if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0)
+		return false;
+
+	return true;
+}
+
+static bool
+verify_client_proof(scram_state *state)
+{
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		client_StoredKey[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+	int			i;
+
+	/* calculate ClientSignature */
+	scram_HMAC_init(&ctx, state->StoredKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+	elog(LOG, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+	elog(LOG, "AuthMessage: %s,%s,%s", state->client_first_message_bare,
+		 state->server_first_message, state->client_final_message_without_proof);
+
+	/* Extract the ClientKey that the client calculated from the proof */
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+
+	/* Hash it one more time, and compare with StoredKey */
+	scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey);
+	elog(LOG, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+	elog(LOG, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+	elog(LOG, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+	if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+		return false;
+
+	/* Also verify the nonce */
+
+	return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+	char		nonce[NONCE_LEN];
+	int			encoded_len;
+
+	/*
+	 * server-first-message =
+	 *                   [reserved-mext ","] nonce "," salt ","
+	 *                   iteration-count ["," extensions]
+	 *
+	 *   nonce           = "r=" c-nonce [s-nonce]
+	 *               ;; Second part provided by server.
+	 *
+	 * c-nonce         = printable
+	 *
+	 * s-nonce         = printable
+	 *
+	 * salt            = "s=" base64
+	 *
+	 * iteration-count = "i=" posit-number
+	 *              ;; A positive number.
+	 *
+	 * Example:
+	 *
+	 * r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+	 */
+	generate_nonce(nonce, NONCE_LEN);
+
+	state->server_nonce = palloc(b64_enc_len(nonce, NONCE_LEN) + 1);
+	encoded_len = b64_encode(nonce, NONCE_LEN, state->server_nonce);
+
+	state->server_nonce[encoded_len] = '\0';
+	state->server_first_message =
+		psprintf("r=%s%s,s=%s,i=%u",
+				 state->client_nonce, state->server_nonce,
+				 state->salt, state->iterations);
+
+	return state->server_first_message;
+}
+
+static void
+read_client_final_message(scram_state *state, char *input)
+{
+	char		attr;
+	char	   *channel_binding;
+	char	   *value;
+	char	   *begin, *proof;
+	char	   *p;
+	char	   *client_proof;
+
+	begin = p = pstrdup(input);
+
+	/*
+	 *
+	 * cbind-input   = gs2-header [ cbind-data ]
+	 *               ;; cbind-data MUST be present for
+	 *               ;; gs2-cbind-flag of "p" and MUST be absent
+	 *               ;; for "y" or "n".
+	 *
+	 * channel-binding = "c=" base64
+	 *               ;; base64 encoding of cbind-input.
+	 *
+	 * proof           = "p=" base64
+	 *
+	 * client-final-message-without-proof =
+	 *               channel-binding "," nonce ["," extensions]
+	 *
+	 * client-final-message =
+	 *              client-final-message-without-proof "," proof
+	 */
+	channel_binding = read_attr_value(&p, 'c');
+	if (strcmp(channel_binding, "biws") != 0)
+		elog(ERROR, "invalid channel binding input");
+	state->client_final_nonce = read_attr_value(&p, 'r');
+
+	/* ignore optional extensions */
+	do
+	{
+		proof = p - 1;
+		value = read_any_attr(&p, &attr);
+	} while (attr != 'p');
+
+	client_proof = palloc(b64_dec_len(value, strlen(value)));
+	if (b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+		elog(ERROR, "invalid ClientProof");
+	memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+	pfree(client_proof);
+
+	if (*p != '\0')
+		elog(ERROR, "malformed SCRAM message (garbage at end of message %c)", attr);
+
+	state->client_final_message_without_proof = palloc(proof - begin + 1);
+	memcpy(state->client_final_message_without_proof, input, proof - begin);
+	state->client_final_message_without_proof[proof - begin] = '\0';
+
+	/* FIXME: check channel_binding field */
+}
+
+
+static char *
+build_server_final_message(scram_state *state)
+{
+	uint8		ServerSignature[SCRAM_KEY_LEN];
+	char	   *server_signature_base64;
+	int			siglen;
+	scram_HMAC_ctx ctx;
+
+	/* calculate ServerSignature */
+	scram_HMAC_init(&ctx, state->ServerKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ServerSignature, &ctx);
+
+	server_signature_base64 = palloc(b64_enc_len((const char *) ServerSignature, SCRAM_KEY_LEN) + 1);
+	siglen = b64_encode((const char *) ServerSignature, SCRAM_KEY_LEN, server_signature_base64);
+	server_signature_base64[siglen] = '\0';
+
+	/*
+	 *
+	 * server-error = "e=" server-error-value
+	 *
+	 * server-error-value = "invalid-encoding" /
+	 *           "extensions-not-supported" /  ; unrecognized 'm' value
+	 *            "invalid-proof" /
+	 *            "channel-bindings-dont-match" /
+	 *            "server-does-support-channel-binding" /
+	 *              ; server does not support channel binding
+	 *            "channel-binding-not-supported" /
+	 *            "unsupported-channel-binding-type" /
+	 *            "unknown-user" /
+	 *            "invalid-username-encoding" /
+	 *              ; invalid username encoding (invalid UTF-8 or
+	 *              ; SASLprep failed)
+	 *            "no-resources" /
+	 *            "other-error" /
+	 *            server-error-value-ext
+	 *     ; Unrecognized errors should be treated as "other-error".
+	 *     ; In order to prevent information disclosure, the server
+	 *     ; may substitute the real reason with "other-error".
+	 *
+	 * server-error-value-ext = value
+	 *     ; Additional error reasons added by extensions
+	 *     ; to this document.
+	 *
+	 * verifier        = "v=" base64
+	 *               ;; base-64 encoded ServerSignature.
+	 *
+	 * server-final-message = (server-error / verifier)
+	 *                ["," extensions]
+	 */
+	return psprintf("v=%s", server_signature_base64);
+}
+
+static void
+generate_nonce(char *result, int len)
+{
+	/*
+	 * TODO: We reuse the salt generated for MD5 authentication. It's only
+	 * four bytes - we'd really want to use a much longer salt.
+	 */
+	memset(result, 0, len);
+	memcpy(result, MyProcPort->md5Salt, Min(sizeof(MyProcPort->md5Salt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 81226c2..54151cb 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -27,6 +27,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -185,6 +186,12 @@ static int	CheckRADIUSAuth(Port *port);
 
 
 /*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
  * Global authentication functions
  *----------------------------------------------------------------
  */
@@ -246,6 +253,7 @@ auth_failed(Port *port, int status, char *logdetail)
 			break;
 		case uaPassword:
 		case uaMD5:
+		case uaSASL:
 			errstr = gettext_noop("password authentication failed for user \"%s\"");
 			/* We use it to indicate if a .pgpass password failed. */
 			errcode_return = ERRCODE_INVALID_PASSWORD;
@@ -523,6 +531,10 @@ ClientAuthentication(Port *port)
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
+		case uaSASL:
+			status = CheckSASLAuth(port, &logdetail);
+			break;
+
 		case uaPAM:
 #ifdef USE_PAM
 			status = CheckPAMAuth(port, port->user_name, "");
@@ -691,6 +703,96 @@ recv_and_check_password_packet(Port *port, char **logdetail)
 	return result;
 }
 
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+	int			mtype;
+	StringInfoData buf;
+	void	   *scram_opaq;
+	char	   *verifier;
+	char	   *output = NULL;
+	int			outputlen = 0;
+	int			result;
+
+	/*
+	 * SASL auth is not supported for protocol versions before 3, because it
+	 * relies on the overall message length word to determine the SASL payload
+	 * size in AuthenticationSASLContinue and PasswordMessage messages. (We
+	 * used to have a hard rule that protocol messages must be parsable
+	 * without relying on the length word, but we hardly care about protocol
+	 * version or older anymore.)
+	 *
+	 * FIXME: the FE/BE docs need to updated.
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SASL authentication is not supported in protocol version 2")));
+
+	/* lookup verifier */
+	verifier = get_role_verifier(port->user_name, "scram", logdetail);
+	if (verifier == NULL)
+		return STATUS_ERROR;
+
+	sendAuthRequest(port, AUTH_REQ_SASL, "SCRAM-SHA-1", strlen("SCRAM-SHA-1") + 1);
+
+	scram_opaq = scram_init(port->user_name, verifier);
+
+	/*
+	 * Loop through SASL message exchange. This exchange can consist of
+	 * multiple messags sent in both directions. First message is always from
+	 * the client. All messages from client to server are password packets
+	 * (type 'p').
+	 */
+	do
+	{
+		pq_startmsgread();
+		mtype = pq_getbyte();
+		if (mtype != 'p')
+		{
+			/* Only log error if client didn't disconnect. */
+			if (mtype != EOF)
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("expected SASL response, got message type %d",
+								mtype)));
+			return STATUS_ERROR;
+		}
+
+		/* Get the actual SASL token */
+		initStringInfo(&buf);
+		if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
+		{
+			/* EOF - pq_getmessage already logged error */
+			pfree(buf.data);
+			return STATUS_ERROR;
+		}
+
+		elog(DEBUG4, "Processing received SASL token of length %d", buf.len);
+
+		result = scram_exchange(scram_opaq, buf.data, buf.len,
+								&output, &outputlen);
+
+		/* input buffer no longer used */
+		pfree(buf.data);
+
+		if (outputlen > 0)
+		{
+			/*
+			 * Negotiation generated data to be sent to the client.
+			 */
+			elog(DEBUG4, "sending SASL response token of length %u", outputlen);
+
+			sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+		}
+	} while (result == SASL_EXCHANGE_CONTINUE);
+
+	return (result == SASL_EXCHANGE_SUCCESS) ? STATUS_OK : STATUS_ERROR;
+}
 
 
 /*----------------------------------------------------------------
@@ -891,8 +993,8 @@ pg_GSS_recvauth(Port *port)
 				 (unsigned int) port->gss->outbuf.length);
 
 			/*
-			 * Add the authentication data for the next step of the GSSAPI or
-			 * SSPI negotiation.
+			 * Add the authentication data for the next step of the GSSAPI
+			 * negotiation.
 			 */
 			elog(DEBUG4, "sending GSS token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
@@ -1142,8 +1244,8 @@ pg_SSPI_recvauth(Port *port)
 			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
 
 			/*
-			 * Add the authentication data for the next step of the GSSAPI or
-			 * SSPI negotiation.
+			 * Add the authentication data for the next step of the SSPI
+			 * negotiation.
 			 */
 			elog(DEBUG4, "sending GSS token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index a0f5396..31bd78d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,20 @@ parse_hba_line(List *line, int line_num, char *raw_line)
 		}
 		parsedline->auth_method = uaMD5;
 	}
+	else if (strcmp(token->string, "scram") == 0)
+	{
+		/* FIXME: could we support Db_user_namespace with SCRAM? */
+		if (Db_user_namespace)
+		{
+			ereport(LOG,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("SCRAM authentication is not supported when \"db_user_namespace\" is enabled"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			return NULL;
+		}
+		parsedline->auth_method = uaSASL;
+	}
 	else if (strcmp(token->string, "pam") == 0)
 #ifdef USE_PAM
 		parsedline->auth_method = uaPAM;
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index 86a89ed..dc3ce2f 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,7 +42,7 @@
 # or "samenet" to match any address in any subnet that the server is
 # directly connected to.
 #
-# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
+# METHOD can be "trust", "reject", "md5", "password", "scram", "gss", "sspi",
 # "ident", "peer", "pam", "ldap", "radius" or "cert".  Note that
 # "password" sends passwords in clear text; "md5" is preferred since
 # it sends encrypted passwords.
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index 4b32b6c..c414e86 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -214,7 +214,7 @@ static const int8 b64lookup[128] = {
 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
 };
 
-static unsigned
+unsigned
 b64_encode(const char *src, unsigned len, char *dst)
 {
 	char	   *p,
@@ -261,7 +261,7 @@ b64_encode(const char *src, unsigned len, char *dst)
 	return p - dst;
 }
 
-static unsigned
+unsigned
 b64_decode(const char *src, unsigned len, char *dst)
 {
 	const char *srcend = src + len,
@@ -331,14 +331,14 @@ b64_decode(const char *src, unsigned len, char *dst)
 }
 
 
-static unsigned
+unsigned
 b64_enc_len(const char *src, unsigned srclen)
 {
 	/* 3 bytes will be converted to 4, linefeed after 76 chars */
 	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
 }
 
-static unsigned
+unsigned
 b64_dec_len(const char *src, unsigned srclen)
 {
 	return (srclen * 3) >> 2;
diff --git a/src/common/Makefile b/src/common/Makefile
index f526b42..5b1b8ac 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = exec.o pg_crc.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o sha1.o string.o username.o wait_error.o
+	rmtree.o scram-common.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o
 
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..714fd30
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,161 @@
+/*-------------------------------------------------------------------------
+ * scram-common.c
+ *		Shared frontend/backend code for SCRAM authentication
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the Salted Challenge Response Authentication
+ * Mechansim (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-1.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+	uint8		k_ipad[SHA1_HMAC_B];
+	int			i;
+	uint8		keybuf[SHA1_RESULTLEN];
+
+	/*
+	 * If the key is longer than the block size (64 bytes for SHA-1),
+	 * pass it through SHA-1 once to shrink it down
+	 */
+	if (keylen > SHA1_HMAC_B)
+	{
+		SHA1_CTX	sha1_ctx;
+
+		SHA1Init(&sha1_ctx);
+		SHA1Update(&sha1_ctx, key, keylen);
+		SHA1Final(keybuf, &sha1_ctx);
+		key = keybuf;
+		keylen = SHA1_RESULTLEN;
+	}
+
+	memset(k_ipad, 0x36, SHA1_HMAC_B);
+	memset(ctx->k_opad, 0x5C, SHA1_HMAC_B);
+	for (i = 0; i < keylen; i++)
+	{
+		k_ipad[i] ^= key[i];
+		ctx->k_opad[i] ^= key[i];
+	}
+
+	/* tmp = H(K XOR ipad, text) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, k_ipad, SHA1_HMAC_B);
+}
+
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const const char *str, int slen)
+{
+	SHA1Update(&ctx->sha1ctx, (const uint8 *) str, slen);
+}
+
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+	uint8		h[SHA1_RESULTLEN];
+
+	SHA1Final(h, &ctx->sha1ctx);
+
+	/* H(K XOR opad, tmp) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, ctx->k_opad, SHA1_HMAC_B);
+	SHA1Update(&ctx->sha1ctx, h, SHA1_RESULTLEN);
+	SHA1Final(result, &ctx->sha1ctx);
+}
+
+static void
+scram_Hi(const char *str, const char *salt, int saltlen, int iterations, uint8 *result)
+{
+	int			str_len = strlen(str);
+	uint32		one = htonl(1);
+	int			i, j;
+	uint8		Ui[SCRAM_KEY_LEN];
+	uint8		Ui_prev[SCRAM_KEY_LEN];
+	scram_HMAC_ctx hmac_ctx;
+
+	/* First iteration */
+	scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+	scram_HMAC_update(&hmac_ctx, salt, saltlen);
+	scram_HMAC_update(&hmac_ctx, (char *) &one, sizeof(uint32));
+	scram_HMAC_final(Ui_prev, &hmac_ctx);
+	memcpy(result, Ui_prev, SCRAM_KEY_LEN);
+
+	/* Subsequent iterations */
+	for (i = 2; i <= iterations; i++)
+	{
+		scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+		scram_HMAC_update(&hmac_ctx, (const char *) Ui_prev, SCRAM_KEY_LEN);
+		scram_HMAC_final(Ui, &hmac_ctx);
+		for (j = 0; j < SCRAM_KEY_LEN; j++)
+			result[j] ^= Ui[j];
+		memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
+	}
+}
+
+
+/*
+ * Calculate SHA-1 hash for a NULL-terminated string. (The NULL terminator is
+ * not included in the hash).
+ */
+void
+scram_H(const uint8 *input, int len, uint8 *result)
+{
+	SHA1_CTX	ctx;
+
+	SHA1Init(&ctx);
+	SHA1Update(&ctx, input, len);
+	SHA1Final(result, &ctx);
+}
+
+static void
+scram_Normalize(const char *password, char *result)
+{
+	/* TODO */
+	strlcpy(result, password, 20);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+					 uint8 *result)
+{
+	/* FIXME: pwbuf needs to be sized properly */
+	char		pwbuf[20];
+
+	scram_Normalize(password, pwbuf);
+	scram_Hi(pwbuf, salt, saltlen, iterations, result);
+}
+
+/*
+ * Calculate ClientKey or ServerKey.
+ */
+void
+scram_ClientOrServerKey(const char *password,
+						const char *salt, int saltlen, int iterations,
+						const char *keystr, uint8 *result)
+{
+	uint8		keybuf[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+
+	scram_SaltedPassword(password, salt, saltlen, iterations, keybuf);
+	scram_HMAC_init(&ctx, keybuf, 20);
+	scram_HMAC_update(&ctx, keystr, strlen(keystr));
+	scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..fce9337
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ *		Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/relpath.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCRAM_COMMON_H
+#define SCRAM_COMMON_H
+
+#include "common/sha1.h"
+
+#define SCRAM_KEY_LEN	SHA1_RESULTLEN
+
+#define SHA1_HMAC_B		64
+
+typedef struct
+{
+	SHA1_CTX	sha1ctx;
+	uint8		k_opad[SHA1_HMAC_B];
+} scram_HMAC_ctx;
+
+extern void scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen);
+extern void scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen);
+extern void scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx);
+
+extern void scram_H(const uint8 *str, int len, uint8 *result);
+extern void scram_ClientOrServerKey(const char *password, const char *salt, int saltlen, int iterations, const char *keystr, uint8 *result);
+
+#endif
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 80f26a8..4469565 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -22,6 +22,11 @@ extern char *pg_krb_realm;
 
 extern void ClientAuthentication(Port *port);
 
+/* Return codes for SASL authentication functions */
+#define	SASL_EXCHANGE_CONTINUE		0
+#define	SASL_EXCHANGE_SUCCESS		1
+#define	SASL_EXCHANGE_FAILURE		2
+
 /* Hook for plugins to get control in ClientAuthentication() */
 typedef void (*ClientAuthentication_hook_type) (Port *, int);
 extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index dfab8f3..77a388e 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,7 @@
 
 #include "libpq/libpq-be.h"
 
+extern char *get_role_verifier(const char *rolname, const char *method, char **logdetail);
 extern int md5_crypt_verify(const Port *port, const char *role,
 				 char *client_pass, char **logdetail);
 
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 68a953a..a73d2f9 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -24,6 +24,7 @@ typedef enum UserAuth
 	uaIdent,
 	uaPassword,
 	uaMD5,
+	uaSASL,
 	uaGSS,
 	uaSSPI,
 	uaPAM,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 639bf72..462a5dd 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -172,6 +172,8 @@ 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 */
 
 typedef uint32 AuthRequest;
 
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
new file mode 100644
index 0000000..f273392
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ *	  Interface to libpq/scram.c
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/scram.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SCRAM_H
+#define PG_SCRAM_H
+
+extern void *scram_init(const char *username, const char *verifier);
+extern int scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen);
+extern char *scram_build_verifier(char *username, char *password,
+					 int iterations);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 6310641..cad9852 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -157,6 +157,10 @@ extern Datum binary_encode(PG_FUNCTION_ARGS);
 extern Datum binary_decode(PG_FUNCTION_ARGS);
 extern unsigned hex_encode(const char *src, unsigned len, char *dst);
 extern unsigned hex_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_encode(const char *src, unsigned len, char *dst);
+extern unsigned b64_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_enc_len(const char *src, unsigned srclen);
+extern unsigned b64_dec_len(const char *src, unsigned srclen);
 
 /* enum.c */
 extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 6973a20..4eae326 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,7 @@ LIBS := $(LIBS:-lpgport=)
 
 # We can't use Makefile variables here because the MSVC build system scrapes
 # OBJS from this file.
-OBJS=	fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+OBJS=	fe-auth.o fe-auth-scram.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
 	fe-protocol2.o fe-protocol3.o pqexpbuffer.o fe-secure.o \
 	libpq-events.o
 # libpgport C files we always use
@@ -43,6 +43,9 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
 OBJS += ip.o md5.o
 # utils/mb
 OBJS += encnames.o wchar.o
+# common/
+# FIXME: any reason not to link with libpgcommon?
+OBJS += scram-common.o sha1.o
 
 ifeq ($(with_openssl),yes)
 OBJS += fe-secure-openssl.o
@@ -102,6 +105,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
 encnames.c wchar.c: % : $(backend_src)/utils/mb/%
 	rm -f $@ && $(LN_S) $< .
 
+scram-common.c sha1.c: % : $(top_srcdir)/src/common/%
+	rm -f $@ && $(LN_S) $< .
+
 
 distprep: libpq-dist.rc
 
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
new file mode 100644
index 0000000..9165a82
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,476 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ *	   The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		NONCE_SENT,
+		PROOF_SENT,
+		FINISHED
+	} state;
+
+	const char *username;
+	const char *password;
+
+	char	   *client_first_message_bare;
+
+	/* These come from the server-first message */
+	char	   *server_first_message;
+	char	   *salt;
+	int			saltlen;
+	int			iterations;
+	char	   *server_nonce;
+
+	/* These come from the server-final message */
+	char	   *server_proof;
+} fe_scram_state;
+
+static bool read_server_first_message(fe_scram_state *state, char *input, PQExpBuffer errormessage);
+static bool read_server_final_message(fe_scram_state *state, char *input);
+static char *build_client_first_message(fe_scram_state *state);
+static char *build_client_final_message(fe_scram_state *state);
+static bool verify_server_proof(fe_scram_state *state);
+static void generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state, const char *client_final_message_without_proof, uint8 *result);
+
+static unsigned b64_decode(const char *src, unsigned len, char *dst);
+static unsigned b64_encode(const char *src, unsigned len, char *dst);
+static unsigned b64_enc_len(const char *src, unsigned srclen);
+static unsigned b64_dec_len(const char *src, unsigned srclen);
+
+void *
+pg_fe_scram_init(const char *username, const char *password)
+{
+	fe_scram_state *state;
+
+	state = (fe_scram_state *) malloc(sizeof(fe_scram_state));
+	if (!state)
+		return NULL;
+	memset(state, 0, sizeof(fe_scram_state));
+	state->state = INIT;
+	state->username = username;
+	state->password = password;
+
+	return state;
+}
+
+void
+pg_fe_scram_free(void *opaq)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	if (state->client_first_message_bare)
+		free(state->client_first_message_bare);
+
+	if (state->server_first_message)
+		free(state->server_first_message);
+	if (state->salt)
+		free(state->salt);
+	if (state->server_nonce)
+		free(state->server_nonce);
+	if (state->server_proof)
+		free(state->server_proof);
+
+	free(state);
+}
+
+/*
+ * TODO: Even though SCRAM will authenticate the server, by verifying the
+ * server-proof, we don't currently store that information anywhere. Nothing
+ * stops the server from leaving the SASL exchange unfinished, and just send an
+ * AuthenticationOK message, leaving the client unsure of the server's
+ * identity.
+ */
+void
+pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	*done = false;
+	*success = false;
+	*output = NULL;
+	*outputlen = 0;
+
+	switch (state->state)
+	{
+		case INIT:
+			/* send client nonce */
+			*output = build_client_first_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = NONCE_SENT;
+			break;
+
+		case NONCE_SENT:
+			/* receive salt and server nonce, send response */
+			read_server_first_message(state, input, errorMessage);
+			*output = build_client_final_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = PROOF_SENT;
+			break;
+
+		case PROOF_SENT:
+			/* receive server proof, and verify it */
+			read_server_final_message(state, input);
+			*success = verify_server_proof(state);
+			*done = true;
+			state->state = FINISHED;
+			break;
+
+		default:
+			/* shouldn't happen */
+			*done = true;
+			*success = false;
+			printfPQExpBuffer(errorMessage, "invalid SCRAM exchange state");
+	}
+}
+
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+	char		nonce[10 + 1];
+	char	   *buf;
+	char		msglen;
+
+	generate_nonce(nonce, 10);
+
+	msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+
+	buf = malloc(msglen + 1);
+	snprintf(buf, msglen + 1, "n,,n=%s,r=%s", state->username, nonce);
+
+	state->client_first_message_bare = strdup(buf + 3);
+	if (!state->client_first_message_bare)
+		return NULL;
+
+	return buf;
+}
+
+static bool
+read_server_first_message(fe_scram_state *state, char *input, PQExpBuffer errormessage)
+{
+	char	   *iterations_str;
+	char	   *endptr;
+	char	   *encoded_salt;
+
+	state->server_first_message = strdup(input);
+	if (!state->server_first_message)
+		return false;
+
+	/* parse the message */
+	state->server_nonce = strdup(read_attr_value(&input, 'r', errormessage));
+	if (state->server_nonce == NULL)
+		return false;
+
+	encoded_salt = read_attr_value(&input, 's', errormessage);
+	if (encoded_salt == NULL)
+		return false;
+	state->salt = malloc(b64_dec_len(encoded_salt, strlen(encoded_salt)));
+	if (state->salt == NULL)
+		return false;
+	state->saltlen = b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+	if (state->saltlen <= 0)
+		return false;
+
+	iterations_str = read_attr_value(&input, 'i', errormessage);
+	if (iterations_str == NULL)
+		return false;
+	state->iterations = strtol(iterations_str, &endptr, 10);
+	if (*endptr != '\0')
+		return false;
+
+	if (*input != '\0')
+		return false;
+
+	return true;
+}
+
+static bool
+read_server_final_message(fe_scram_state *state, char *input)
+{
+	/* TODO: verify ServerSignature */
+	return true;
+}
+
+static char *
+build_client_final_message(fe_scram_state *state)
+{
+	char		client_final_message_without_proof[200];
+	uint8		client_proof[SCRAM_KEY_LEN];
+	char		client_proof_base64[SCRAM_KEY_LEN * 2 + 1];
+	int			client_proof_len;
+	char		buf[300];
+
+	snprintf(client_final_message_without_proof, sizeof(client_final_message_without_proof),
+			 "c=biws,r=%s", state->server_nonce);
+
+	calculate_client_proof(state, client_final_message_without_proof, client_proof);
+	if (b64_enc_len((char *) client_proof, SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+		return NULL;
+
+	client_proof_len = b64_encode((char *) client_proof, SCRAM_KEY_LEN, client_proof_base64);
+	client_proof_base64[client_proof_len] = '\0';
+
+	snprintf(buf, sizeof(buf), "%s,p=%s", client_final_message_without_proof, client_proof_base64);
+	fprintf(stderr, "final_msg: %s", buf);
+
+	return strdup(buf);
+}
+
+static void
+calculate_client_proof(fe_scram_state *state, const char *client_final_message_without_proof, uint8 *result)
+{
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	int			i;
+	scram_HMAC_ctx ctx;
+
+	scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+							state->iterations, "Client Key", ClientKey);
+	scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+	scram_HMAC_init(&ctx, StoredKey, 20);
+	scram_HMAC_update(&ctx, state->client_first_message_bare, strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx, state->server_first_message, strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx, client_final_message_without_proof, strlen(client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+
+	fprintf(stderr, "ClientSignature: %02X%02X\n", ClientSignature[0], ClientSignature[1]);
+
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+	/* TODO */
+	return true;
+}
+
+
+/*
+ * TODO: We need a source of randomness. libpq doesn't currently have one.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+	int			i;
+
+	for (i = 0; i < len; i++)
+	{
+		buf[i] = 'x';
+	}
+	buf[i] = '\0';
+}
+
+
+/*
+ * BASE64
+ */
+
+static const char _base64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static const int8 b64lookup[128] = {
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+};
+
+static unsigned
+b64_encode(const char *src, unsigned len, char *dst)
+{
+	char	   *p,
+			   *lend = dst + 76;
+	const char *s,
+			   *end = src + len;
+	int			pos = 2;
+	uint32		buf = 0;
+
+	s = src;
+	p = dst;
+
+	while (s < end)
+	{
+		buf |= (unsigned char) *s << (pos << 3);
+		pos--;
+		s++;
+
+		/* write it out */
+		if (pos < 0)
+		{
+			*p++ = _base64[(buf >> 18) & 0x3f];
+			*p++ = _base64[(buf >> 12) & 0x3f];
+			*p++ = _base64[(buf >> 6) & 0x3f];
+			*p++ = _base64[buf & 0x3f];
+
+			pos = 2;
+			buf = 0;
+		}
+		if (p >= lend)
+		{
+			*p++ = '\n';
+			lend = p + 76;
+		}
+	}
+	if (pos != 2)
+	{
+		*p++ = _base64[(buf >> 18) & 0x3f];
+		*p++ = _base64[(buf >> 12) & 0x3f];
+		*p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+		*p++ = '=';
+	}
+
+	return p - dst;
+}
+
+static unsigned
+b64_decode(const char *src, unsigned len, char *dst)
+{
+	const char *srcend = src + len,
+			   *s = src;
+	char	   *p = dst;
+	char		c;
+	int			b = 0;
+	uint32		buf = 0;
+	int			pos = 0,
+				end = 0;
+
+	while (s < srcend)
+	{
+		c = *s++;
+
+		if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+			continue;
+
+		if (c == '=')
+		{
+			/* end sequence */
+			if (!end)
+			{
+				if (pos == 2)
+					end = 1;
+				else if (pos == 3)
+					end = 2;
+				else
+				{
+					return 0;
+					//ereport(ERROR,
+					//		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					//		 errmsg("unexpected \"=\" while decoding base64 sequence")));
+				}
+			}
+			b = 0;
+		}
+		else
+		{
+			b = -1;
+			if (c > 0 && c < 127)
+				b = b64lookup[(unsigned char) c];
+			if (b < 0)
+			{
+				//ereport(ERROR,
+				//		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				//		 errmsg("invalid symbol '%c' while decoding base64 sequence", (int) c)));
+				return 0;
+			}
+		}
+		/* add it to buffer */
+		buf = (buf << 6) + b;
+		pos++;
+		if (pos == 4)
+		{
+			*p++ = (buf >> 16) & 255;
+			if (end == 0 || end > 1)
+				*p++ = (buf >> 8) & 255;
+			if (end == 0 || end > 2)
+				*p++ = buf & 255;
+			buf = 0;
+			pos = 0;
+		}
+	}
+
+	if (pos != 0)
+	{
+		//ereport(ERROR,
+		//		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+		//		 errmsg("invalid base64 end sequence"),
+		//		 errhint("Input data is missing padding, truncated, or otherwise corrupted.")));
+		return 0;
+	}
+
+	return p - dst;
+}
+
+
+static unsigned
+b64_enc_len(const char *src, unsigned srclen)
+{
+	/* 3 bytes will be converted to 4, linefeed after 76 chars */
+	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
+}
+
+static unsigned
+b64_dec_len(const char *src, unsigned srclen)
+{
+	return (srclen * 3) >> 2;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 8927df4..24c8d4a 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -428,6 +428,74 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
 }
 #endif   /* ENABLE_SSPI */
 
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+	/*
+	 * Check the authentication mechanism (only SCRAM-SHA-1 is supported at
+	 * the moment.)
+	 */
+	if (strcmp(conn->auth_req_inbuf, "SCRAM-SHA-1") == 0)
+	{
+		conn->password_needed = true;
+		if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  PQnoPasswordSupplied);
+			return STATUS_ERROR;
+		}
+		conn->sasl_state = pg_fe_scram_init(conn->pguser, conn->pgpass);
+		if (!conn->sasl_state)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("out of memory\n"));
+			return STATUS_ERROR;
+		}
+		else
+			return STATUS_OK;
+	}
+	else
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("SASL authentication mechanism %s not supported\n"),
+						  (char *) conn->auth_req_inbuf);
+		return STATUS_ERROR;
+	}
+}
+
+static int
+pg_SASL_exchange(PGconn *conn)
+{
+	char	   *output;
+	int			outputlen;
+	bool		done;
+	bool		success;
+	int			res;
+
+	pg_fe_scram_exchange(conn->sasl_state,
+						 conn->auth_req_inbuf, conn->auth_req_inlen,
+						 &output, &outputlen,
+						 &done, &success, &conn->errorMessage);
+	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);
+
+		if (res != STATUS_OK)
+			return STATUS_ERROR;
+	}
+
+	if (done && !success)
+		return STATUS_ERROR;
+
+	return STATUS_OK;
+}
+
 /*
  * Respond to AUTH_REQ_SCM_CREDS challenge.
  *
@@ -696,6 +764,33 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
 			}
 			break;
 
+		case AUTH_REQ_SASL:
+			/*
+			 * The request contains the name (as assigned by IANA) of the
+			 * authentication mechanism.
+			 */
+			if (pg_SASL_init(conn, conn->auth_req_inbuf) != STATUS_OK)
+			{
+				/* pg_SASL_init already set the error message */
+				return STATUS_ERROR;
+			}
+			/* fall through */
+
+		case AUTH_REQ_SASL_CONT:
+			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) != STATUS_OK)
+			{
+				printfPQExpBuffer(&conn->errorMessage,
+					 "fe_sendauth: error sending password authentication\n");
+				return STATUS_ERROR;
+			}
+			break;
+
 		case AUTH_REQ_SCM_CREDS:
 			if (pg_local_sendauth(conn) != STATUS_OK)
 				return STATUS_ERROR;
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 8d35767..b1b0294 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -18,7 +18,15 @@
 #include "libpq-int.h"
 
 
+/* Prototypes for functions in fe-auth.c */
 extern int	pg_fe_sendauth(AuthRequest areq, PGconn *conn);
 extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 
+/* Prototypes for functions in fe-auth-scram.c */
+extern void *pg_fe_scram_init(const char *username, const char *password);
+extern void pg_fe_scram_free(void *opaq);
+extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage);
+
 #endif   /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e2a06b3..c14fe73 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2479,6 +2479,46 @@ keep_going:						/* We will come back to here until there is
 					}
 				}
 #endif
+				/* Get additional payload, if any */
+				if (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 GSSAPI buffer (%d)"),
+											  llen);
+							goto error_return;
+						}
+					}
+
+					if (pqGetnchar(conn->auth_req_inbuf, llen, conn))
+					{
+						/* We'll come back when there is 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
@@ -3035,6 +3075,15 @@ closePGconn(PGconn *conn)
 		conn->sspictx = NULL;
 	}
 #endif
+	if (conn->sasl_state)
+	{
+		/*
+		 * XXX: if we add support for more authentication mechanisms, this
+		 * needs to call the right 'free' function.
+		 */
+		pg_fe_scram_free(conn->sasl_state);
+		conn->sasl_state = NULL;
+	}
 }
 
 /*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 2175957..391192b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -421,7 +421,12 @@ 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 SSL, GSS, etc */
+	void	   *sasl_state;
 
 #ifdef USE_SSL
 	bool		allow_ssl_try;	/* Allowed to try SSL negotiation */
-- 
2.1.4

#2Stephen Frost
sfrost@snowman.net
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

* Heikki Linnakangas (hlinnaka@iki.fi) wrote:

There have been numerous threads on replacing our MD5 authentication
method, so I started hacking on that to see what it might look like.
Just to be clear, this is 9.6 material. Attached is a WIP patch
series that adds support for SCRAM. There's no need to look at the
details yet, but it demonstrates what the protocol changes and the
code structure would be like.

Great! Very glad that you're working on this.

I'm not wedded to SCRAM - SRP or JPAKE or something else might be
better. But replacing the algorithm, or adding more of them, should
be straightforward with this.

Excellent.

3. Allow storing multiple verifiers in pg_authid
------------------------------------------------

Replace the pg_authid.rolpassword text field with an array, and
rename it to 'rolverifiers'. This allows storing multiple password
hashes: an MD5 hash for MD5 authentication, and a SCRAM salt and
stored key for SCRAM authentication, etc. Each element in the array
is a string that begins with the method's name. For example
"md5:<MD5 hash>", or "password:<plaintext>".

For dump/reload, and for clients that wish to create the hashes in
the client-side, there is a new option to CREATE/ALTER USER
commands: PASSWORD VERIFIERS '{ ... }', that allows replacing the
array.

The old "ENCRYPTED/UNENCRYPTED PASSWORD 'foo'" options are still
supported for backwards-compatibility, but it's not clear what it
should mean now.

TODO:

* Password-checking hook needs to be redesigned, to allow for more
kinds of hashes.

* With "CREATE USER PASSWORD 'foo'", which hashes/verifiers should
be generated by default? We currently have a boolean
password_encryption setting for that. Needs to be a list.

This generally sounds good to me but we definitely need to have that
list of hashes to be used. The MIT KDC for Kerberos (and I believe all
the other Kerberos implementations) have a similar setting for what will
be stored and what will be allowed for hashing and encryption options.
It's very important that we allow users to tweak this list, as we will
want to encourage users to migrate off of the existing md5 storage
mechanism and on to the SCRAM based one eventually.

Unfortunately, the first major release with this will certainly need to
default to including md5 as we can't have a password update or change
break clients right off the bat. What I think would be fantastic would
be a warning, perhaps in the first release or maybe the second, which
deprecates md5 as an auth method and is thrown when a password is set
which includes storing an md5-based password. I'm sure there will be
plenty of discussion about that in the future.

One additional item is that we need to have a way to prefer SCRAM-based
auth while allowing a fall-back to md5 if the client doesn't support it.
This might have to be driven by the client side explicitly saying "I
support SCRAM" from the start to avoid breaking existing clients.

4. Implement SCRAM
------------------

The protocol and the code is structured so that it would be fairly
easy to add more built-in SASL mechanisms, or to use a SASL library
to provide more. But for now I'm focusing on adding exactly one new
built-in mechanism, to replace MD5 in the long term.

In the protocol, there is a new AuthenticationSASL message,
alongside the existing AuthenticationMD5, AuthenticationSSPI etc.
The AuthenticationSASL message contains the name of the SASL
mechanism used ("SCRAM-SHA-1"). Just like in the GSSAPI/SSPI
authentication, a number of PasswordMessage and
AuthenticationSASLContinue messages are exchanged after that,
carrying the data specified by the SCRAM spec, until the
authentication succeeds (or not).

TODO:

* Per the SCRAM specification, the client sends the username in the
handshake. But in the FE/BE protocol, we've already sent it in the
startup packet. In the patch, libpq always sends an empty username
in the SCRAM exchange, and the username from the startup packet is
what matters. We could also require it to be the same, but in SCRAM
the username to be UTF-8 encoded, while in PostgreSQL the username
can be in any encoding. That is a source of annoyance in itself, as
it's not well-defined in PostgreSQL which encoding to use when
sending a username to the server. But I don't want to try fixing
that in this patch, so it seems easiest to just require the username
to be empty.

I don't like having it be empty.. I'm not looking at the spec right at
the moment, but have you confirmed that the username being empty during
the SCRAM discussion doesn't reduce the effectiveness of the
authentication method overall in some way? Is it ever used in
generation of the authentication verifier, etc? One way to address the
risk which you bring up about the different encodings might be to simply
discourage using non-UTF8-compliant encodings by throwing a warning or
refusing to support SCRAM in cases where the role wouldn't be allowed by
SCRAM (eg: in CREATE ROLE or ALTER ROLE when the SCRAM auth verifier
storage is being handled). Another option might be to define a way to
convert from "whatever" to "UTF8 something" for the purposes of the
SCRAM auth method.

* Need a source of randomness in client, to generate random nonces
used in the handshake. The SCRAM specification is not explicit about
it, but I believe it doesn't need to be unpredictable, as long as a
different nonce is used for each authentication.

I'd *very* much prefer a well defined and understood way (ideally
implemented in some well known and maintained library) rather than
trying to work out something ourselves. Further, it'd be good to review
what others have done in this space with SCRAM as there may be lessons
learned or at least well reviewed approaches to consider.

* The client does not authenticate the server, even though the SCRAM
protocol allows that. The client does verify the proof the server
sends, but nothing stops a malicious server that's impersonating the
real server from not requesting SCRAM authentication in the first
place. It could just send AuthenticationOK without any
authentication at all. To take advantage of the server
authentication, we'll need to add something similar to the
"sslmode=verify-ca" option in the client. In that mode, the client
should refuse the connection if the server doesn't request SCRAM
authentication (or some other future authentication mechanism that
authenticates the server to the client).

Agreed, we should have a way for the client to require SCRAM.
Presumably we would do this for libpq-based clients and expect other
implementations to look at the options we build into libpq for their own
versions (eg: JDBC). There's nothing protocol-level to be done here
that I can think of off-hand.

* Channel binding is not implemented. Not essential, but would be
nice to have. Together with the new client option mentioned in the
previous point, it would allow the client to know that there is no
man-in-the-middle, without having to verify the server's SSL
certificate.

Agreed, this is definitely one of the good features of SCRAM and should
be included.

Haven't looked at the code at all.

Thanks!

Stephen

#3Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Stephen Frost (#2)
Re: WIP: SCRAM authentication

On 03/30/2015 06:46 PM, Stephen Frost wrote:

* Heikki Linnakangas (hlinnaka@iki.fi) wrote:

* With "CREATE USER PASSWORD 'foo'", which hashes/verifiers should
be generated by default? We currently have a boolean
password_encryption setting for that. Needs to be a list.

This generally sounds good to me but we definitely need to have that
list of hashes to be used. The MIT KDC for Kerberos (and I believe all
the other Kerberos implementations) have a similar setting for what will
be stored and what will be allowed for hashing and encryption options.
It's very important that we allow users to tweak this list, as we will
want to encourage users to migrate off of the existing md5 storage
mechanism and on to the SCRAM based one eventually.

Unfortunately, the first major release with this will certainly need to
default to including md5 as we can't have a password update or change
break clients right off the bat. What I think would be fantastic would
be a warning, perhaps in the first release or maybe the second, which
deprecates md5 as an auth method and is thrown when a password is set
which includes storing an md5-based password. I'm sure there will be
plenty of discussion about that in the future.

Yeah. And even if client are updated, and the server is upgraded, you
still cannot use SCRAM until all the passwords have been changed and the
SCRAM verifiers for them generated. Unless we go with the scheme I
mentioned earlier, and use the MD5 hash of the password as the
"plaintext" password to SCRAM.

One additional item is that we need to have a way to prefer SCRAM-based
auth while allowing a fall-back to md5 if the client doesn't support it.
This might have to be driven by the client side explicitly saying "I
support SCRAM" from the start to avoid breaking existing clients.

I'll start a separate thread on this. It's an interesting feature on its
own. As well as an option in libpq to refuse plaintext authentication
even if the server asks for it.

* Per the SCRAM specification, the client sends the username in the
handshake. But in the FE/BE protocol, we've already sent it in the
startup packet. In the patch, libpq always sends an empty username
in the SCRAM exchange, and the username from the startup packet is
what matters. We could also require it to be the same, but in SCRAM
the username to be UTF-8 encoded, while in PostgreSQL the username
can be in any encoding. That is a source of annoyance in itself, as
it's not well-defined in PostgreSQL which encoding to use when
sending a username to the server. But I don't want to try fixing
that in this patch, so it seems easiest to just require the username
to be empty.

I don't like having it be empty.. I'm not looking at the spec right at
the moment, but have you confirmed that the username being empty during
the SCRAM discussion doesn't reduce the effectiveness of the
authentication method overall in some way?

Yes.

Is it ever used in
generation of the authentication verifier, etc? One way to address the
risk which you bring up about the different encodings might be to simply
discourage using non-UTF8-compliant encodings by throwing a warning or
refusing to support SCRAM in cases where the role wouldn't be allowed by
SCRAM (eg: in CREATE ROLE or ALTER ROLE when the SCRAM auth verifier
storage is being handled). Another option might be to define a way to
convert from "whatever" to "UTF8 something" for the purposes of the
SCRAM auth method.

Presumably the username used in the SCRAM exchange would have to match
the username sent in the startup packet. Otherwise things get weird. If
an empty string is a problem (there actually seems to be some language
in the spec to forbid or at least discourage using an empty string as
username), we could also specify some other constant that must be used,
to mean "same as in startup packet".

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Stephen Frost
sfrost@snowman.net
In reply to: Heikki Linnakangas (#3)
Re: WIP: SCRAM authentication

Heikki,

* Heikki Linnakangas (hlinnaka@iki.fi) wrote:

On 03/30/2015 06:46 PM, Stephen Frost wrote:

Unfortunately, the first major release with this will certainly need to
default to including md5 as we can't have a password update or change
break clients right off the bat. What I think would be fantastic would
be a warning, perhaps in the first release or maybe the second, which
deprecates md5 as an auth method and is thrown when a password is set
which includes storing an md5-based password. I'm sure there will be
plenty of discussion about that in the future.

Yeah. And even if client are updated, and the server is upgraded,
you still cannot use SCRAM until all the passwords have been changed
and the SCRAM verifiers for them generated. Unless we go with the
scheme I mentioned earlier, and use the MD5 hash of the password as
the "plaintext" password to SCRAM.

I don't like using the MD5 hash of the password for the token or to
generate the verifier. I'm no cryptographer and can't speak with any
certainty on the topic, but I understand there can be cases where doing
such would actively reduce the complexity required to find a usable
token. Further, to be frank and slightly paranoid, it's possible that
individuals have built up caches of known-to-be-valid PG-encoded md5
strings from backups or other stolen data and we would not be doing
people who run those systems any favors with that approach.

At the end of the day, I'd much prefer to see a clean break with new
passwords rolled out by administrators working with their users. That,
of course, would be much easier if we provided the basics that every
other auth system out there does today, which are the capabilities
available through pam_unix, pam_pwquality, pam_cracklib, etc. In
particular, things like password history, password validity, password
complexity, account inactivity, account login history (successful vs.
failed attempts), are required in many environments, specifically called
out by NIST 800-53 which is required for US Government deployments, and
probably included in other standards also (eg: PCI, HIPPA, etc).

These are all things I was fighting for 10-or-so years ago. I sincerely
hope that we're ready to consider these capabilities as being desirable.
I'm certainly hoping to work on them for 9.6 and will be happy to
support them going forward.

One additional item is that we need to have a way to prefer SCRAM-based
auth while allowing a fall-back to md5 if the client doesn't support it.
This might have to be driven by the client side explicitly saying "I
support SCRAM" from the start to avoid breaking existing clients.

I'll start a separate thread on this. It's an interesting feature on
its own. As well as an option in libpq to refuse plaintext
authentication even if the server asks for it.

Agreed on both counts.

Is it ever used in
generation of the authentication verifier, etc? One way to address the
risk which you bring up about the different encodings might be to simply
discourage using non-UTF8-compliant encodings by throwing a warning or
refusing to support SCRAM in cases where the role wouldn't be allowed by
SCRAM (eg: in CREATE ROLE or ALTER ROLE when the SCRAM auth verifier
storage is being handled). Another option might be to define a way to
convert from "whatever" to "UTF8 something" for the purposes of the
SCRAM auth method.

Presumably the username used in the SCRAM exchange would have to
match the username sent in the startup packet. Otherwise things get
weird. If an empty string is a problem (there actually seems to be
some language in the spec to forbid or at least discourage using an
empty string as username), we could also specify some other constant
that must be used, to mean "same as in startup packet".

Ok.. Having it be a constant which means "same as in startup packet"
may be workable. My suggestion above was intended to be "let's figure
out a way to encode whatever is in the startup packet to work in UTF8,
and then we will decode it ourselves" kind of idea. That would make it
byte-wise different (at least in some cases- we might be able to
minimize the number of cases that happens), but semantically identical.
What would be *really* nice would be to say "if your client only speaks
UTF8, then you don't have to worry about this and everything just works
like normal." That might be too much of a stretch though.

Thanks!

Stephen

#5Michael Paquier
michael.paquier@gmail.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On Mon, Mar 30, 2015 at 7:52 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

There have been numerous threads on replacing our MD5 authentication
method, so I started hacking on that to see what it might look like. Just
to be clear, this is 9.6 material. Attached is a WIP patch series that adds
support for SCRAM. There's no need to look at the details yet, but it
demonstrates what the protocol changes and the code structure would be like.

I'm not wedded to SCRAM - SRP or JPAKE or something else might be better.
But replacing the algorithm, or adding more of them, should be
straightforward with this.

Agreed. We need such a facility.

There is no negotiation of the authentication mechanism. SCRAM is just
added as a new one, alongside all the existing ones. If the server requests
SCRAM authentication, but the client doesn't support it, the attempt will
fail. We might want to do something about that, to make the transition
easier, but it's an orthogonal feature and not absolutely required.

There are four patches in the series. The first two are just refactoring:
moving the SHA-1 implementation from pgcrypto to src/common, and some
refactoring in src/backend/auth.c that IMHO would make sense anyway.

The two first patches of the series look good to me.

Patches three and four are the interesting ones:

I have not looked in details yet at number implementing SCRAM.

3. Allow storing multiple verifiers in pg_authid
------------------------------------------------

Replace the pg_authid.rolpassword text field with an array, and rename it
to 'rolverifiers'. This allows storing multiple password hashes: an MD5
hash for MD5 authentication, and a SCRAM salt and stored key for SCRAM
authentication, etc. Each element in the array is a string that begins with
the method's name. For example "md5:<MD5 hash>", or "password:<plaintext>".

For dump/reload, and for clients that wish to create the hashes in the
client-side, there is a new option to CREATE/ALTER USER commands: PASSWORD
VERIFIERS '{ ... }', that allows replacing the array.

The old "ENCRYPTED/UNENCRYPTED PASSWORD 'foo'" options are still supported
for backwards-compatibility, but it's not clear what it should mean now.

TODO:

* Password-checking hook needs to be redesigned, to allow for more kinds
of hashes.

* With "CREATE USER PASSWORD 'foo'", which hashes/verifiers should be
generated by default? We currently have a boolean password_encryption
setting for that. Needs to be a list.

I have been looking more in depths at this one, which adds essential
infrastructure to support multiple authentication hashes for more
protocols. Here are some comments:
- Docs are missing (not a big issue for a WIP)
- Instead of an array that has an identified embedded, let's add a new
catalog pg_authid_hashes that stores all the hashes for a user (idea by
Heikki):
-- hashrol, role Oid associated with the hash
-- hashmet, hash method
-- hashval, value of the hash
- New password-checking hook (contrib/passwordcheck will need a refresh).
As of now, we have that:
void (*check_password_hook_type)
(const char *username,
const char *password,
int password_type,
Datum validuntil_time,
bool validuntil_null);
We need to switch to something that checks a list of hashes:
void (*check_password_hook_type)
(const char *username,
list *passwd,
Datum validuntil_time,
bool validuntil_null);
passwd is a structure containing the password type and the hash value.
Password type can then be "plain" (or password to match pg_hba.conf) or
"md5" for now.
- When password_encryption is switched to a list, true means md5, and false
means plain. At the addition of SCRAM, we could think harder the default
value, "true" may be worth meaning "md5,scram".
- For CREATE ROLE/ALTER ROLE, it is necessary to be able to define the list
of hashes that need to be generated, with something like that for example:
[ ENCRYPTED [(md5[, scram])] | UNENCRYPTED ] PASSWORD 'password'
When UNENCRYPTED is used, we could simply store the password as plain. When
only ENCRYPTED is used, we store it for all the methods available, except
"plain". ENCRYPTED and plain are not allowed combinations.
- Also, do we really want an option at SQL level to allow storing custom
hashes generated on client side as a first step? We could have something
like WITH (md5 = 'blah', scram = 'blah2') appended after PASSWORD for
example.
- rolpassword is removed from pg_authid.

I am willing to write a patch for the next CF following more or less those
lines, depending of course on the outcome of the discussion we can have
here, so feel free to comment.

I'll have a look more in-depth at the scram patch as well.
Regards,
--
Michael

#6Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#5)
4 attachment(s)
Re: WIP: SCRAM authentication

On Tue, Aug 4, 2015 at 4:20 PM, Michael Paquier wrote:

I have been looking more in depths at this one, which adds essential
infrastructure to support multiple authentication hashes for more protocols.
Here are some comments:
[spec lines]
I am willing to write a patch for the next CF following more or less those
lines, depending of course on the outcome of the discussion we can have
here, so feel free to comment.

OK, attached as 0001 is the patch that respects those lines for the
support of multiple password verifiers in system catalogs. I have
added a new catalog called pg_auth_verifiers that is used at
authentication to fetch a password value depending on the protocol
used. With only this patch attached there are two types of verifiers:
plain and md5. This new catalog is REVOKE'd like pg_authid (pg_authid
could be made readable be this seems sensitive to me so I am not
changing it).

I have as well done the following things:
- Added PASSWORD VERIFIER (md5 = 'hoge', plain = 'hoge') which is used
as well by pg_dump all to be able to specify password verifiers one by
one.
- password check hook has been reworked as mentioned to be able to
manage a list of password verifiers instead of a single entry.
contrib/passwordcheck has been updated as well.
- Added regression tests testing UNENCRYPTED/ENCRYPTED, PASSWORD
VERIFIER, PASSWORD, etc.
- The patch does not break backward compatibility regarding CREATE
ROLE and ALTER ROLE.
- password_encryption has been changed to a list with comma-separated
elements, for now the possible elements are 'md5' and 'plain'. This
breaks backward compatibility, so if we care about it we should
consider having a new GUC password_encryption_list or similar. Default
is md5, default that does not change backward compatibility.
- Added documentation.
- pg_shadow has been updated, switching to an array with
method:password as elements.

I'll have a look more in-depth at the scram patch as well.

The SCRAM patch (0002~0004) has been rebased to use the new facility.
I have as well fixed a syscache leak... But I haven't been able to
enter much in the details yet.

The patch 0001 to add the multiple verifier facility is in a state
good enough to get some review, so I am registering an entry for it in
the next CF. And I'll continue the work on the SCRAM portion next
week, with hopefully a version ready for CF submission.
Regards,
--
Michael

Attachments:

0001-Add-facility-to-store-multiple-password-formats.patchapplication/x-patch; name=0001-Add-facility-to-store-multiple-password-formats.patchDownload
From 30e502490276224713a0fcd533cd55e278327d2e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 7 Aug 2015 13:35:40 +0900
Subject: [PATCH 1/4] Add facility to store multiple password formats

This commit adds a new cluster-wide catalog table called pg_auth_verifiers
extending the existing one-password value per role approach into a facility
ablt to store multiple passwords formats for one user. This makes easier to
add additional password format support in the future and is a requirement
for the additional of SCRAM-SHA1.

CREATE ROLE and ALTER ROLE are extended with PASSWORD VERIFIERS that allow
a user to set a list of password identifiers at will, something particularly
useful for pg_dump that makes use of it with this commit.

password_encryption is transformed into a list able to use "md5" or "plain",
or even both when CREATE/ALTER ROLE uses neither ENCRYPTED/UNENCRYPTED.

The password check hook has been redesigned to be able to check a list
of passwords instead of a single entry, and the related contrib module
passwordcheck/ is updated respecting the new format.

Regression tests and documentation are added accordingly.
---
 contrib/passwordcheck/passwordcheck.c         | 138 +++++------
 doc/src/sgml/catalogs.sgml                    |  99 ++++++--
 doc/src/sgml/config.sgml                      |  17 +-
 doc/src/sgml/ref/create_role.sgml             |  23 +-
 src/backend/catalog/Makefile                  |   4 +-
 src/backend/catalog/catalog.c                 |   4 +
 src/backend/catalog/system_views.sql          |  11 +-
 src/backend/commands/user.c                   | 315 +++++++++++++++++---------
 src/backend/libpq/crypt.c                     |  72 ++++--
 src/backend/nodes/copyfuncs.c                 |  14 ++
 src/backend/nodes/equalfuncs.c                |  12 +
 src/backend/parser/gram.y                     |  98 +++++++-
 src/backend/utils/cache/catcache.c            |   1 +
 src/backend/utils/cache/relcache.c            |  17 +-
 src/backend/utils/cache/syscache.c            |  23 ++
 src/backend/utils/misc/guc.c                  |  65 ++++--
 src/backend/utils/misc/postgresql.conf.sample |   2 +-
 src/bin/initdb/initdb.c                       |   5 +-
 src/bin/pg_dump/pg_dumpall.c                  |  77 ++++++-
 src/include/catalog/indexing.h                |   5 +
 src/include/catalog/pg_auth_verifiers.h       |  62 +++++
 src/include/catalog/pg_authid.h               |   8 +-
 src/include/commands/user.h                   |  11 +-
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/parsenodes.h                |  11 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/utils/syscache.h                  |   2 +
 src/test/regress/expected/password.out        | 101 +++++++++
 src/test/regress/expected/rules.out           |   9 +-
 src/test/regress/expected/sanity_check.out    |   1 +
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/serial_schedule              |   1 +
 src/test/regress/sql/password.sql             |  70 ++++++
 33 files changed, 1017 insertions(+), 265 deletions(-)
 create mode 100644 src/include/catalog/pg_auth_verifiers.h
 create mode 100644 src/test/regress/expected/password.out
 create mode 100644 src/test/regress/sql/password.sql

diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 78c44b2..5ee38ed 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -20,9 +20,11 @@
 #include <crack.h>
 #endif
 
+#include "catalog/pg_auth_verifiers.h"
 #include "commands/user.h"
 #include "fmgr.h"
 #include "libpq/md5.h"
+#include "nodes/parsenodes.h"
 
 PG_MODULE_MAGIC;
 
@@ -50,87 +52,93 @@ extern void _PG_init(void);
  */
 static void
 check_password(const char *username,
-			   const char *password,
-			   int password_type,
+			   List *passwordVerifiers,
 			   Datum validuntil_time,
 			   bool validuntil_null)
 {
 	int			namelen = strlen(username);
-	int			pwdlen = strlen(password);
+	int			pwdlen;
 	char		encrypted[MD5_PASSWD_LEN + 1];
 	int			i;
 	bool		pwd_has_letter,
 				pwd_has_nonletter;
+	ListCell   *l;
 
-	switch (password_type)
+	foreach(l, passwordVerifiers)
 	{
-		case PASSWORD_TYPE_MD5:
-
-			/*
-			 * Unfortunately we cannot perform exhaustive checks on encrypted
-			 * passwords - we are restricted to guessing. (Alternatively, we
-			 * could insist on the password being presented non-encrypted, but
-			 * that has its own security disadvantages.)
-			 *
-			 * We only check for username = password.
-			 */
-			if (!pg_md5_encrypt(username, username, namelen, encrypted))
-				elog(ERROR, "password encryption failed");
-			if (strcmp(password, encrypted) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password must not contain user name")));
-			break;
-
-		case PASSWORD_TYPE_PLAINTEXT:
-
-			/*
-			 * For unencrypted passwords we can perform better checks
-			 */
-
-			/* enforce minimum length */
-			if (pwdlen < MIN_PWD_LENGTH)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password is too short")));
-
-			/* check if the password contains the username */
-			if (strstr(password, username))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password must not contain user name")));
-
-			/* check if the password contains both letters and non-letters */
-			pwd_has_letter = false;
-			pwd_has_nonletter = false;
-			for (i = 0; i < pwdlen; i++)
-			{
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		switch (spec->veriftype)
+		{
+			case AUTH_VERIFIER_MD5:
+
+				/*
+				 * Unfortunately we cannot perform exhaustive checks on encrypted
+				 * passwords - we are restricted to guessing. (Alternatively, we
+				 * could insist on the password being presented non-encrypted, but
+				 * that has its own security disadvantages.)
+				 *
+				 * We only check for username = password.
+				 */
+				if (!pg_md5_encrypt(username, username, namelen, encrypted))
+					elog(ERROR, "password encryption failed");
+				if (strcmp(spec->value, encrypted) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password must not contain user name")));
+				break;
+
+			case AUTH_VERIFIER_PLAIN:
+
 				/*
-				 * isalpha() does not work for multibyte encodings but let's
-				 * consider non-ASCII characters non-letters
+				 * For unencrypted passwords we can perform better checks
 				 */
-				if (isalpha((unsigned char) password[i]))
-					pwd_has_letter = true;
-				else
-					pwd_has_nonletter = true;
-			}
-			if (!pwd_has_letter || !pwd_has_nonletter)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				errmsg("password must contain both letters and nonletters")));
+				pwdlen = strlen(spec->value);
+
+				/* enforce minimum length */
+				if (pwdlen < MIN_PWD_LENGTH)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password is too short")));
+
+				/* check if the password contains the username */
+				if (strstr(spec->value, username))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password must not contain user name")));
+
+				/* check if the password contains both letters and non-letters */
+				pwd_has_letter = false;
+				pwd_has_nonletter = false;
+				for (i = 0; i < pwdlen; i++)
+				{
+					/*
+					 * isalpha() does not work for multibyte encodings but let's
+					 * consider non-ASCII characters non-letters
+					 */
+					if (isalpha((unsigned char) spec->value[i]))
+						pwd_has_letter = true;
+					else
+						pwd_has_nonletter = true;
+				}
+				if (!pwd_has_letter || !pwd_has_nonletter)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("password must contain both letters and nonletters")));
 
 #ifdef USE_CRACKLIB
-			/* call cracklib to check password */
-			if (FascistCheck(password, CRACKLIB_DICTPATH))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password is easily cracked")));
+				/* call cracklib to check password */
+				if (FascistCheck(spec->value, CRACKLIB_DICTPATH))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password is easily cracked")));
 #endif
-			break;
+				break;
 
-		default:
-			elog(ERROR, "unrecognized password type: %d", password_type);
-			break;
+			default:
+				elog(ERROR, "unrecognized password type: %d", spec->veriftype);
+				break;
+		}
 	}
 
 	/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7781c56..6d3323b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1360,13 +1360,6 @@
   </para>
 
   <para>
-   Since this catalog contains passwords, it must not be publicly readable.
-   <link linkend="view-pg-roles"><structname>pg_roles</structname></link>
-   is a publicly readable view on
-   <structname>pg_authid</structname> that blanks out the password field.
-  </para>
-
-  <para>
    <xref linkend="user-manag"> contains detailed information about user and
    privilege management.
   </para>
@@ -1469,21 +1462,6 @@
      </row>
 
      <row>
-      <entry><structfield>rolpassword</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>
-       Password (possibly encrypted); null if none.  If the password
-       is encrypted, this column will begin with the string <literal>md5</>
-       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
-       will be of the user's password concatenated to their user name.
-       For example, if user <literal>joe</> has password <literal>xyzzy</>,
-       <productname>PostgreSQL</> will store the md5 hash of
-       <literal>xyzzyjoe</>.  A password that does not follow that
-       format is assumed to be unencrypted.
-      </entry>
-     </row>
-
-     <row>
       <entry><structfield>rolvaliduntil</structfield></entry>
       <entry><type>timestamptz</type></entry>
       <entry>Password expiry time (only used for password authentication);
@@ -1495,6 +1473,77 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-auth-verifiers">
+  <title><structname>pg_auth_verifiers</structname></title>
+
+  <indexterm zone="catalog-pg-auth-verifiers">
+   <primary>pg_auth_verifiers</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_auth_verifiers</structname> contains password
+   information for database authorization identifiers (roles).
+  </para>
+
+  <para>
+   Since this catalog contains passwords, it must not be publicly readable.
+  </para>
+
+  <para>
+   Because user identities are cluster-wide,
+   <structname>pg_auth_verifiers</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_auth_verifiers</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_auth_verifiers</> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>roleid</type></entry>
+      <entry>Role identifier OID</entry>
+     </row>
+
+     <row>
+      <entry><structfield>verimet</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry>
+       <literal>p</> = plain format,
+       <literal>m</> = MD5-encrypted
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>text</structfield></entry>
+      <entry><type>verival</type></entry>
+      <entry>
+       Password (possibly encrypted with format defined in
+       <structfield>verimet</>).  If the password
+       is MD5-encrypted, this column will begin with the string <literal>md5</>
+       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
+       will be of the user's password concatenated to their user name.
+       For example, if user <literal>joe</> has password <literal>xyzzy</>,
+       <productname>PostgreSQL</> will store the md5 hash of
+       <literal>xyzzyjoe</>.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
 
  <sect1 id="catalog-pg-auth-members">
   <title><structname>pg_auth_members</structname></title>
@@ -9865,9 +9914,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
      </row>
 
      <row>
-      <entry><structfield>passwd</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Not the password (always reads as <literal>********</>)</entry>
+      <entry><structfield>verifiers</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry>List of password verifiers listed as method:password.</entry>
      </row>
 
      <row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e900dcc..5ae27f8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,20 +1163,29 @@ include_dir 'conf.d'
      </varlistentry>
 
      <varlistentry id="guc-password-encryption" xreflabel="password_encryption">
-      <term><varname>password_encryption</varname> (<type>boolean</type>)
+      <term><varname>password_encryption</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>password_encryption</> configuration parameter</primary>
       </indexterm>
       </term>
       <listitem>
        <para>
+        Specifies a comma-separated list of password encryption formats.
+        Supported formats are <literal>plain</> and <literal>md5</>.
+       </para>
+
+       <para>
         When a password is specified in <xref
         linkend="sql-createuser"> or
         <xref linkend="sql-alterrole">
         without writing either <literal>ENCRYPTED</> or
-        <literal>UNENCRYPTED</>, this parameter determines whether the
-        password is to be encrypted. The default is <literal>on</>
-        (encrypt the password).
+        <literal>UNENCRYPTED</>, this parameter determines the list of
+        encryption formats this password is to be stored as.
+       </para>
+
+       <para>
+        The default is <literal>md5</> (encrypt the password with MD5
+        encryption).
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index ea26027..a48d188 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -35,6 +35,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
     | BYPASSRLS | NOBYPASSRLS
     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
     | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+    | PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' [, ...] )
     | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
     | IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
     | IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -228,9 +229,9 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
         roles having the <literal>LOGIN</literal> attribute, but you
         can nonetheless define one for roles without it.)  If you do
         not plan to use password authentication you can omit this
-        option.  If no password is specified, the password will be set
-        to null and password authentication will always fail for that
-        user.  A null password can optionally be written explicitly as
+        option.  If no password is specified, no password will be set
+        and password authentication will always fail for that user.
+        A null password can optionally be written explicitly as
         <literal>PASSWORD NULL</literal>.
        </para>
       </listitem>
@@ -262,6 +263,22 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
      </varlistentry>
 
      <varlistentry>
+      <term>PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>'</term>
+      <listitem>
+       <para>
+        Sets the list of password verifiers for the role. Currently only
+        <literal>md5</>, for MD5-encrypted format, and <literal>plain</>
+        for unencrypted format, can be specified as verifier format type
+        for <literal>verifier_type</>. If the password defined with
+        <literal>plain</> type is already in MD5-encrypted format
+        the password will be stored as MD5-encrypted. If the password defined
+        is in plain-format and that <literal>verifier_type</> is set
+        to <literal>md5</>, the password will be stored as MD5-encrypted.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
       <listitem>
        <para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..2e695b8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,8 +35,8 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
 	pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
 	pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
-	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
-	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
+	pg_auth_verifiers.h pg_authid.h pg_auth_members.h pg_shdepend.h \
+	pg_shdescription.h pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 81ccebf..663e51b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -27,6 +27,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
@@ -218,6 +219,7 @@ IsSharedRelation(Oid relationId)
 {
 	/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
 	if (relationId == AuthIdRelationId ||
+		relationId == AuthVerifRelationId ||
 		relationId == AuthMemRelationId ||
 		relationId == DatabaseRelationId ||
 		relationId == PLTemplateRelationId ||
@@ -231,6 +233,8 @@ IsSharedRelation(Oid relationId)
 	/* These are their indexes (see indexing.h) */
 	if (relationId == AuthIdRolnameIndexId ||
 		relationId == AuthIdOidIndexId ||
+		relationId == AuthVerifRoleMethodIndexId ||
+		relationId == AuthVerifMethodRoleIndexId ||
 		relationId == AuthMemRoleMemIndexId ||
 		relationId == AuthMemMemRoleIndexId ||
 		relationId == DatabaseNameIndexId ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c0bd6fa..f9121e6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -32,7 +32,16 @@ CREATE VIEW pg_shadow AS
         rolsuper AS usesuper,
         rolreplication AS userepl,
         rolbypassrls AS usebypassrls,
-        rolpassword AS passwd,
+        ARRAY
+        (
+            SELECT
+                CASE verimet
+                    WHEN 'p' THEN 'plain:' || verival
+                    WHEN 'm' THEN 'md5:' || verival
+                END AS verifiers
+            FROM pg_auth_verifiers
+	    WHERE roleid = pg_authid.oid
+	) AS verifiers,
         rolvaliduntil::abstime AS valuntil,
         setconfig AS useconfig
     FROM pg_authid LEFT JOIN pg_db_role_setting s
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index afbf276..c6bf9db 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -21,9 +21,11 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "commands/seclabel.h"
@@ -42,9 +44,6 @@
 Oid			binary_upgrade_next_pg_authid_oid = InvalidOid;
 
 
-/* GUC parameter */
-extern bool Password_encryption;
-
 /* Hook to check passwords in CreateRole() and AlterRole() */
 check_password_hook_type check_password_hook = NULL;
 
@@ -54,6 +53,10 @@ static void AddRoleMems(const char *rolename, Oid roleid,
 static void DelRoleMems(const char *rolename, Oid roleid,
 			List *memberSpecs, List *memberIds,
 			bool admin_opt);
+static void FlattenPasswordIdentifiers(List *verifiers, char *rolname);
+static void InsertPasswordIdentifiers(Oid roleid, List *verifiers,
+			char *rolname);
+static void DeletePasswordVerifiers(Oid roleid);
 
 
 /* Check if current user has createrole privileges */
@@ -78,9 +81,7 @@ CreateRole(CreateRoleStmt *stmt)
 	Oid			roleid;
 	ListCell   *item;
 	ListCell   *option;
-	char	   *password = NULL;	/* user password */
-	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	List	   *passwordVerifiers = NIL;	/* password verifiers */
 	bool		issuper = false;	/* Make the user a superuser? */
 	bool		inherit = true; /* Auto inherit privileges? */
 	bool		createrole = false;		/* Can this user create roles? */
@@ -96,7 +97,7 @@ CreateRole(CreateRoleStmt *stmt)
 	char	   *validUntil = NULL;		/* time the login is valid until */
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
-	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordVerifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -128,19 +129,13 @@ CreateRole(CreateRoleStmt *stmt)
 	{
 		DefElem    *defel = (DefElem *) lfirst(option);
 
-		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+		if (strcmp(defel->defname, "passwordVerifiers") == 0)
 		{
-			if (dpassword)
+			if (dpasswordVerifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
-			dpassword = defel;
-			if (strcmp(defel->defname, "encryptedPassword") == 0)
-				encrypt_password = true;
-			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
-				encrypt_password = false;
+			dpasswordVerifiers = defel;
 		}
 		else if (strcmp(defel->defname, "sysid") == 0)
 		{
@@ -248,8 +243,8 @@ CreateRole(CreateRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
+	if (dpasswordVerifiers && dpasswordVerifiers->arg)
+		passwordVerifiers = (List *) dpasswordVerifiers->arg;
 	if (dissuper)
 		issuper = intVal(dissuper->arg) != 0;
 	if (dinherit)
@@ -340,12 +335,16 @@ CreateRole(CreateRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Flatten list of password verifiers.
+	 */
+	FlattenPasswordIdentifiers(passwordVerifiers, stmt->role);
+
+	/*
+	 * Call the password checking hook if there is one defined.
 	 */
-	if (check_password_hook && password)
+	if (check_password_hook && passwordVerifiers != NIL)
 		(*check_password_hook) (stmt->role,
-								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								passwordVerifiers,
 								validUntil_datum,
 								validUntil_null);
 
@@ -365,24 +364,6 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
 	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-	}
-	else
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
@@ -411,6 +392,10 @@ CreateRole(CreateRoleStmt *stmt)
 	roleid = simple_heap_insert(pg_authid_rel, tuple);
 	CatalogUpdateIndexes(pg_authid_rel, tuple);
 
+	/* store password verifiers */
+	if (passwordVerifiers)
+		InsertPasswordIdentifiers(roleid, passwordVerifiers, stmt->role);
+
 	/*
 	 * Advance command counter so we can see new record; else tests in
 	 * AddRoleMems may fail.
@@ -479,9 +464,7 @@ AlterRole(AlterRoleStmt *stmt)
 	Form_pg_authid authform;
 	ListCell   *option;
 	char	   *rolename = NULL;
-	char	   *password = NULL;	/* user password */
-	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	List	   *passwordVerifiers = NIL;	/* password verifiers */
 	int			issuper = -1;	/* Make the user a superuser? */
 	int			inherit = -1;	/* Auto inherit privileges? */
 	int			createrole = -1;	/* Can this user create roles? */
@@ -494,7 +477,7 @@ AlterRole(AlterRoleStmt *stmt)
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
 	bool		bypassrls = -1;
-	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordVerifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -512,19 +495,13 @@ AlterRole(AlterRoleStmt *stmt)
 	{
 		DefElem    *defel = (DefElem *) lfirst(option);
 
-		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+		if (strcmp(defel->defname, "passwordVerifiers") == 0)
 		{
-			if (dpassword)
+			if (dpasswordVerifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
-			dpassword = defel;
-			if (strcmp(defel->defname, "encryptedPassword") == 0)
-				encrypt_password = true;
-			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
-				encrypt_password = false;
+			dpasswordVerifiers = defel;
 		}
 		else if (strcmp(defel->defname, "superuser") == 0)
 		{
@@ -612,8 +589,8 @@ AlterRole(AlterRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
+	if (dpasswordVerifiers && dpasswordVerifiers->arg)
+		passwordVerifiers = (List *) dpasswordVerifiers->arg;
 	if (dissuper)
 		issuper = intVal(dissuper->arg);
 	if (dinherit)
@@ -687,7 +664,7 @@ AlterRole(AlterRoleStmt *stmt)
 			  !dconnlimit &&
 			  !rolemembers &&
 			  !validUntil &&
-			  dpassword &&
+			  dpasswordVerifiers &&
 			  roleid == GetUserId()))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -712,12 +689,16 @@ AlterRole(AlterRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Flatten list of password verifiers.
 	 */
-	if (check_password_hook && password)
+	FlattenPasswordIdentifiers(passwordVerifiers, rolename);
+
+	/*
+	 * Call the password checking hook if there is one defined.
+	 */
+	if (check_password_hook && passwordVerifiers)
 		(*check_password_hook) (rolename,
-								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								passwordVerifiers,
 								validUntil_datum,
 								validUntil_null);
 
@@ -773,30 +754,6 @@ AlterRole(AlterRoleStmt *stmt)
 		new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
 	}
 
-	/* password */
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, rolename, strlen(rolename),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
-	/* unset password */
-	if (dpassword && dpassword->arg == NULL)
-	{
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
 	/* valid until */
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -821,6 +778,21 @@ AlterRole(AlterRoleStmt *stmt)
 	heap_freetuple(new_tuple);
 
 	/*
+	 * Update password verifiers. The old entries are completely cleaned up
+	 * and are updated by what is wanted through this command.
+	 */
+	if (passwordVerifiers)
+	{
+		DeletePasswordVerifiers(roleid);
+		CommandCounterIncrement();
+		InsertPasswordIdentifiers(roleid, passwordVerifiers, rolename);
+	}
+
+	/* remove password verifiers */
+	if (dpasswordVerifiers && dpasswordVerifiers->arg == NULL)
+		DeletePasswordVerifiers(roleid);
+
+	/*
 	 * Advance command counter so we can see new record; else tests in
 	 * AddRoleMems may fail.
 	 */
@@ -1070,8 +1042,10 @@ DropRole(DropRoleStmt *stmt)
 		systable_endscan(sscan);
 
 		/*
-		 * Remove any comments or security labels on this role.
+		 * Remove any comments, password verifiers or security labels on this
+		 * role.
 		 */
+		DeletePasswordVerifiers(roleid);
 		DeleteSharedComments(roleid, AuthIdRelationId);
 		DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
 
@@ -1106,11 +1080,11 @@ ObjectAddress
 RenameRole(const char *oldname, const char *newname)
 {
 	HeapTuple	oldtuple,
-				newtuple;
+				newtuple,
+				authtuple;
 	TupleDesc	dsc;
-	Relation	rel;
-	Datum		datum;
-	bool		isnull;
+	Relation	pg_authid_rel,
+				pg_auth_verifiers_rel;
 	Datum		repl_val[Natts_pg_authid];
 	bool		repl_null[Natts_pg_authid];
 	bool		repl_repl[Natts_pg_authid];
@@ -1118,8 +1092,10 @@ RenameRole(const char *oldname, const char *newname)
 	Oid			roleid;
 	ObjectAddress address;
 
-	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
-	dsc = RelationGetDescr(rel);
+	pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+
+	dsc = RelationGetDescr(pg_authid_rel);
 
 	oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
 	if (!HeapTupleIsValid(oldtuple))
@@ -1179,22 +1155,10 @@ RenameRole(const char *oldname, const char *newname)
 												   CStringGetDatum(newname));
 	repl_null[Anum_pg_authid_rolname - 1] = false;
 
-	datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
-
-	if (!isnull && isMD5(TextDatumGetCString(datum)))
-	{
-		/* MD5 uses the username as salt, so just clear it on a rename */
-		repl_repl[Anum_pg_authid_rolpassword - 1] = true;
-		repl_null[Anum_pg_authid_rolpassword - 1] = true;
-
-		ereport(NOTICE,
-				(errmsg("MD5 password cleared because of role rename")));
-	}
-
 	newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
-	simple_heap_update(rel, &oldtuple->t_self, newtuple);
+	simple_heap_update(pg_authid_rel, &oldtuple->t_self, newtuple);
 
-	CatalogUpdateIndexes(rel, newtuple);
+	CatalogUpdateIndexes(pg_authid_rel, newtuple);
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
@@ -1202,10 +1166,23 @@ RenameRole(const char *oldname, const char *newname)
 
 	ReleaseSysCache(oldtuple);
 
+	/* look for md5 entry in pg_auth_verifiers and remove it if it exists */
+	authtuple = SearchSysCache2(AUTHVERIFROLEMETH,
+								ObjectIdGetDatum(roleid),
+								CharGetDatum(AUTH_VERIFIER_MD5));
+	if (HeapTupleIsValid(authtuple))
+	{
+		simple_heap_delete(pg_auth_verifiers_rel, &authtuple->t_self);
+		ereport(NOTICE,
+				(errmsg("MD5 password cleared because of role rename")));
+		ReleaseSysCache(authtuple);
+	}
+
 	/*
-	 * Close pg_authid, but keep lock till commit.
+	 * Close pg_authid and pg_auth_verifiers, but keep lock till commit.
 	 */
-	heap_close(rel, NoLock);
+	heap_close(pg_auth_verifiers_rel, NoLock);
+	heap_close(pg_authid_rel, NoLock);
 
 	return address;
 }
@@ -1611,3 +1588,127 @@ DelRoleMems(const char *rolename, Oid roleid,
 	 */
 	heap_close(pg_authmem_rel, NoLock);
 }
+
+/*
+ * FlattenPasswordIdentifiers
+ * Make list of password verifier types and values consistent with input.
+ */
+static void
+FlattenPasswordIdentifiers(List *verifiers, char *rolname)
+{
+	ListCell   *l;
+
+	foreach(l, verifiers)
+	{
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		if (spec->value == NULL)
+			continue;
+
+		/*
+		 * Check if given value for an MD5 verifier is adapted and
+		 * do conversion as needed. If an MD5 password is provided but
+		 * that the verifier has a plain format switch type of verifier
+		 * accordingly.
+		 */
+		if (spec->veriftype == AUTH_VERIFIER_MD5 &&
+			!isMD5(spec->value))
+		{
+			char encrypted_passwd[MD5_PASSWD_LEN + 1];
+			if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
+								encrypted_passwd))
+				elog(ERROR, "password encryption failed");
+			spec->value = pstrdup(encrypted_passwd);
+		}
+		else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
+				 isMD5(spec->value))
+			spec->veriftype = AUTH_VERIFIER_MD5;
+	}
+}
+
+/*
+ * InsertPasswordIdentifiers
+ * Add list of given identifiers into pg_auth_verifiers for given role.
+ */
+static void
+InsertPasswordIdentifiers(Oid roleid, List *verifiers, char *rolname)
+{
+	ListCell   *l;
+	Relation	pg_auth_verifiers_rel;
+	TupleDesc	pg_auth_verifiers_dsc;
+
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+	pg_auth_verifiers_dsc = RelationGetDescr(pg_auth_verifiers_rel);
+
+	foreach(l, verifiers)
+	{
+		Datum		new_record[Natts_pg_auth_verifiers];
+		bool		new_record_nulls[Natts_pg_auth_verifiers];
+		HeapTuple	tuple;
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		/* Move on if no verifier value define */
+		if (spec->value == NULL)
+			continue;
+
+		/* Build tuple and insert it */
+		MemSet(new_record, 0, sizeof(new_record));
+		MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+		new_record[Anum_pg_auth_verifiers_roleid - 1] = ObjectIdGetDatum(roleid);
+		new_record[Anum_pg_auth_verifiers_method - 1] =
+			CharGetDatum(spec->veriftype);
+
+		new_record[Anum_pg_auth_verifiers_value - 1] =
+			CStringGetTextDatum(spec->value);
+
+		tuple = heap_form_tuple(pg_auth_verifiers_dsc,
+								new_record, new_record_nulls);
+
+		simple_heap_insert(pg_auth_verifiers_rel, tuple);
+		CatalogUpdateIndexes(pg_auth_verifiers_rel, tuple);
+
+		/* CCI after each change */
+		CommandCounterIncrement();
+	}
+
+	/* Keep locks until the end of transaction */
+	heap_close(pg_auth_verifiers_rel, NoLock);
+}
+
+/*
+ * DeletePasswordVerifiers
+ * Remove all password identifiers for given role.
+ */
+static void
+DeletePasswordVerifiers(Oid roleid)
+{
+	Relation	pg_auth_verifiers_rel;
+	ScanKeyData scankey;
+	SysScanDesc	sscan;
+	HeapTuple	tmp_tuple;
+
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+	/*
+	 * Remove role entries from pg_auth_verifiers table. All the tuples that
+	 * are similar to the role dropped need to be removed.
+	 */
+	ScanKeyInit(&scankey,
+				Anum_pg_auth_verifiers_roleid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(roleid));
+
+	sscan = systable_beginscan(pg_auth_verifiers_rel,
+							   AuthVerifRoleMethodIndexId,
+							   true, NULL, 1, &scankey);
+
+	while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(pg_auth_verifiers_rel, &tmp_tuple->t_self);
+	}
+
+	systable_endscan(sscan);
+
+	/* keep lock until the end of transaction */
+	heap_close(pg_auth_verifiers_rel, NoLock);
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 97be944..f04d17a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,14 +20,46 @@
 #include <crypt.h>
 #endif
 
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_type.h"
 #include "libpq/crypt.h"
 #include "libpq/md5.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+/*
+ * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * method.
+ */
+static char *
+get_role_verifier(Oid roleid, const char method)
+{
+	HeapTuple	tuple;
+	Datum		verifier_datum;
+	char	   *verifier;
+	bool		isnull;
+
+	/* Now attempt to grab the verifier value */
+	tuple = SearchSysCache2(AUTHVERIFROLEMETH,
+							ObjectIdGetDatum(roleid),
+							CharGetDatum(method));
+	if (!HeapTupleIsValid(tuple))
+		return NULL; /* no verifier available */
+
+	verifier_datum = SysCacheGetAttr(AUTHVERIFROLEMETH, tuple,
+							   Anum_pg_auth_verifiers_value,
+							   &isnull);
+	verifier = TextDatumGetCString(verifier_datum);
+
+	ReleaseSysCache(tuple);
+
+	return verifier;
+}
 
 /*
  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,29 +71,39 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				 char **logdetail)
 {
 	int			retval = STATUS_ERROR;
-	char	   *shadow_pass,
+	char	   *verifier,
 			   *crypt_pwd;
 	TimestampTz vuntil = 0;
+	bool		verifier_is_md5;
 	char	   *crypt_client_pass = client_pass;
 	HeapTuple	roleTup;
 	Datum		datum;
 	bool		isnull;
+	Oid			roleid;
 
 	/* Get role info from pg_authid */
 	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
 	if (!HeapTupleIsValid(roleTup))
 		return STATUS_ERROR;	/* no such user */
 
-	datum = SysCacheGetAttr(AUTHNAME, roleTup,
-							Anum_pg_authid_rolpassword, &isnull);
-	if (isnull)
+	verifier_is_md5 = true;
+
+	roleid = HeapTupleGetOid(roleTup);
+	verifier = get_role_verifier(roleid, AUTH_VERIFIER_MD5);
+	if (verifier == NULL)
 	{
-		ReleaseSysCache(roleTup);
-		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
-							  role);
-		return STATUS_ERROR;	/* user has no password */
+		/* we can also use a plaintext password, by creating the hash from it */
+		verifier_is_md5 = false;
+		verifier = get_role_verifier(roleid, AUTH_VERIFIER_PLAIN);
+
+		if (verifier == NULL)
+		{
+			*logdetail = psprintf(_("User \"%s\" has no password assigned for authentication method \"%s\"."),
+								  role, "md5");
+			ReleaseSysCache(roleTup);
+			return STATUS_ERROR;
+		}
 	}
-	shadow_pass = TextDatumGetCString(datum);
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
 							Anum_pg_authid_rolvaliduntil, &isnull);
@@ -70,7 +112,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 
 	ReleaseSysCache(roleTup);
 
-	if (*shadow_pass == '\0')
+	if (*verifier == '\0')
 		return STATUS_ERROR;	/* empty password */
 
 	CHECK_FOR_INTERRUPTS();
@@ -83,10 +125,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 	{
 		case uaMD5:
 			crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* stored password already encrypted, only do salt */
-				if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+				if (!pg_md5_encrypt(verifier + strlen("md5"),
 									port->md5Salt,
 									sizeof(port->md5Salt), crypt_pwd))
 				{
@@ -99,7 +141,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				/* stored password is plain, double-encrypt */
 				char	   *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
 
-				if (!pg_md5_encrypt(shadow_pass,
+				if (!pg_md5_encrypt(verifier,
 									port->user_name,
 									strlen(port->user_name),
 									crypt_pwd2))
@@ -121,7 +163,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 			}
 			break;
 		default:
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* Encrypt user-supplied password to match stored MD5 */
 				crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
@@ -134,7 +176,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 					return STATUS_ERROR;
 				}
 			}
-			crypt_pwd = shadow_pass;
+			crypt_pwd = verifier;
 			break;
 	}
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c8425d..6034dac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2677,6 +2677,17 @@ _copyRoleSpec(const RoleSpec *from)
 	return newnode;
 }
 
+static AuthVerifierSpec *
+_copyAuthVerifierSpec(const AuthVerifierSpec *from)
+{
+	AuthVerifierSpec *newnode = makeNode(AuthVerifierSpec);
+
+	COPY_SCALAR_FIELD(veriftype);
+	COPY_STRING_FIELD(value);
+
+	return newnode;
+}
+
 static Query *
 _copyQuery(const Query *from)
 {
@@ -4964,6 +4975,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_AuthVerifierSpec:
+			retval = _copyAuthVerifierSpec(from);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1d6c43c..935b8dd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2581,6 +2581,15 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalAuthVerifierSpec(const AuthVerifierSpec *a, const AuthVerifierSpec *b)
+{
+	COMPARE_SCALAR_FIELD(veriftype);
+	COMPARE_STRING_FIELD(value);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3321,6 +3330,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_AuthVerifierSpec:
+			retval = _equalAuthVerifierSpec(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6b02cec..5b080c2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,15 +51,18 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
+#include "commands/user.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/gramparse.h"
 #include "parser/parser.h"
 #include "parser/parse_expr.h"
 #include "storage/lmgr.h"
+#include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/datetime.h"
 #include "utils/numeric.h"
@@ -145,6 +148,7 @@ static Node *makeNullAConst(int location);
 static Node *makeAConst(Value *v, int location);
 static Node *makeBoolAConst(bool state, int location);
 static Node *makeRoleSpec(RoleSpecType type, int location);
+static Node *makeAuthVerifierSpec(char type, char *password);
 static void check_qualified_name(List *names, core_yyscan_t yyscanner);
 static List *check_func_name(List *names, core_yyscan_t yyscanner);
 static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -370,6 +374,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				create_generic_options alter_generic_options
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
+				auth_verifier_list
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -497,6 +502,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		createdb_opt_name
 %type <node>	var_value zone_value
 %type <node>	auth_ident RoleSpec opt_granted_by
+%type <node>	AuthVerifierSpec
 
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
@@ -638,7 +644,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERIFIERS VERSION_P VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -919,25 +925,86 @@ AlterOptRoleList:
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
+auth_verifier_list:
+			AuthVerifierSpec
+				{ $$ = list_make1((Node*)$1); }
+			| auth_verifier_list ',' AuthVerifierSpec
+				{ $$ = lappend($1, (Node *)$3); }
+
+AuthVerifierSpec:
+			NonReservedWord '=' Sconst
+				{
+					char	type;
+
+					if (strcmp($1, "md5") == 0)
+						type = AUTH_VERIFIER_MD5;
+					else if (strcmp($1, "plain") == 0)
+						type = AUTH_VERIFIER_PLAIN;
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized authorization verifier option \"%s\"", $1),
+									 parser_errposition(@1)));
+					$$ = (Node *) makeAuthVerifierSpec(type, $3);
+				}
+
 AlterOptRoleElem:
 			PASSWORD Sconst
 				{
-					$$ = makeDefElem("password",
-									 (Node *)makeString($2));
+					char	   *rawstring = pstrdup(Password_encryption);
+					List	   *elemlist;
+					ListCell   *l;
+					List	   *result = NIL;
+
+					if (!SplitIdentifierString(rawstring, ',', &elemlist))
+						Assert(false); /* should not happen */
+
+					foreach(l, elemlist)
+					{
+						char	   *meth_name = (char *) lfirst(l);
+						char		veriftype;
+						AuthVerifierSpec *n;
+
+						if (strcmp(meth_name, "md5") == 0)
+							veriftype = AUTH_VERIFIER_MD5;
+						else if (strcmp(meth_name, "plain") == 0)
+							veriftype = AUTH_VERIFIER_PLAIN;
+						else
+							Assert(false);	/* should not happen */
+						n = (AuthVerifierSpec *)
+							makeAuthVerifierSpec(veriftype, $2);
+						result = lappend(result, (Node *)n);
+					}
+					pfree(rawstring);
+					list_free(elemlist);
+
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *) result);
 				}
 			| PASSWORD NULL_P
 				{
-					$$ = makeDefElem("password", NULL);
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, NULL);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
 				}
 			| ENCRYPTED PASSWORD Sconst
 				{
-					$$ = makeDefElem("encryptedPassword",
-									 (Node *)makeString($3));
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_MD5, $3);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
 				}
 			| UNENCRYPTED PASSWORD Sconst
 				{
-					$$ = makeDefElem("unencryptedPassword",
-									 (Node *)makeString($3));
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, $3);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
+				}
+			| PASSWORD VERIFIERS '(' auth_verifier_list ')'
+				{
+					$$ = makeDefElem("passwordVerifiers", (Node *)$4);
 				}
 			| INHERIT
 				{
@@ -13878,6 +13945,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARYING
+			| VERIFIERS
 			| VERSION_P
 			| VIEW
 			| VIEWS
@@ -14272,6 +14340,20 @@ makeRoleSpec(RoleSpecType type, int location)
 	return (Node *) spec;
 }
 
+/* makeAuthVerifierSpec
+ * Create a AuthVerifierSpec for the given type.
+ */
+static Node *
+makeAuthVerifierSpec(char type, char *password)
+{
+	AuthVerifierSpec *spec = makeNode(AuthVerifierSpec);
+
+	spec->veriftype = type;
+	spec->value = password;
+
+	return (Node *) spec;
+}
+
 /* check_qualified_name --- check the result of qualified_name production
  *
  * It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 577c059..9befb84 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1073,6 +1073,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
 		case AUTHNAME:
 		case AUTHOID:
 		case AUTHMEMMEMROLE:
+		case AUTHVERIFROLEMETH:
 
 			/*
 			 * Protect authentication lookups occurring before relcache has
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 44e9509..0990e71 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
@@ -97,6 +98,7 @@ static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type
 static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
 static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
 static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
+static const FormData_pg_attribute Desc_pg_auth_verifiers[Natts_pg_auth_verifiers] = {Schema_pg_auth_verifiers};
 static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
 
 /*
@@ -1539,7 +1541,7 @@ LookupOpclassInfo(Oid operatorClassOid,
  *		catalogs.
  *
  * formrdesc is currently used for: pg_database, pg_authid, pg_auth_members,
- * pg_class, pg_attribute, pg_proc, and pg_type
+ * pg_auth_verifiers, pg_class, pg_attribute, pg_proc, and pg_type
  * (see RelationCacheInitializePhase2/3).
  *
  * Note that these catalogs can't have constraints (except attnotnull),
@@ -2816,6 +2818,7 @@ RelationBuildLocalRelation(const char *relname,
 		case DatabaseRelationId:
 		case AuthIdRelationId:
 		case AuthMemRelationId:
+		case AuthVerifRelationId:
 		case RelationRelationId:
 		case AttributeRelationId:
 		case ProcedureRelationId:
@@ -3202,8 +3205,10 @@ RelationCacheInitializePhase2(void)
 				  true, Natts_pg_authid, Desc_pg_authid);
 		formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
 				  false, Natts_pg_auth_members, Desc_pg_auth_members);
+		formrdesc("pg_auth_verifiers", AuthVerifRelation_Rowtype_Id, true,
+				  false, Natts_pg_auth_verifiers, Desc_pg_auth_verifiers);
 
-#define NUM_CRITICAL_SHARED_RELS	3	/* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS	4	/* fix if you change list above */
 	}
 
 	MemoryContextSwitchTo(oldcxt);
@@ -3323,8 +3328,8 @@ RelationCacheInitializePhase3(void)
 	 * initial lookup of MyDatabaseId, without which we'll never find any
 	 * non-shared catalogs at all.  Autovacuum calls InitPostgres with a
 	 * database OID, so it instead depends on DatabaseOidIndexId.  We also
-	 * need to nail up some indexes on pg_authid and pg_auth_members for use
-	 * during client authentication.
+	 * need to nail up some indexes on pg_authid, pg_auth_verifiers and
+	 * pg_auth_members for use during client authentication.
 	 */
 	if (!criticalSharedRelcachesBuilt)
 	{
@@ -3338,8 +3343,10 @@ RelationCacheInitializePhase3(void)
 							AuthIdRelationId);
 		load_critical_index(AuthMemMemRoleIndexId,
 							AuthMemRelationId);
+		load_critical_index(AuthVerifRoleMethodIndexId,
+							AuthVerifRelationId);
 
-#define NUM_CRITICAL_SHARED_INDEXES 5	/* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 6	/* fix if you change list above */
 
 		criticalSharedRelcachesBuilt = true;
 	}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index efce7b9..5294558 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
@@ -247,6 +248,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{AuthVerifRelationId,		/* AUTHVERIFMETHROLE */
+	 AuthVerifMethodRoleIndexId,
+		2,
+		{
+			Anum_pg_auth_verifiers_method,
+			Anum_pg_auth_verifiers_roleid,
+			0,
+			0
+		},
+		4
+	},
+	{AuthVerifRelationId,		/* AUTHVERIFROLEMETH */
+	 AuthVerifRoleMethodIndexId,
+		2,
+		{
+			Anum_pg_auth_verifiers_roleid,
+			Anum_pg_auth_verifiers_method,
+			0,
+			0
+		},
+		4
+	},
 	{
 		CastRelationId,			/* CASTSOURCETARGET */
 		CastSourceTargetIndexId,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b3dac51..082108e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -179,6 +179,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
 static bool check_application_name(char **newval, void **extra, GucSource source);
 static void assign_application_name(const char *newval, void *extra);
 static bool check_cluster_name(char **newval, void **extra, GucSource source);
+static bool check_password_encryption(char **newval, void **extra,
+									  GucSource source);
 static const char *show_unix_socket_permissions(void);
 static const char *show_log_file_mode(void);
 
@@ -425,8 +427,6 @@ bool		check_function_bodies = true;
 bool		default_with_oids = false;
 bool		SQL_inheritance = true;
 
-bool		Password_encryption = true;
-
 int			log_min_error_statement = ERROR;
 int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
@@ -438,6 +438,8 @@ int			temp_file_limit = -1;
 
 int			num_temp_buffers = 1024;
 
+char	   *Password_encryption;
+
 char	   *cluster_name = "";
 char	   *ConfigFileName;
 char	   *HbaFileName;
@@ -1304,17 +1306,6 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
-			gettext_noop("Encrypt passwords."),
-			gettext_noop("When a password is specified in CREATE USER or "
-			   "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
-						 "this parameter determines whether the password is to be encrypted.")
-		},
-		&Password_encryption,
-		true,
-		NULL, NULL, NULL
-	},
-	{
 		{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
 			gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
 			gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3262,6 +3253,19 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
+		{"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+			gettext_noop("List of password encryption methods."),
+			gettext_noop("When a password is specified in CREATE USER or "
+				"ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+				"this parameter determines how the password is to be encrypted."),
+			GUC_LIST_INPUT
+		},
+		&Password_encryption,
+		"md5",
+		check_password_encryption, NULL, NULL
+	},
+
+	{
 		{"ssl_cert_file", PGC_POSTMASTER, CONN_AUTH_SECURITY,
 			gettext_noop("Location of the SSL server certificate file."),
 			NULL
@@ -10116,6 +10120,41 @@ check_cluster_name(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+static bool
+check_password_encryption(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring = pstrdup(*newval);	/* get copy of list string */
+	List	   *elemlist;
+	ListCell   *l;
+
+	if (!SplitIdentifierString(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		pfree(rawstring);
+		list_free(elemlist);
+		Assert(false);
+		return false;	/* GUC machinery should have already complained */
+	}
+
+	/* Check that only supported formats are listed */
+	foreach(l, elemlist)
+	{
+		char	   *encryption_name = (char *) lfirst(l);
+
+		if (strcmp(encryption_name, "md5") != 0 &&
+			strcmp(encryption_name, "plain") != 0)
+		{
+			pfree(rawstring);
+			list_free(elemlist);
+			return false;
+		}
+	}
+
+	pfree(rawstring);
+	list_free(elemlist);
+	return true;
+}
+
 static const char *
 show_unix_socket_permissions(void)
 {
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e5d275d..bc0efbf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -87,7 +87,7 @@
 #ssl_key_file = 'server.key'		# (change requires restart)
 #ssl_ca_file = ''			# (change requires restart)
 #ssl_crl_file = ''			# (change requires restart)
-#password_encryption = on
+#password_encryption = 'md5'
 #db_user_namespace = off
 #row_security = on
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index feeff9e..9ae22bc 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1551,10 +1551,11 @@ setup_auth(void)
 	const char **line;
 	static const char *pg_authid_setup[] = {
 		/*
-		 * The authid table shouldn't be readable except through views, to
-		 * ensure passwords are not publicly visible.
+		 * The authorization tables shouldn't be readable except through
+		 * views, to ensure password data are not publicly visible.
 		 */
 		"REVOKE ALL on pg_authid FROM public;\n",
+		"REVOKE ALL on pg_auth_verifiers FROM public;\n",
 		NULL
 	};
 
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c4b6ae8..b4d09e5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -663,8 +663,22 @@ dumpRoles(PGconn *conn)
 				i_is_current_user;
 	int			i;
 
-	/* note: rolconfig is dumped later */
-	if (server_version >= 90500)
+	/*
+	 * Note: rolconfig is dumped later. In 9.6 and above, password
+	 * information is dumped later on.
+	 */
+	if (server_version >= 90600)
+		printfPQExpBuffer(buf,
+						  "SELECT oid, rolname, rolsuper, rolinherit, "
+						  "rolcreaterole, rolcreatedb, "
+						  "rolcanlogin, rolconnlimit, "
+						  "null::text as rolpassword, "
+						  "rolvaliduntil, rolreplication, rolbypassrls, "
+			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+						  "rolname = current_user AS is_current_user "
+						  "FROM pg_authid "
+						  "ORDER BY 2");
+	else if (server_version >= 90500)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
 						  "rolcreaterole, rolcreatedb, "
@@ -869,6 +883,65 @@ dumpRoles(PGconn *conn)
 
 	PQclear(res);
 
+	/*
+	 * Dump password configuration for all roles.
+	 */
+	if (server_version >= 90600)
+	{
+		char   *current_user = NULL;
+		bool	first_elt = true;
+		res = executeQuery(conn,
+						   "SELECT a.rolname, v.verimet, v.verival "
+						   "FROM pg_auth_verifiers AS v "
+						   "LEFT JOIN pg_authid AS a ON (v.roleid = a.oid) "
+						   "ORDER BY rolname;");
+
+		for (i = 0; i < PQntuples(res); i++)
+		{
+			char   *user_name = PQgetvalue(res, i, 0);
+			char	verifier_meth = *PQgetvalue(res, i, 1);
+			char   *verifier_value = PQgetvalue(res, i, 2);
+
+			/* Switch to new ALTER ROLE query when a different user is found */
+			if (current_user == NULL ||
+				strcmp(user_name, current_user) != 0)
+			{
+				/* Finish last query */
+				if (current_user != NULL)
+				{
+					appendPQExpBufferStr(buf, ");\n");
+					fprintf(OPF, "%s", buf->data);
+				}
+
+				resetPQExpBuffer(buf);
+
+				if (current_user)
+					pg_free(current_user);
+				current_user = pg_strdup(user_name);
+				first_elt = true;
+				appendPQExpBuffer(buf, "ALTER ROLE %s PASSWORD VERIFIERS (",
+					current_user);
+			}
+
+			if (first_elt)
+				first_elt = false;
+			else
+				appendPQExpBufferStr(buf, ", ");
+
+			if (verifier_meth == 'm')
+				appendPQExpBufferStr(buf, "md5 = ");
+			else if (verifier_meth == 'p')
+				appendPQExpBufferStr(buf, "plain = ");
+			appendStringLiteralConn(buf, verifier_value, conn);
+		}
+		if (current_user != NULL)
+		{
+			appendPQExpBufferStr(buf, ");\n");
+			fprintf(OPF, "%s", buf->data);
+		}
+	}
+
+
 	fprintf(OPF, "\n\n");
 
 	destroyPQExpBuffer(buf);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index c38958d..cd262a2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -97,6 +97,11 @@ DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, on pg_auth_members
 DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
 #define AuthMemMemRoleIndexId	2695
 
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_role_method_index, 3315, on pg_auth_verifiers using btree(roleid oid_ops, verimet char_ops));
+#define AuthVerifRoleMethodIndexId	3315
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_method_role_index, 3316, on pg_auth_verifiers using btree(verimet char_ops, roleid oid_ops));
+#define AuthVerifMethodRoleIndexId	3316
+
 DECLARE_UNIQUE_INDEX(pg_cast_oid_index, 2660, on pg_cast using btree(oid oid_ops));
 #define CastOidIndexId	2660
 DECLARE_UNIQUE_INDEX(pg_cast_source_target_index, 2661, on pg_cast using btree(castsource oid_ops, casttarget oid_ops));
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
new file mode 100644
index 0000000..daef049
--- /dev/null
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_verifiers.h
+ *	  definition of the system "authorization password hashes" relation
+ *	  (pg_auth_verifiers) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_auth_verifiers.h
+ *
+ * NOTES
+ *	  the genbki.pl script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_VERIFIERS_H
+#define PG_AUTH_VERIFIERS_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_auth_verifiers definition.  cpp turns this into
+ *		typedef struct FormData_pg_auth_verifiers
+ * ----------------
+ */
+#define AuthVerifRelationId	3300
+#define AuthVerifRelation_Rowtype_Id	3308
+
+CATALOG(pg_auth_verifiers,3300) BKI_SHARED_RELATION BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(3308) BKI_SCHEMA_MACRO
+{
+	Oid			roleid;					/* ID of the role using this hash */
+	char		verimet;				/* Method used to generate the hash *
+										 * See AUTH_VERIFIER_xxx below */
+
+#ifdef CATALOG_VARLEN					/* variable-length fields start here */
+	text verival BKI_FORCE_NOT_NULL;	/* Hash value */
+#endif
+} FormData_pg_auth_verifiers;
+
+/* ----------------
+ *		Form_pg_auth_verifiers corresponds to a pointer to a tuple with
+ *		the format of pg_auth_verifiers relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
+
+/* ----------------
+ *		compiler constants for pg_auth_verifiers
+ * ----------------
+ */
+#define Natts_pg_auth_verifiers				3
+#define Anum_pg_auth_verifiers_roleid		1
+#define Anum_pg_auth_verifiers_method		2
+#define Anum_pg_auth_verifiers_value		3
+
+#define AUTH_VERIFIER_PLAIN	'p'		/* plain verifier */
+#define AUTH_VERIFIER_MD5	'm'		/* md5 verifier */
+
+#endif   /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index d5f19d6..623dc1d 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,6 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
 
 	/* remaining fields may be null; use heap_getattr to read them! */
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
-	text		rolpassword;	/* password, if any */
 	timestamptz rolvaliduntil;	/* password expiration time, if any */
 #endif
 } FormData_pg_authid;
@@ -75,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  *		compiler constants for pg_authid
  * ----------------
  */
-#define Natts_pg_authid					11
+#define Natts_pg_authid					10
 #define Anum_pg_authid_rolname			1
 #define Anum_pg_authid_rolsuper			2
 #define Anum_pg_authid_rolinherit		3
@@ -85,8 +84,7 @@ typedef FormData_pg_authid *Form_pg_authid;
 #define Anum_pg_authid_rolreplication	7
 #define Anum_pg_authid_rolbypassrls		8
 #define Anum_pg_authid_rolconnlimit		9
-#define Anum_pg_authid_rolpassword		10
-#define Anum_pg_authid_rolvaliduntil	11
+#define Anum_pg_authid_rolvaliduntil	10
 
 /* ----------------
  *		initial contents of pg_authid
@@ -95,7 +93,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  * user choices.
  * ----------------
  */
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_));
 
 #define BOOTSTRAP_SUPERUSERID 10
 
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..636e8ac 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,12 +14,13 @@
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
+/* GUC parameter */
+extern char *Password_encryption;
 
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT		0
-#define PASSWORD_TYPE_MD5			1
-
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username,
+								List *passwordVerifiers,
+								Datum validuntil_time,
+								bool validuntil_null);
 
 extern PGDLLIMPORT check_password_hook_type check_password_hook;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 748e434..b060ce8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -427,6 +427,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_AuthVerifierSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151c93a..6ac716d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -309,6 +309,17 @@ typedef struct RoleSpec
 } RoleSpec;
 
 /*
+ * AuthVerifierSpec - a password verifier with a some dedicated values.
+ */
+typedef struct AuthVerifierSpec
+{
+	NodeTag		type;
+	char		veriftype;		/* type of this verifier, as listed in *
+								 * pg_auth_verifiers.h */
+	char	   *value;			/* value specified by user */
+} AuthVerifierSpec;
+
+/*
  * FuncCall - a function or aggregate invocation
  *
  * agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)', or if
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 2414069..c8201b4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -415,6 +415,7 @@ PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("verifiers", VERIFIERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 18404e2..0d6c29e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,8 @@ enum SysCacheIdentifier
 	AUTHMEMROLEMEM,
 	AUTHNAME,
 	AUTHOID,
+	AUTHVERIFMETHROLE,
+	AUTHVERIFROLEMETH,
 	CASTSOURCETARGET,
 	CLAAMNAMENSP,
 	CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..5639a37
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,101 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR:  invalid value for parameter "password_encryption": "novalue"
+SET password_encryption = true; -- error
+ERROR:  invalid value for parameter "password_encryption": "true"
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+   rolname    | verimet | substr 
+--------------+---------+--------
+ role_passwd1 | p       | rol
+ role_passwd2 | m       | md5
+ role_passwd3 | m       | md5
+ role_passwd3 | p       | rol
+(4 rows)
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+NOTICE:  MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname = 'role_passwd3_new'
+    ORDER BY a.rolname, v.verimet;
+     rolname      | verimet | substr 
+------------------+---------+--------
+ role_passwd3_new | p       | rol
+(1 row)
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 ENCRYPTED PASSWORD 'po'; -- encrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'po'; -- unencrypted
+ALTER ROLE role_passwd3_new UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+     rolname      | verimet | substr 
+------------------+---------+--------
+ role_passwd1     | m       | md5
+ role_passwd2     | p       | po
+ role_passwd3_new | m       | md5
+(3 rows)
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ERROR:  unrecognized authorization verifier option "unexistent_verif"
+LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
+                                                    ^
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd3_new PASSWORD VERIFIERS (plain = 'foo', md5 = 'foo2'); -- ok
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+     rolname      | verimet | substr 
+------------------+---------+--------
+ role_passwd1     | m       | md5
+ role_passwd2     | p       | foo
+ role_passwd3_new | m       | md5
+ role_passwd3_new | p       | foo
+(4 rows)
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3_new;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
+ rolname | verimet 
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6206c81..8a34d54 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1627,7 +1627,14 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.rolsuper AS usesuper,
     pg_authid.rolreplication AS userepl,
     pg_authid.rolbypassrls AS usebypassrls,
-    pg_authid.rolpassword AS passwd,
+    ARRAY( SELECT
+                CASE pg_auth_verifiers.verimet
+                    WHEN 'p'::"char" THEN ('plain:'::text || pg_auth_verifiers.verival)
+                    WHEN 'm'::"char" THEN ('md5:'::text || pg_auth_verifiers.verival)
+                    ELSE NULL::text
+                END AS verifiers
+           FROM pg_auth_verifiers
+          WHERE (pg_auth_verifiers.roleid = pg_authid.oid)) AS verifiers,
     (pg_authid.rolvaliduntil)::abstime AS valuntil,
     s.setconfig AS useconfig
    FROM (pg_authid
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..d4b3f36 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -91,6 +91,7 @@ pg_amproc|t
 pg_attrdef|t
 pg_attribute|t
 pg_auth_members|t
+pg_auth_verifiers|t
 pg_authid|t
 pg_cast|t
 pg_class|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4df15de..c24420b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets password
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 15d74d4..88d10cf 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -109,6 +109,7 @@ test: matview
 test: lock
 test: replica_identity
 test: rowsecurity
+test: password
 test: object_address
 test: tablesample
 test: alter_generic
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4ebc7ce
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,70 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- error
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname = 'role_passwd3_new'
+    ORDER BY a.rolname, v.verimet;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 ENCRYPTED PASSWORD 'po'; -- encrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'po'; -- unencrypted
+ALTER ROLE role_passwd3_new UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd3_new PASSWORD VERIFIERS (plain = 'foo', md5 = 'foo2'); -- ok
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3_new;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
-- 
2.5.0

0002-Move-sha1.c-to-src-common.patchapplication/x-patch; name=0002-Move-sha1.c-to-src-common.patchDownload
From 7fd549eb80ff67246973f3ce0d3dbbb87028d778 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 7 Aug 2015 13:42:53 +0900
Subject: [PATCH 2/4] Move sha1.c to src/common

---
 contrib/pgcrypto/Makefile                       | 4 ++--
 contrib/pgcrypto/internal.c                     | 2 +-
 src/common/Makefile                             | 2 +-
 {contrib/pgcrypto => src/common}/sha1.c         | 4 ++--
 {contrib/pgcrypto => src/include/common}/sha1.h | 2 +-
 src/tools/msvc/Mkvcbuild.pm                     | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)
 rename {contrib/pgcrypto => src/common}/sha1.c (99%)
 rename {contrib/pgcrypto => src/include/common}/sha1.h (98%)

diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 18bad1a..bb5118e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,6 +1,6 @@
 # contrib/pgcrypto/Makefile
 
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
+INT_SRCS = md5.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
 		fortuna.c random.c pgp-mpi-internal.c imath.c
 INT_TESTS = sha2
 
@@ -30,7 +30,7 @@ DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
 	pgcrypto--unpackaged--1.0.sql
 PGFILEDESC = "pgcrypto - cryptographic functions"
 
-REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
+REGRESS = init md5 hmac-md5 hmac-sha1 blowfish rijndael \
 	$(CF_TESTS) \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
 	pgp-armor pgp-decrypt pgp-encrypt $(CF_PGP_TESTS) \
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..9f42955 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -35,7 +35,7 @@
 
 #include "px.h"
 #include "md5.h"
-#include "sha1.h"
+#include "common/sha1.h"
 #include "blf.h"
 #include "rijndael.h"
 #include "fortuna.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index c47445e..d6c2a57 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = exec.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o string.o username.o wait_error.o
+	rmtree.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
diff --git a/contrib/pgcrypto/sha1.c b/src/common/sha1.c
similarity index 99%
rename from contrib/pgcrypto/sha1.c
rename to src/common/sha1.c
index 0e753ce..4d9a325 100644
--- a/contrib/pgcrypto/sha1.c
+++ b/src/common/sha1.c
@@ -28,7 +28,7 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * contrib/pgcrypto/sha1.c
+ * src/common/sha1.c
  */
 /*
  * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
@@ -40,7 +40,7 @@
 
 #include <sys/param.h>
 
-#include "sha1.h"
+#include "common/sha1.h"
 
 /* constant table */
 static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
diff --git a/contrib/pgcrypto/sha1.h b/src/include/common/sha1.h
similarity index 98%
rename from contrib/pgcrypto/sha1.h
rename to src/include/common/sha1.h
index 5532ca1..d5ff296 100644
--- a/contrib/pgcrypto/sha1.h
+++ b/src/include/common/sha1.h
@@ -1,4 +1,4 @@
-/*	contrib/pgcrypto/sha1.h */
+/*	src/include/common/sha1.h */
 /*	   $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $    */
 
 /*
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index d70db40..bef8bf3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -106,7 +106,7 @@ sub mkvcbuild
 
 	our @pgcommonallfiles = qw(
 	  exec.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
-	  string.c username.c wait_error.c);
+	  sha1.c string.c username.c wait_error.c);
 
 	our @pgcommonfrontendfiles = (
 		@pgcommonallfiles, qw(fe_memutils.c
-- 
2.5.0

0003-Refactor-sendAuthRequest.patchapplication/x-patch; name=0003-Refactor-sendAuthRequest.patchDownload
From 124cb3c128030a3f1fe2e75ae783bee698487898 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 3 Aug 2015 15:42:49 +0900
Subject: [PATCH 3/4] Refactor sendAuthRequest

---
 src/backend/libpq/auth.c | 65 ++++++++++++++++++++++++------------------------
 1 file changed, 32 insertions(+), 33 deletions(-)

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 4699efa..1b28722 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -36,7 +36,8 @@
  * Global authentication functions
  *----------------------------------------------------------------
  */
-static void sendAuthRequest(Port *port, AuthRequest areq);
+static void sendAuthRequest(Port *port, AuthRequest areq, char *extradata,
+				int extralen);
 static void auth_failed(Port *port, int status, char *logdetail);
 static char *recv_password_packet(Port *port);
 static int	recv_and_check_password_packet(Port *port, char **logdetail);
@@ -479,7 +480,7 @@ ClientAuthentication(Port *port)
 
 		case uaGSS:
 #ifdef ENABLE_GSS
-			sendAuthRequest(port, AUTH_REQ_GSS);
+			sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
 			status = pg_GSS_recvauth(port);
 #else
 			Assert(false);
@@ -488,7 +489,7 @@ ClientAuthentication(Port *port)
 
 		case uaSSPI:
 #ifdef ENABLE_SSPI
-			sendAuthRequest(port, AUTH_REQ_SSPI);
+			sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
 			status = pg_SSPI_recvauth(port);
 #else
 			Assert(false);
@@ -512,12 +513,13 @@ ClientAuthentication(Port *port)
 				ereport(FATAL,
 						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
 						 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
-			sendAuthRequest(port, AUTH_REQ_MD5);
+			/* Add the salt for encrypted passwords. */
+			sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
 		case uaPassword:
-			sendAuthRequest(port, AUTH_REQ_PASSWORD);
+			sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
@@ -556,7 +558,7 @@ ClientAuthentication(Port *port)
 		(*ClientAuthentication_hook) (port, status);
 
 	if (status == STATUS_OK)
-		sendAuthRequest(port, AUTH_REQ_OK);
+		sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
 	else
 		auth_failed(port, status, logdetail);
 }
@@ -566,7 +568,7 @@ ClientAuthentication(Port *port)
  * Send an authentication request packet to the frontend.
  */
 static void
-sendAuthRequest(Port *port, AuthRequest areq)
+sendAuthRequest(Port *port, AuthRequest areq, char *extradata, int extralen)
 {
 	StringInfoData buf;
 
@@ -575,27 +577,8 @@ sendAuthRequest(Port *port, AuthRequest areq)
 	pq_beginmessage(&buf, 'R');
 	pq_sendint(&buf, (int32) areq, sizeof(int32));
 
-	/* Add the salt for encrypted passwords. */
-	if (areq == AUTH_REQ_MD5)
-		pq_sendbytes(&buf, port->md5Salt, 4);
-
-#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
-
-	/*
-	 * Add the authentication data for the next step of the GSSAPI or SSPI
-	 * negotiation.
-	 */
-	else if (areq == AUTH_REQ_GSS_CONT)
-	{
-		if (port->gss->outbuf.length > 0)
-		{
-			elog(DEBUG4, "sending GSS token of length %u",
-				 (unsigned int) port->gss->outbuf.length);
-
-			pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
-		}
-	}
-#endif
+	if (extralen > 0)
+		pq_sendbytes(&buf, extradata, extralen);
 
 	pq_endmessage(&buf);
 
@@ -908,7 +891,15 @@ pg_GSS_recvauth(Port *port)
 			elog(DEBUG4, "sending GSS response token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			gss_release_buffer(&lmin_s, &port->gss->outbuf);
 		}
@@ -1151,7 +1142,15 @@ pg_SSPI_recvauth(Port *port)
 			port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
 			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
 		}
@@ -1672,7 +1671,7 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
 					 * let's go ask the client to send a password, which we
 					 * then stuff into PAM.
 					 */
-					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
+					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
 					passwd = recv_password_packet(pam_port_cludge);
 					if (passwd == NULL)
 					{
@@ -1947,7 +1946,7 @@ CheckLDAPAuth(Port *port)
 	if (port->hba->ldapport == 0)
 		port->hba->ldapport = LDAP_PORT;
 
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
@@ -2303,7 +2302,7 @@ CheckRADIUSAuth(Port *port)
 		identifier = port->hba->radiusidentifier;
 
 	/* Send regular password request to client, and get the response */
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
-- 
2.5.0

0004-SCRAM-authentication.patchapplication/x-patch; name=0004-SCRAM-authentication.patchDownload
From 27b890d41d6655746fdfbdc256300545d990d6dc Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 7 Aug 2015 16:01:51 +0900
Subject: [PATCH 4/4] SCRAM authentication

---
 contrib/passwordcheck/passwordcheck.c   |   3 +
 doc/src/sgml/catalogs.sgml              |   3 +-
 doc/src/sgml/config.sgml                |   3 +-
 doc/src/sgml/protocol.sgml              | 148 +++++++-
 src/backend/catalog/system_views.sql    |   1 +
 src/backend/commands/user.c             |   6 +
 src/backend/libpq/Makefile              |   2 +-
 src/backend/libpq/auth-scram.c          | 621 ++++++++++++++++++++++++++++++++
 src/backend/libpq/auth.c                | 125 ++++++-
 src/backend/libpq/crypt.c               |   4 +-
 src/backend/libpq/hba.c                 |  14 +
 src/backend/libpq/pg_hba.conf.sample    |   2 +-
 src/backend/parser/gram.y               |   4 +
 src/backend/utils/adt/encode.c          |   8 +-
 src/backend/utils/misc/guc.c            |   3 +-
 src/bin/pg_dump/pg_dumpall.c            |   2 +
 src/common/Makefile                     |   2 +-
 src/common/scram-common.c               | 161 +++++++++
 src/include/catalog/pg_auth_verifiers.h |   1 +
 src/include/common/scram-common.h       |  35 ++
 src/include/libpq/auth.h                |   5 +
 src/include/libpq/crypt.h               |   1 +
 src/include/libpq/hba.h                 |   1 +
 src/include/libpq/pqcomm.h              |   2 +
 src/include/libpq/scram.h               |  23 ++
 src/include/utils/builtins.h            |   4 +
 src/interfaces/libpq/.gitignore         |   1 +
 src/interfaces/libpq/Makefile           |   8 +-
 src/interfaces/libpq/fe-auth-scram.c    | 476 ++++++++++++++++++++++++
 src/interfaces/libpq/fe-auth.c          |  95 +++++
 src/interfaces/libpq/fe-auth.h          |   8 +
 src/interfaces/libpq/fe-connect.c       |  49 +++
 src/interfaces/libpq/libpq-int.h        |   5 +
 src/interfaces/libpq/sha1.c             |   1 +
 34 files changed, 1804 insertions(+), 23 deletions(-)
 create mode 100644 src/backend/libpq/auth-scram.c
 create mode 100644 src/common/scram-common.c
 create mode 100644 src/include/common/scram-common.h
 create mode 100644 src/include/libpq/scram.h
 create mode 100644 src/interfaces/libpq/fe-auth-scram.c
 create mode 120000 src/interfaces/libpq/sha1.c

diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 5ee38ed..10e1ca9 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -135,6 +135,9 @@ check_password(const char *username,
 #endif
 				break;
 
+			case AUTH_VERIFIER_SCRAM:
+				/* unfortunately not much can be done here */
+				break;
 			default:
 				elog(ERROR, "unrecognized password type: %d", spec->veriftype);
 				break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6d3323b..796290b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1522,7 +1522,8 @@
       <entry><type>char</type></entry>
       <entry>
        <literal>p</> = plain format,
-       <literal>m</> = MD5-encrypted
+       <literal>m</> = MD5-encrypted,
+       <literal>s</> = SCRAM-SHA1-encrypted
       </entry>
      </row>
 
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5ae27f8..dbcb985 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1171,7 +1171,8 @@ include_dir 'conf.d'
       <listitem>
        <para>
         Specifies a comma-separated list of password encryption formats.
-        Supported formats are <literal>plain</> and <literal>md5</>.
+        Supported formats are <literal>plain</>,<literal>md5</> and
+        <literal>scram</>.
        </para>
 
        <para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 42e9497..2a9b0e4 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -228,11 +228,11 @@
     The server then sends an appropriate authentication request message,
     to which the frontend must reply with an appropriate authentication
     response message (such as a password).
-    For all authentication methods except GSSAPI and SSPI, there is at most
-    one request and one response. In some methods, no response
+    For all authentication methods except GSSAPI, SSPI and SASL, there is at
+    most one request and one response. In some methods, no response
     at all is needed from the frontend, and so no authentication request
-    occurs. For GSSAPI and SSPI, multiple exchanges of packets may be needed
-    to complete the authentication.
+    occurs. For GSSAPI, SSPI and SASL, multiple exchanges of packets may be
+    needed to complete the authentication.
    </para>
 
    <para>
@@ -366,6 +366,35 @@
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <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.
+       </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.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
 
@@ -2572,6 +2601,115 @@ AuthenticationGSSContinue (B)
 </listitem>
 </varlistentry>
 
+<varlistentry>
+<term>
+AuthenticationSASL (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(10)
+</term>
+<listitem>
+<para>
+                Specifies that SASL authentication is started.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of a SASL authentication mechanism.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+AuthenticationSASLContinue (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(11)
+</term>
+<listitem>
+<para>
+                Specifies that this message contains SASL-mechanism specific
+                data.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+                SASL data, specific to the SASL mechanism being used.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
 
 <varlistentry>
 <term>
@@ -4334,7 +4472,7 @@ PasswordMessage (F)
 <listitem>
 <para>
                 Identifies the message as a password response. Note that
-                this is also used for GSSAPI and SSPI response messages
+                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).
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f9121e6..6de4821 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -38,6 +38,7 @@ CREATE VIEW pg_shadow AS
                 CASE verimet
                     WHEN 'p' THEN 'plain:' || verival
                     WHEN 'm' THEN 'md5:' || verival
+                    WHEN 's' THEN 'scram:' || verival
                 END AS verifiers
             FROM pg_auth_verifiers
 	    WHERE roleid = pg_authid.oid
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index c6bf9db..e1846eb 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -31,6 +31,7 @@
 #include "commands/seclabel.h"
 #include "commands/user.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
@@ -1623,6 +1624,11 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
 		else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
 				 isMD5(spec->value))
 			spec->veriftype = AUTH_VERIFIER_MD5;
+		else if (spec->veriftype == AUTH_VERIFIER_SCRAM)
+		{
+			/* create SCRAM verifier */
+			spec->value = scram_build_verifier(rolname, spec->value, 0);
+		}
 	}
 }
 
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..3dd60e1 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
 # be-fsstubs is here for historical reasons, probably belongs elsewhere
 
 OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \
-       pqformat.o pqmq.o pqsignal.o
+       pqformat.o pqmq.o pqsignal.o auth-scram.o
 
 ifeq ($(with_openssl),yes)
 OBJS += be-secure-openssl.o
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
new file mode 100644
index 0000000..060193b
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,621 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ *	  Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See RFC 5802. Some differences:
+ *
+ * - Username from the authentication exchange is not used. The client
+ *   should send an empty string as the username.
+ *
+ * - Password is not processed with the SASLprep algorithm.
+ *
+ * - Channel binding is not supported.
+ *
+ * The verifier stored in pg_authid consists of the salt, iteration count,
+ * StoredKey, and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_authid.h"
+#include "common/scram-common.h"
+#include "common/sha1.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+#define SALT_LEN		10		/* length of salt when generating new
+								 * verifiers */
+#define NONCE_LEN		10		/* length of random nonce generated in the
+								 * authentication exchange */
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		SALT_SENT,
+		FINISHED
+	} state;
+
+	const char *username;	/* username from startup packet */
+	char	   *salt;		/* base64-encoded */
+	int			iterations;
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+
+	/* These come from the client-first message */
+	char	   *client_first_message_bare;
+	char	   *client_username;	/* username from client-first message */
+	char	   *client_authzid;
+	char	   *client_nonce;
+
+	/* These come from the client-final message */
+	char	   *client_final_message_without_proof;
+	char	   *client_final_nonce;
+	char		ClientProof[SCRAM_KEY_LEN];
+
+	char	   *server_first_message;
+	char	   *server_nonce;		/* base64-encoded */
+	char	   *server_signature;
+
+} scram_state;
+
+static void read_client_first_message(scram_state *state, char *input);
+static void read_client_final_message(scram_state *state, char *input);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+
+static void generate_nonce(char *out, int len);
+
+/*
+ * Initialize a new SCRAM authentication exchange, with given username and
+ * its stored verifier.
+ */
+void *
+scram_init(const char *username, const char *verifier)
+{
+	scram_state *state;
+	char	   *v;
+	char	   *p;
+
+	state = (scram_state *) palloc0(sizeof(scram_state));
+	state->state = INIT;
+	state->username = username;
+
+	/*
+	 * The verifier is of form:
+	 *
+	 * salt:iterations:storedkey:serverkey
+	 */
+	v = pstrdup(verifier);
+
+	/* salt */
+	if ((p = strtok(v, ":")) == NULL)
+		goto invalid_verifier;
+	state->salt = pstrdup(p);
+
+	/* iterations */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	errno = 0;
+	state->iterations = strtol(p, &p, 10);
+	if (*p || errno != 0)
+		goto invalid_verifier;
+
+	/* storedkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+	hex_decode(p, SCRAM_KEY_LEN*2, (char *) state->StoredKey);
+
+	/* serverkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+	hex_decode(p, SCRAM_KEY_LEN*2, (char *) state->ServerKey);
+
+	pfree(v);
+
+	return state;
+
+invalid_verifier:
+	/* FIXME: should we keep this secret from the unauthenticated user? */
+	elog(ERROR, "invalid SCRAM verifier: %s", verifier);
+	return NULL;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ */
+int
+scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen)
+{
+	scram_state *state = (scram_state *) opaq;
+	int			result;
+
+	*output = NULL;
+	*outputlen = 0;
+
+	if (inputlen > 0)
+		elog(LOG, "got SCRAM message: %s", input);
+
+	switch (state->state)
+	{
+		case INIT:
+			/* receive username and client nonce, send challenge */
+			read_client_first_message(state, input);
+			*output = build_server_first_message(state);
+			*outputlen = strlen(*output);
+			result = SASL_EXCHANGE_CONTINUE;
+			state->state = SALT_SENT;
+			break;
+
+		case SALT_SENT:
+			/* receive response to challenge and verify it */
+			read_client_final_message(state, input);
+			if (verify_final_nonce(state) && verify_client_proof(state))
+			{
+				*output = build_server_final_message(state);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_SUCCESS;
+			}
+			else
+			{
+				result = SASL_EXCHANGE_FAILURE;
+			}
+			state->state = FINISHED;
+			break;
+
+		default:
+			elog(ERROR, "invalid SCRAM exchange state");
+			result = 0;
+	}
+
+	return result;
+}
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolverifiers.
+ *
+ * If salt is NULL, a random salt is generated. If iterations is 0, default
+ * number of iterations is used;
+ */
+char *
+scram_build_verifier(char *username, char *password, int iterations)
+{
+	uint8		keybuf[SCRAM_KEY_LEN + 1];
+	char		storedkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		serverkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		salt[SALT_LEN];
+	char	   *encoded_salt;
+	int			encoded_len;
+
+	if (iterations <= 0)
+		iterations = 4096;
+
+	generate_nonce(salt, SALT_LEN);
+
+	encoded_salt = palloc(b64_enc_len(salt, SALT_LEN) + 1);
+	encoded_len = b64_encode(salt, SALT_LEN, encoded_salt);
+	encoded_salt[encoded_len] = '\0';
+
+	/* Calculate StoredKey, and encode it in hex */
+	scram_ClientOrServerKey(password, salt, SALT_LEN, iterations, "Client Key", keybuf);
+	scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, storedkey_hex);
+	storedkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	/* And same for ServerKey */
+	scram_ClientOrServerKey(password, salt, SALT_LEN, iterations, "Server Key", keybuf);
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
+	serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	return psprintf("%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
+}
+
+
+static char *
+read_attr_value(char **input, char attr)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		elog(ERROR, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+read_any_attr(char **input, char *attr_p)
+{
+	char *begin = *input;
+	char *end;
+	char attr = *begin;
+
+	if (!((attr >= 'A' && attr <= 'Z') ||
+		  (attr >= 'a' && attr <= 'z')))
+		elog(ERROR, "malformed SCRAM message (invalid attribute char)");
+	if (attr_p)
+		*attr_p = attr;
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static void
+read_client_first_message(scram_state *state, char *input)
+{
+	input = pstrdup(input);
+
+	/*
+	 * saslname        = 1*(value-safe-char / "=2C" / "=3D")
+	 *              ;; Conforms to <value>.
+	 *
+	 * authzid         = "a=" saslname
+	 *              ;; Protocol specific.
+	 *
+	 * username        = "n=" saslname
+	 *               ;; Usernames are prepared using SASLprep.
+	 *
+	 * gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
+	 *               ;; "n" -> client doesn't support channel binding.
+	 *               ;; "y" -> client does support channel binding
+	 *               ;;        but thinks the server does not.
+	 *               ;; "p" -> client requires channel binding.
+	 *               ;; The selected channel binding follows "p=".
+	 *
+	 * gs2-header      = gs2-cbind-flag "," [ authzid ] ","
+	 *               ;; GS2 header for SCRAM
+	 *               ;; (the actual GS2 header includes an optional
+	 *               ;; flag to indicate that the GSS mechanism is not
+	 *               ;; "standard", but since SCRAM is "standard", we
+	 *               ;; don't include that flag).
+	 *
+	 *   client-first-message-bare =
+	 *               [reserved-mext ","]
+	 *               username "," nonce ["," extensions]
+	 *
+	 *   client-first-message =
+	 *                gs2-header client-first-message-bare
+	 *
+	 *
+	 * For example:
+	 * n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+	 */
+
+	/* read gs2-cbind-flag */
+	switch (*input)
+	{
+		case 'n':
+			/* client does not support channel binding */
+			input++;
+			break;
+		case 'y':
+			/* client supports channel binding, but we're not doing it today */
+			input++;
+			break;
+		case 'p':
+			/* client requires channel binding. We don't support it */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("channel binding not supported")));
+	}
+
+	/* any mandatory extensions would go here. */
+	if (*input != ',')
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mandatory extension %c not supported", *input)));
+	input++;
+
+	/* read optional authzid (authorization identity) */
+	if (*input != ',')
+		state->client_authzid = read_attr_value(&input, 'a');
+	else
+		input++;
+
+	state->client_first_message_bare = pstrdup(input);
+
+	/* read username (FIXME: unescape) */
+	state->client_username = read_attr_value(&input, 'n');
+
+	/* read nonce */
+	state->client_nonce = read_attr_value(&input, 'r');
+
+	/*
+	 * There can be any number of optional extensions after this. We don't
+	 * support any extensions, so ignore them.
+	 */
+	while (*input != '\0')
+		read_any_attr(&input, NULL);
+
+	/* success! */
+}
+
+static bool
+verify_final_nonce(scram_state *state)
+{
+	int			client_nonce_len = strlen(state->client_nonce);
+	int			server_nonce_len = strlen(state->server_nonce);
+	int			final_nonce_len = strlen(state->client_final_nonce);
+
+	if (final_nonce_len != client_nonce_len + server_nonce_len)
+		return false;
+	if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0)
+		return false;
+	if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0)
+		return false;
+
+	return true;
+}
+
+static bool
+verify_client_proof(scram_state *state)
+{
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		client_StoredKey[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+	int			i;
+
+	/* calculate ClientSignature */
+	scram_HMAC_init(&ctx, state->StoredKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+	elog(LOG, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+	elog(LOG, "AuthMessage: %s,%s,%s", state->client_first_message_bare,
+		 state->server_first_message, state->client_final_message_without_proof);
+
+	/* Extract the ClientKey that the client calculated from the proof */
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+
+	/* Hash it one more time, and compare with StoredKey */
+	scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey);
+	elog(LOG, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+	elog(LOG, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+	elog(LOG, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+	if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+		return false;
+
+	/* Also verify the nonce */
+
+	return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+	char		nonce[NONCE_LEN];
+	int			encoded_len;
+
+	/*
+	 * server-first-message =
+	 *                   [reserved-mext ","] nonce "," salt ","
+	 *                   iteration-count ["," extensions]
+	 *
+	 *   nonce           = "r=" c-nonce [s-nonce]
+	 *               ;; Second part provided by server.
+	 *
+	 * c-nonce         = printable
+	 *
+	 * s-nonce         = printable
+	 *
+	 * salt            = "s=" base64
+	 *
+	 * iteration-count = "i=" posit-number
+	 *              ;; A positive number.
+	 *
+	 * Example:
+	 *
+	 * r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+	 */
+	generate_nonce(nonce, NONCE_LEN);
+
+	state->server_nonce = palloc(b64_enc_len(nonce, NONCE_LEN) + 1);
+	encoded_len = b64_encode(nonce, NONCE_LEN, state->server_nonce);
+
+	state->server_nonce[encoded_len] = '\0';
+	state->server_first_message =
+		psprintf("r=%s%s,s=%s,i=%u",
+				 state->client_nonce, state->server_nonce,
+				 state->salt, state->iterations);
+
+	return state->server_first_message;
+}
+
+static void
+read_client_final_message(scram_state *state, char *input)
+{
+	char		attr;
+	char	   *channel_binding;
+	char	   *value;
+	char	   *begin, *proof;
+	char	   *p;
+	char	   *client_proof;
+
+	begin = p = pstrdup(input);
+
+	/*
+	 *
+	 * cbind-input   = gs2-header [ cbind-data ]
+	 *               ;; cbind-data MUST be present for
+	 *               ;; gs2-cbind-flag of "p" and MUST be absent
+	 *               ;; for "y" or "n".
+	 *
+	 * channel-binding = "c=" base64
+	 *               ;; base64 encoding of cbind-input.
+	 *
+	 * proof           = "p=" base64
+	 *
+	 * client-final-message-without-proof =
+	 *               channel-binding "," nonce ["," extensions]
+	 *
+	 * client-final-message =
+	 *              client-final-message-without-proof "," proof
+	 */
+	channel_binding = read_attr_value(&p, 'c');
+	if (strcmp(channel_binding, "biws") != 0)
+		elog(ERROR, "invalid channel binding input");
+	state->client_final_nonce = read_attr_value(&p, 'r');
+
+	/* ignore optional extensions */
+	do
+	{
+		proof = p - 1;
+		value = read_any_attr(&p, &attr);
+	} while (attr != 'p');
+
+	client_proof = palloc(b64_dec_len(value, strlen(value)));
+	if (b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+		elog(ERROR, "invalid ClientProof");
+	memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+	pfree(client_proof);
+
+	if (*p != '\0')
+		elog(ERROR, "malformed SCRAM message (garbage at end of message %c)", attr);
+
+	state->client_final_message_without_proof = palloc(proof - begin + 1);
+	memcpy(state->client_final_message_without_proof, input, proof - begin);
+	state->client_final_message_without_proof[proof - begin] = '\0';
+
+	/* FIXME: check channel_binding field */
+}
+
+
+static char *
+build_server_final_message(scram_state *state)
+{
+	uint8		ServerSignature[SCRAM_KEY_LEN];
+	char	   *server_signature_base64;
+	int			siglen;
+	scram_HMAC_ctx ctx;
+
+	/* calculate ServerSignature */
+	scram_HMAC_init(&ctx, state->ServerKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ServerSignature, &ctx);
+
+	server_signature_base64 = palloc(b64_enc_len((const char *) ServerSignature, SCRAM_KEY_LEN) + 1);
+	siglen = b64_encode((const char *) ServerSignature, SCRAM_KEY_LEN, server_signature_base64);
+	server_signature_base64[siglen] = '\0';
+
+	/*
+	 *
+	 * server-error = "e=" server-error-value
+	 *
+	 * server-error-value = "invalid-encoding" /
+	 *           "extensions-not-supported" /  ; unrecognized 'm' value
+	 *            "invalid-proof" /
+	 *            "channel-bindings-dont-match" /
+	 *            "server-does-support-channel-binding" /
+	 *              ; server does not support channel binding
+	 *            "channel-binding-not-supported" /
+	 *            "unsupported-channel-binding-type" /
+	 *            "unknown-user" /
+	 *            "invalid-username-encoding" /
+	 *              ; invalid username encoding (invalid UTF-8 or
+	 *              ; SASLprep failed)
+	 *            "no-resources" /
+	 *            "other-error" /
+	 *            server-error-value-ext
+	 *     ; Unrecognized errors should be treated as "other-error".
+	 *     ; In order to prevent information disclosure, the server
+	 *     ; may substitute the real reason with "other-error".
+	 *
+	 * server-error-value-ext = value
+	 *     ; Additional error reasons added by extensions
+	 *     ; to this document.
+	 *
+	 * verifier        = "v=" base64
+	 *               ;; base-64 encoded ServerSignature.
+	 *
+	 * server-final-message = (server-error / verifier)
+	 *                ["," extensions]
+	 */
+	return psprintf("v=%s", server_signature_base64);
+}
+
+static void
+generate_nonce(char *result, int len)
+{
+	/*
+	 * TODO: We reuse the salt generated for MD5 authentication. It's only
+	 * four bytes - we'd really want to use a much longer salt.
+	 */
+	memset(result, 0, len);
+	memcpy(result, MyProcPort->md5Salt, Min(sizeof(MyProcPort->md5Salt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 1b28722..05bba8f 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,16 +21,19 @@
 #include <arpa/inet.h>
 #include <unistd.h>
 
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "libpq/auth.h"
 #include "libpq/crypt.h"
 #include "libpq/ip.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
-
+#include "utils/syscache.h"
 
 /*----------------------------------------------------------------
  * Global authentication functions
@@ -185,6 +188,12 @@ static int	CheckRADIUSAuth(Port *port);
 
 
 /*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
  * Global authentication functions
  *----------------------------------------------------------------
  */
@@ -246,6 +255,7 @@ auth_failed(Port *port, int status, char *logdetail)
 			break;
 		case uaPassword:
 		case uaMD5:
+		case uaSASL:
 			errstr = gettext_noop("password authentication failed for user \"%s\"");
 			/* We use it to indicate if a .pgpass password failed. */
 			errcode_return = ERRCODE_INVALID_PASSWORD;
@@ -523,6 +533,10 @@ ClientAuthentication(Port *port)
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
+		case uaSASL:
+			status = CheckSASLAuth(port, &logdetail);
+			break;
+
 		case uaPAM:
 #ifdef USE_PAM
 			status = CheckPAMAuth(port, port->user_name, "");
@@ -691,6 +705,107 @@ recv_and_check_password_packet(Port *port, char **logdetail)
 	return result;
 }
 
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+	int			mtype;
+	StringInfoData buf;
+	void	   *scram_opaq;
+	char	   *verifier;
+	char	   *output = NULL;
+	int			outputlen = 0;
+	int			result;
+	HeapTuple	roleTup;
+
+	/*
+	 * SASL auth is not supported for protocol versions before 3, because it
+	 * relies on the overall message length word to determine the SASL payload
+	 * size in AuthenticationSASLContinue and PasswordMessage messages. (We
+	 * used to have a hard rule that protocol messages must be parsable
+	 * without relying on the length word, but we hardly care about protocol
+	 * version or older anymore.)
+	 *
+	 * FIXME: the FE/BE docs need to updated.
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SASL authentication is not supported in protocol version 2")));
+
+	/* Get role info from pg_authid */
+	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(port->user_name));
+	if (!HeapTupleIsValid(roleTup))
+		return STATUS_ERROR;
+
+	/* lookup verifier */
+	verifier = get_role_verifier(HeapTupleGetOid(roleTup), AUTH_VERIFIER_SCRAM);
+	if (verifier == NULL)
+	{
+		ReleaseSysCache(roleTup);
+		return STATUS_ERROR;
+	}
+
+	ReleaseSysCache(roleTup);
+
+	sendAuthRequest(port, AUTH_REQ_SASL, "SCRAM-SHA-1", strlen("SCRAM-SHA-1") + 1);
+
+	scram_opaq = scram_init(port->user_name, verifier);
+
+	/*
+	 * Loop through SASL message exchange. This exchange can consist of
+	 * multiple messags sent in both directions. First message is always from
+	 * the client. All messages from client to server are password packets
+	 * (type 'p').
+	 */
+	do
+	{
+		pq_startmsgread();
+		mtype = pq_getbyte();
+		if (mtype != 'p')
+		{
+			/* Only log error if client didn't disconnect. */
+			if (mtype != EOF)
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("expected SASL response, got message type %d",
+								mtype)));
+			return STATUS_ERROR;
+		}
+
+		/* Get the actual SASL token */
+		initStringInfo(&buf);
+		if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
+		{
+			/* EOF - pq_getmessage already logged error */
+			pfree(buf.data);
+			return STATUS_ERROR;
+		}
+
+		elog(DEBUG4, "Processing received SASL token of length %d", buf.len);
+
+		result = scram_exchange(scram_opaq, buf.data, buf.len,
+								&output, &outputlen);
+
+		/* input buffer no longer used */
+		pfree(buf.data);
+
+		if (outputlen > 0)
+		{
+			/*
+			 * Negotiation generated data to be sent to the client.
+			 */
+			elog(DEBUG4, "sending SASL response token of length %u", outputlen);
+
+			sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+		}
+	} while (result == SASL_EXCHANGE_CONTINUE);
+
+	return (result == SASL_EXCHANGE_SUCCESS) ? STATUS_OK : STATUS_ERROR;
+}
 
 
 /*----------------------------------------------------------------
@@ -892,8 +1007,8 @@ pg_GSS_recvauth(Port *port)
 				 (unsigned int) port->gss->outbuf.length);
 
 			/*
-			 * Add the authentication data for the next step of the GSSAPI or
-			 * SSPI negotiation.
+			 * Add the authentication data for the next step of the GSSAPI
+			 * negotiation.
 			 */
 			elog(DEBUG4, "sending GSS token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
@@ -1143,8 +1258,8 @@ pg_SSPI_recvauth(Port *port)
 			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
 
 			/*
-			 * Add the authentication data for the next step of the GSSAPI or
-			 * SSPI negotiation.
+			 * Add the authentication data for the next step of the SSPI
+			 * negotiation.
 			 */
 			elog(DEBUG4, "sending GSS token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index f04d17a..4140dd8 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -33,10 +33,10 @@
 #include "utils/timestamp.h"
 
 /*
- * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * Get verifier stored in pg_auth_verifiers, for given authentication
  * method.
  */
-static char *
+char *
 get_role_verifier(Oid roleid, const char method)
 {
 	HeapTuple	tuple;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 23c8b5d..646898b 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,20 @@ parse_hba_line(List *line, int line_num, char *raw_line)
 		}
 		parsedline->auth_method = uaMD5;
 	}
+	else if (strcmp(token->string, "scram") == 0)
+	{
+		/* FIXME: could we support Db_user_namespace with SCRAM? */
+		if (Db_user_namespace)
+		{
+			ereport(LOG,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("SCRAM authentication is not supported when \"db_user_namespace\" is enabled"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			return NULL;
+		}
+		parsedline->auth_method = uaSASL;
+	}
 	else if (strcmp(token->string, "pam") == 0)
 #ifdef USE_PAM
 		parsedline->auth_method = uaPAM;
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index 86a89ed..dc3ce2f 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,7 +42,7 @@
 # or "samenet" to match any address in any subnet that the server is
 # directly connected to.
 #
-# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
+# METHOD can be "trust", "reject", "md5", "password", "scram", "gss", "sspi",
 # "ident", "peer", "pam", "ldap", "radius" or "cert".  Note that
 # "password" sends passwords in clear text; "md5" is preferred since
 # it sends encrypted passwords.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5b080c2..37997d9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -940,6 +940,8 @@ AuthVerifierSpec:
 						type = AUTH_VERIFIER_MD5;
 					else if (strcmp($1, "plain") == 0)
 						type = AUTH_VERIFIER_PLAIN;
+					else if (strcmp($1, "scram") == 0)
+						type = AUTH_VERIFIER_SCRAM;
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
@@ -969,6 +971,8 @@ AlterOptRoleElem:
 							veriftype = AUTH_VERIFIER_MD5;
 						else if (strcmp(meth_name, "plain") == 0)
 							veriftype = AUTH_VERIFIER_PLAIN;
+						else if (strcmp(meth_name, "scram") == 0)
+							veriftype = AUTH_VERIFIER_SCRAM;
 						else
 							Assert(false);	/* should not happen */
 						n = (AuthVerifierSpec *)
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index 4b32b6c..c414e86 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -214,7 +214,7 @@ static const int8 b64lookup[128] = {
 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
 };
 
-static unsigned
+unsigned
 b64_encode(const char *src, unsigned len, char *dst)
 {
 	char	   *p,
@@ -261,7 +261,7 @@ b64_encode(const char *src, unsigned len, char *dst)
 	return p - dst;
 }
 
-static unsigned
+unsigned
 b64_decode(const char *src, unsigned len, char *dst)
 {
 	const char *srcend = src + len,
@@ -331,14 +331,14 @@ b64_decode(const char *src, unsigned len, char *dst)
 }
 
 
-static unsigned
+unsigned
 b64_enc_len(const char *src, unsigned srclen)
 {
 	/* 3 bytes will be converted to 4, linefeed after 76 chars */
 	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
 }
 
-static unsigned
+unsigned
 b64_dec_len(const char *src, unsigned srclen)
 {
 	return (srclen * 3) >> 2;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 082108e..c45ca09 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10142,7 +10142,8 @@ check_password_encryption(char **newval, void **extra, GucSource source)
 		char	   *encryption_name = (char *) lfirst(l);
 
 		if (strcmp(encryption_name, "md5") != 0 &&
-			strcmp(encryption_name, "plain") != 0)
+			strcmp(encryption_name, "plain") != 0 &&
+			strcmp(encryption_name, "scram") != 0)
 		{
 			pfree(rawstring);
 			list_free(elemlist);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index b4d09e5..f5c0b1a 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -932,6 +932,8 @@ dumpRoles(PGconn *conn)
 				appendPQExpBufferStr(buf, "md5 = ");
 			else if (verifier_meth == 'p')
 				appendPQExpBufferStr(buf, "plain = ");
+			else if (verifier_meth == 's')
+				appendPQExpBufferStr(buf, "scram = ");
 			appendStringLiteralConn(buf, verifier_value, conn);
 		}
 		if (current_user != NULL)
diff --git a/src/common/Makefile b/src/common/Makefile
index d6c2a57..8d3a6a4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = exec.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o sha1.o string.o username.o wait_error.o
+	rmtree.o scram-common.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..7da58c0
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,161 @@
+/*-------------------------------------------------------------------------
+ * scram-common.c
+ *		Shared frontend/backend code for SCRAM authentication
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the Salted Challenge Response Authentication
+ * Mechansim (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-1.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+	uint8		k_ipad[SHA1_HMAC_B];
+	int			i;
+	uint8		keybuf[SHA1_RESULTLEN];
+
+	/*
+	 * If the key is longer than the block size (64 bytes for SHA-1),
+	 * pass it through SHA-1 once to shrink it down
+	 */
+	if (keylen > SHA1_HMAC_B)
+	{
+		SHA1_CTX	sha1_ctx;
+
+		SHA1Init(&sha1_ctx);
+		SHA1Update(&sha1_ctx, key, keylen);
+		SHA1Final(keybuf, &sha1_ctx);
+		key = keybuf;
+		keylen = SHA1_RESULTLEN;
+	}
+
+	memset(k_ipad, 0x36, SHA1_HMAC_B);
+	memset(ctx->k_opad, 0x5C, SHA1_HMAC_B);
+	for (i = 0; i < keylen; i++)
+	{
+		k_ipad[i] ^= key[i];
+		ctx->k_opad[i] ^= key[i];
+	}
+
+	/* tmp = H(K XOR ipad, text) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, k_ipad, SHA1_HMAC_B);
+}
+
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+	SHA1Update(&ctx->sha1ctx, (const uint8 *) str, slen);
+}
+
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+	uint8		h[SHA1_RESULTLEN];
+
+	SHA1Final(h, &ctx->sha1ctx);
+
+	/* H(K XOR opad, tmp) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, ctx->k_opad, SHA1_HMAC_B);
+	SHA1Update(&ctx->sha1ctx, h, SHA1_RESULTLEN);
+	SHA1Final(result, &ctx->sha1ctx);
+}
+
+static void
+scram_Hi(const char *str, const char *salt, int saltlen, int iterations, uint8 *result)
+{
+	int			str_len = strlen(str);
+	uint32		one = htonl(1);
+	int			i, j;
+	uint8		Ui[SCRAM_KEY_LEN];
+	uint8		Ui_prev[SCRAM_KEY_LEN];
+	scram_HMAC_ctx hmac_ctx;
+
+	/* First iteration */
+	scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+	scram_HMAC_update(&hmac_ctx, salt, saltlen);
+	scram_HMAC_update(&hmac_ctx, (char *) &one, sizeof(uint32));
+	scram_HMAC_final(Ui_prev, &hmac_ctx);
+	memcpy(result, Ui_prev, SCRAM_KEY_LEN);
+
+	/* Subsequent iterations */
+	for (i = 2; i <= iterations; i++)
+	{
+		scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+		scram_HMAC_update(&hmac_ctx, (const char *) Ui_prev, SCRAM_KEY_LEN);
+		scram_HMAC_final(Ui, &hmac_ctx);
+		for (j = 0; j < SCRAM_KEY_LEN; j++)
+			result[j] ^= Ui[j];
+		memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
+	}
+}
+
+
+/*
+ * Calculate SHA-1 hash for a NULL-terminated string. (The NULL terminator is
+ * not included in the hash).
+ */
+void
+scram_H(const uint8 *input, int len, uint8 *result)
+{
+	SHA1_CTX	ctx;
+
+	SHA1Init(&ctx);
+	SHA1Update(&ctx, input, len);
+	SHA1Final(result, &ctx);
+}
+
+static void
+scram_Normalize(const char *password, char *result)
+{
+	/* TODO */
+	strlcpy(result, password, 20);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+					 uint8 *result)
+{
+	/* FIXME: pwbuf needs to be sized properly */
+	char		pwbuf[20];
+
+	scram_Normalize(password, pwbuf);
+	scram_Hi(pwbuf, salt, saltlen, iterations, result);
+}
+
+/*
+ * Calculate ClientKey or ServerKey.
+ */
+void
+scram_ClientOrServerKey(const char *password,
+						const char *salt, int saltlen, int iterations,
+						const char *keystr, uint8 *result)
+{
+	uint8		keybuf[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+
+	scram_SaltedPassword(password, salt, saltlen, iterations, keybuf);
+	scram_HMAC_init(&ctx, keybuf, 20);
+	scram_HMAC_update(&ctx, keystr, strlen(keystr));
+	scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
index daef049..5b72e40 100644
--- a/src/include/catalog/pg_auth_verifiers.h
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -58,5 +58,6 @@ typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
 
 #define AUTH_VERIFIER_PLAIN	'p'		/* plain verifier */
 #define AUTH_VERIFIER_MD5	'm'		/* md5 verifier */
+#define AUTH_VERIFIER_SCRAM	's'		/* SCRAM verifier */
 
 #endif   /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..fce9337
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ *		Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/relpath.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCRAM_COMMON_H
+#define SCRAM_COMMON_H
+
+#include "common/sha1.h"
+
+#define SCRAM_KEY_LEN	SHA1_RESULTLEN
+
+#define SHA1_HMAC_B		64
+
+typedef struct
+{
+	SHA1_CTX	sha1ctx;
+	uint8		k_opad[SHA1_HMAC_B];
+} scram_HMAC_ctx;
+
+extern void scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen);
+extern void scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen);
+extern void scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx);
+
+extern void scram_H(const uint8 *str, int len, uint8 *result);
+extern void scram_ClientOrServerKey(const char *password, const char *salt, int saltlen, int iterations, const char *keystr, uint8 *result);
+
+#endif
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 80f26a8..4469565 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -22,6 +22,11 @@ extern char *pg_krb_realm;
 
 extern void ClientAuthentication(Port *port);
 
+/* Return codes for SASL authentication functions */
+#define	SASL_EXCHANGE_CONTINUE		0
+#define	SASL_EXCHANGE_SUCCESS		1
+#define	SASL_EXCHANGE_FAILURE		2
+
 /* Hook for plugins to get control in ClientAuthentication() */
 typedef void (*ClientAuthentication_hook_type) (Port *, int);
 extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index dfab8f3..1dcf955 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,7 @@
 
 #include "libpq/libpq-be.h"
 
+extern char *get_role_verifier(Oid roleid, char method);
 extern int md5_crypt_verify(const Port *port, const char *role,
 				 char *client_pass, char **logdetail);
 
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 68a953a..a73d2f9 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -24,6 +24,7 @@ typedef enum UserAuth
 	uaIdent,
 	uaPassword,
 	uaMD5,
+	uaSASL,
 	uaGSS,
 	uaSSPI,
 	uaPAM,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 639bf72..462a5dd 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -172,6 +172,8 @@ 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 */
 
 typedef uint32 AuthRequest;
 
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
new file mode 100644
index 0000000..f273392
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ *	  Interface to libpq/scram.c
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/scram.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SCRAM_H
+#define PG_SCRAM_H
+
+extern void *scram_init(const char *username, const char *verifier);
+extern int scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen);
+extern char *scram_build_verifier(char *username, char *password,
+					 int iterations);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fc1679e..295d1c6 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -157,6 +157,10 @@ extern Datum binary_encode(PG_FUNCTION_ARGS);
 extern Datum binary_decode(PG_FUNCTION_ARGS);
 extern unsigned hex_encode(const char *src, unsigned len, char *dst);
 extern unsigned hex_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_encode(const char *src, unsigned len, char *dst);
+extern unsigned b64_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_enc_len(const char *src, unsigned srclen);
+extern unsigned b64_dec_len(const char *src, unsigned srclen);
 
 /* enum.c */
 extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..f52dcc2 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -9,6 +9,7 @@
 /open.c
 /pgstrcasecmp.c
 /pqsignal.c
+/scram-common.c
 /snprintf.c
 /strerror.c
 /strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c2105f1..90b1fee 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,7 @@ LIBS := $(LIBS:-lpgport=)
 
 # We can't use Makefile variables here because the MSVC build system scrapes
 # OBJS from this file.
-OBJS=	fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+OBJS=	fe-auth.o fe-auth-scram.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
 	fe-protocol2.o fe-protocol3.o pqexpbuffer.o fe-secure.o \
 	libpq-events.o
 # libpgport C files we always use
@@ -43,6 +43,9 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
 OBJS += ip.o md5.o
 # utils/mb
 OBJS += encnames.o wchar.o
+# common/
+# FIXME: any reason not to link with libpgcommon?
+OBJS += scram-common.o sha1.o
 
 ifeq ($(with_openssl),yes)
 OBJS += fe-secure-openssl.o
@@ -102,6 +105,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
 encnames.c wchar.c: % : $(backend_src)/utils/mb/%
 	rm -f $@ && $(LN_S) $< .
 
+scram-common.c sha1.c: % : $(top_srcdir)/src/common/%
+	rm -f $@ && $(LN_S) $< .
+
 
 distprep: libpq-dist.rc
 
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
new file mode 100644
index 0000000..f57308f
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,476 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ *	   The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		NONCE_SENT,
+		PROOF_SENT,
+		FINISHED
+	} state;
+
+	const char *username;
+	const char *password;
+
+	char	   *client_first_message_bare;
+
+	/* These come from the server-first message */
+	char	   *server_first_message;
+	char	   *salt;
+	int			saltlen;
+	int			iterations;
+	char	   *server_nonce;
+
+	/* These come from the server-final message */
+	char	   *server_proof;
+} fe_scram_state;
+
+static bool read_server_first_message(fe_scram_state *state, char *input, PQExpBuffer errormessage);
+static bool read_server_final_message(fe_scram_state *state, char *input);
+static char *build_client_first_message(fe_scram_state *state);
+static char *build_client_final_message(fe_scram_state *state);
+static bool verify_server_proof(fe_scram_state *state);
+static void generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state, const char *client_final_message_without_proof, uint8 *result);
+
+static unsigned b64_decode(const char *src, unsigned len, char *dst);
+static unsigned b64_encode(const char *src, unsigned len, char *dst);
+static unsigned b64_enc_len(const char *src, unsigned srclen);
+static unsigned b64_dec_len(const char *src, unsigned srclen);
+
+void *
+pg_fe_scram_init(const char *username, const char *password)
+{
+	fe_scram_state *state;
+
+	state = (fe_scram_state *) malloc(sizeof(fe_scram_state));
+	if (!state)
+		return NULL;
+	memset(state, 0, sizeof(fe_scram_state));
+	state->state = INIT;
+	state->username = username;
+	state->password = password;
+
+	return state;
+}
+
+void
+pg_fe_scram_free(void *opaq)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	if (state->client_first_message_bare)
+		free(state->client_first_message_bare);
+
+	if (state->server_first_message)
+		free(state->server_first_message);
+	if (state->salt)
+		free(state->salt);
+	if (state->server_nonce)
+		free(state->server_nonce);
+	if (state->server_proof)
+		free(state->server_proof);
+
+	free(state);
+}
+
+/*
+ * TODO: Even though SCRAM will authenticate the server, by verifying the
+ * server-proof, we don't currently store that information anywhere. Nothing
+ * stops the server from leaving the SASL exchange unfinished, and just send an
+ * AuthenticationOK message, leaving the client unsure of the server's
+ * identity.
+ */
+void
+pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	*done = false;
+	*success = false;
+	*output = NULL;
+	*outputlen = 0;
+
+	switch (state->state)
+	{
+		case INIT:
+			/* send client nonce */
+			*output = build_client_first_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = NONCE_SENT;
+			break;
+
+		case NONCE_SENT:
+			/* receive salt and server nonce, send response */
+			read_server_first_message(state, input, errorMessage);
+			*output = build_client_final_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = PROOF_SENT;
+			break;
+
+		case PROOF_SENT:
+			/* receive server proof, and verify it */
+			read_server_final_message(state, input);
+			*success = verify_server_proof(state);
+			*done = true;
+			state->state = FINISHED;
+			break;
+
+		default:
+			/* shouldn't happen */
+			*done = true;
+			*success = false;
+			printfPQExpBuffer(errorMessage, "invalid SCRAM exchange state");
+	}
+}
+
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+	char		nonce[10 + 1];
+	char	   *buf;
+	char		msglen;
+
+	generate_nonce(nonce, 10);
+
+	msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+
+	buf = malloc(msglen + 1);
+	snprintf(buf, msglen + 1, "n,,n=%s,r=%s", state->username, nonce);
+
+	state->client_first_message_bare = strdup(buf + 3);
+	if (!state->client_first_message_bare)
+		return NULL;
+
+	return buf;
+}
+
+static bool
+read_server_first_message(fe_scram_state *state, char *input, PQExpBuffer errormessage)
+{
+	char	   *iterations_str;
+	char	   *endptr;
+	char	   *encoded_salt;
+
+	state->server_first_message = strdup(input);
+	if (!state->server_first_message)
+		return false;
+
+	/* parse the message */
+	state->server_nonce = strdup(read_attr_value(&input, 'r', errormessage));
+	if (state->server_nonce == NULL)
+		return false;
+
+	encoded_salt = read_attr_value(&input, 's', errormessage);
+	if (encoded_salt == NULL)
+		return false;
+	state->salt = malloc(b64_dec_len(encoded_salt, strlen(encoded_salt)));
+	if (state->salt == NULL)
+		return false;
+	state->saltlen = b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+	if (state->saltlen <= 0)
+		return false;
+
+	iterations_str = read_attr_value(&input, 'i', errormessage);
+	if (iterations_str == NULL)
+		return false;
+	state->iterations = strtol(iterations_str, &endptr, 10);
+	if (*endptr != '\0')
+		return false;
+
+	if (*input != '\0')
+		return false;
+
+	return true;
+}
+
+static bool
+read_server_final_message(fe_scram_state *state, char *input)
+{
+	/* TODO: verify ServerSignature */
+	return true;
+}
+
+static char *
+build_client_final_message(fe_scram_state *state)
+{
+	char		client_final_message_without_proof[200];
+	uint8		client_proof[SCRAM_KEY_LEN];
+	char		client_proof_base64[SCRAM_KEY_LEN * 2 + 1];
+	int			client_proof_len;
+	char		buf[300];
+
+	snprintf(client_final_message_without_proof, sizeof(client_final_message_without_proof),
+			 "c=biws,r=%s", state->server_nonce);
+
+	calculate_client_proof(state, client_final_message_without_proof, client_proof);
+	if (b64_enc_len((char *) client_proof, SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+		return NULL;
+
+	client_proof_len = b64_encode((char *) client_proof, SCRAM_KEY_LEN, client_proof_base64);
+	client_proof_base64[client_proof_len] = '\0';
+
+	snprintf(buf, sizeof(buf), "%s,p=%s", client_final_message_without_proof, client_proof_base64);
+	fprintf(stderr, "final_msg: %s\n", buf);
+
+	return strdup(buf);
+}
+
+static void
+calculate_client_proof(fe_scram_state *state, const char *client_final_message_without_proof, uint8 *result)
+{
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	int			i;
+	scram_HMAC_ctx ctx;
+
+	scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+							state->iterations, "Client Key", ClientKey);
+	scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+	scram_HMAC_init(&ctx, StoredKey, 20);
+	scram_HMAC_update(&ctx, state->client_first_message_bare, strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx, state->server_first_message, strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx, client_final_message_without_proof, strlen(client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+
+	fprintf(stderr, "ClientSignature: %02X%02X\n", ClientSignature[0], ClientSignature[1]);
+
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+	/* TODO */
+	return true;
+}
+
+
+/*
+ * TODO: We need a source of randomness. libpq doesn't currently have one.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+	int			i;
+
+	for (i = 0; i < len; i++)
+	{
+		buf[i] = 'x';
+	}
+	buf[i] = '\0';
+}
+
+
+/*
+ * BASE64
+ */
+
+static const char _base64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static const int8 b64lookup[128] = {
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+};
+
+static unsigned
+b64_encode(const char *src, unsigned len, char *dst)
+{
+	char	   *p,
+			   *lend = dst + 76;
+	const char *s,
+			   *end = src + len;
+	int			pos = 2;
+	uint32		buf = 0;
+
+	s = src;
+	p = dst;
+
+	while (s < end)
+	{
+		buf |= (unsigned char) *s << (pos << 3);
+		pos--;
+		s++;
+
+		/* write it out */
+		if (pos < 0)
+		{
+			*p++ = _base64[(buf >> 18) & 0x3f];
+			*p++ = _base64[(buf >> 12) & 0x3f];
+			*p++ = _base64[(buf >> 6) & 0x3f];
+			*p++ = _base64[buf & 0x3f];
+
+			pos = 2;
+			buf = 0;
+		}
+		if (p >= lend)
+		{
+			*p++ = '\n';
+			lend = p + 76;
+		}
+	}
+	if (pos != 2)
+	{
+		*p++ = _base64[(buf >> 18) & 0x3f];
+		*p++ = _base64[(buf >> 12) & 0x3f];
+		*p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+		*p++ = '=';
+	}
+
+	return p - dst;
+}
+
+static unsigned
+b64_decode(const char *src, unsigned len, char *dst)
+{
+	const char *srcend = src + len,
+			   *s = src;
+	char	   *p = dst;
+	char		c;
+	int			b = 0;
+	uint32		buf = 0;
+	int			pos = 0,
+				end = 0;
+
+	while (s < srcend)
+	{
+		c = *s++;
+
+		if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+			continue;
+
+		if (c == '=')
+		{
+			/* end sequence */
+			if (!end)
+			{
+				if (pos == 2)
+					end = 1;
+				else if (pos == 3)
+					end = 2;
+				else
+				{
+					return 0;
+					//ereport(ERROR,
+					//		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					//		 errmsg("unexpected \"=\" while decoding base64 sequence")));
+				}
+			}
+			b = 0;
+		}
+		else
+		{
+			b = -1;
+			if (c > 0 && c < 127)
+				b = b64lookup[(unsigned char) c];
+			if (b < 0)
+			{
+				//ereport(ERROR,
+				//		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				//		 errmsg("invalid symbol '%c' while decoding base64 sequence", (int) c)));
+				return 0;
+			}
+		}
+		/* add it to buffer */
+		buf = (buf << 6) + b;
+		pos++;
+		if (pos == 4)
+		{
+			*p++ = (buf >> 16) & 255;
+			if (end == 0 || end > 1)
+				*p++ = (buf >> 8) & 255;
+			if (end == 0 || end > 2)
+				*p++ = buf & 255;
+			buf = 0;
+			pos = 0;
+		}
+	}
+
+	if (pos != 0)
+	{
+		//ereport(ERROR,
+		//		(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+		//		 errmsg("invalid base64 end sequence"),
+		//		 errhint("Input data is missing padding, truncated, or otherwise corrupted.")));
+		return 0;
+	}
+
+	return p - dst;
+}
+
+
+static unsigned
+b64_enc_len(const char *src, unsigned srclen)
+{
+	/* 3 bytes will be converted to 4, linefeed after 76 chars */
+	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
+}
+
+static unsigned
+b64_dec_len(const char *src, unsigned srclen)
+{
+	return (srclen * 3) >> 2;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 5891c75..6e3c921 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -428,6 +428,74 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
 }
 #endif   /* ENABLE_SSPI */
 
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+	/*
+	 * Check the authentication mechanism (only SCRAM-SHA-1 is supported at
+	 * the moment.)
+	 */
+	if (strcmp(conn->auth_req_inbuf, "SCRAM-SHA-1") == 0)
+	{
+		conn->password_needed = true;
+		if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  PQnoPasswordSupplied);
+			return STATUS_ERROR;
+		}
+		conn->sasl_state = pg_fe_scram_init(conn->pguser, conn->pgpass);
+		if (!conn->sasl_state)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("out of memory\n"));
+			return STATUS_ERROR;
+		}
+		else
+			return STATUS_OK;
+	}
+	else
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("SASL authentication mechanism %s not supported\n"),
+						  (char *) conn->auth_req_inbuf);
+		return STATUS_ERROR;
+	}
+}
+
+static int
+pg_SASL_exchange(PGconn *conn)
+{
+	char	   *output;
+	int			outputlen;
+	bool		done;
+	bool		success;
+	int			res;
+
+	pg_fe_scram_exchange(conn->sasl_state,
+						 conn->auth_req_inbuf, conn->auth_req_inlen,
+						 &output, &outputlen,
+						 &done, &success, &conn->errorMessage);
+	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);
+
+		if (res != STATUS_OK)
+			return STATUS_ERROR;
+	}
+
+	if (done && !success)
+		return STATUS_ERROR;
+
+	return STATUS_OK;
+}
+
 /*
  * Respond to AUTH_REQ_SCM_CREDS challenge.
  *
@@ -696,6 +764,33 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
 			}
 			break;
 
+		case AUTH_REQ_SASL:
+			/*
+			 * The request contains the name (as assigned by IANA) of the
+			 * authentication mechanism.
+			 */
+			if (pg_SASL_init(conn, conn->auth_req_inbuf) != STATUS_OK)
+			{
+				/* pg_SASL_init already set the error message */
+				return STATUS_ERROR;
+			}
+			/* fall through */
+
+		case AUTH_REQ_SASL_CONT:
+			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) != STATUS_OK)
+			{
+				printfPQExpBuffer(&conn->errorMessage,
+					 "fe_sendauth: error sending password authentication\n");
+				return STATUS_ERROR;
+			}
+			break;
+
 		case AUTH_REQ_SCM_CREDS:
 			if (pg_local_sendauth(conn) != STATUS_OK)
 				return STATUS_ERROR;
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 8d35767..b1b0294 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -18,7 +18,15 @@
 #include "libpq-int.h"
 
 
+/* Prototypes for functions in fe-auth.c */
 extern int	pg_fe_sendauth(AuthRequest areq, PGconn *conn);
 extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 
+/* Prototypes for functions in fe-auth-scram.c */
+extern void *pg_fe_scram_init(const char *username, const char *password);
+extern void pg_fe_scram_free(void *opaq);
+extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage);
+
 #endif   /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a45f4cb..52ae7fc 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2479,6 +2479,46 @@ keep_going:						/* We will come back to here until there is
 					}
 				}
 #endif
+				/* Get additional payload, if any */
+				if (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 GSSAPI buffer (%d)"),
+											  llen);
+							goto error_return;
+						}
+					}
+
+					if (pqGetnchar(conn->auth_req_inbuf, llen, conn))
+					{
+						/* We'll come back when there is 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
@@ -3035,6 +3075,15 @@ closePGconn(PGconn *conn)
 		conn->sspictx = NULL;
 	}
 #endif
+	if (conn->sasl_state)
+	{
+		/*
+		 * XXX: if we add support for more authentication mechanisms, this
+		 * needs to call the right 'free' function.
+		 */
+		pg_fe_scram_free(conn->sasl_state);
+		conn->sasl_state = NULL;
+	}
 }
 
 /*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 2175957..391192b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -421,7 +421,12 @@ 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 SSL, GSS, etc */
+	void	   *sasl_state;
 
 #ifdef USE_SSL
 	bool		allow_ssl_try;	/* Allowed to try SSL negotiation */
diff --git a/src/interfaces/libpq/sha1.c b/src/interfaces/libpq/sha1.c
new file mode 120000
index 0000000..cd1c060
--- /dev/null
+++ b/src/interfaces/libpq/sha1.c
@@ -0,0 +1 @@
+../../../src/common/sha1.c
\ No newline at end of file
-- 
2.5.0

#7Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#6)
Re: WIP: SCRAM authentication

On Fri, Aug 7, 2015 at 3:22 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Tue, Aug 4, 2015 at 4:20 PM, Michael Paquier wrote:

I have been looking more in depths at this one, which adds essential
infrastructure to support multiple authentication hashes for more protocols.
Here are some comments:
[spec lines]
I am willing to write a patch for the next CF following more or less those
lines, depending of course on the outcome of the discussion we can have
here, so feel free to comment.

OK, attached as 0001 is the patch that respects those lines for the
support of multiple password verifiers in system catalogs. I have
added a new catalog called pg_auth_verifiers that is used at
authentication to fetch a password value depending on the protocol
used. With only this patch attached there are two types of verifiers:
plain and md5. This new catalog is REVOKE'd like pg_authid (pg_authid
could be made readable be this seems sensitive to me so I am not
changing it).

I have as well done the following things:
- Added PASSWORD VERIFIER (md5 = 'hoge', plain = 'hoge') which is used
as well by pg_dump all to be able to specify password verifiers one by
one.

Maybe I'm chiming in too late here but I am sorta unimpressed by this.
If the user's password is stored both MD5-hashed and hashed some other
way in the system catalogs, that's less secure than storing it in the
least secure of those ways. And I'm afraid that if we introduce this
new mechanism, we won't really gain any security, because everybody
will just pg_dump or pg_upgrade and the old passwords will stick
around in the system forever. In fact we might lose security if
somebody changes one password verifier but doesn't realize that the
other one is still floating around, memorializing the old password,
and still available to be used for login.

I think we should look for a solution that either (a) allows SCRAM
authentication without requiring any changes to the contents of
pg_authid, like what Heikki proposed before; or (b) forces a hard
break, where at each password change you can decide if you want the
old or new format (probably based on the current value of some
compatibility GUC).

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Robert Haas (#7)
Re: WIP: SCRAM authentication

On 08/07/2015 09:26 PM, Robert Haas wrote:

Maybe I'm chiming in too late here but I am sorta unimpressed by this.
If the user's password is stored both MD5-hashed and hashed some other
way in the system catalogs, that's less secure than storing it in the
least secure of those ways. And I'm afraid that if we introduce this
new mechanism, we won't really gain any security, because everybody
will just pg_dump or pg_upgrade and the old passwords will stick
around in the system forever. In fact we might lose security if
somebody changes one password verifier but doesn't realize that the
other one is still floating around, memorializing the old password,
and still available to be used for login.

Yeah, that's certainly a risk. You wouldn't want to keep around
verifiers for authentication methods you don't use.

I think we should look for a solution that either (a) allows SCRAM
authentication without requiring any changes to the contents of
pg_authid, like what Heikki proposed before; or (b) forces a hard
break, where at each password change you can decide if you want the
old or new format (probably based on the current value of some
compatibility GUC).

Yeah, something to force a hard break when you want it would be really
good. Perhaps a command you can run to remove all MD5 hashes, or at
least find all the roles that have them. And a GUC to disallow creating
new ones.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Michael Paquier
michael.paquier@gmail.com
In reply to: Heikki Linnakangas (#8)
Re: WIP: SCRAM authentication

On Sat, Aug 8, 2015 at 3:45 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 08/07/2015 09:26 PM, Robert Haas wrote:

Maybe I'm chiming in too late here but I am sorta unimpressed by this.
If the user's password is stored both MD5-hashed and hashed some other
way in the system catalogs, that's less secure than storing it in the
least secure of those ways. And I'm afraid that if we introduce this
new mechanism, we won't really gain any security, because everybody
will just pg_dump or pg_upgrade and the old passwords will stick
around in the system forever. In fact we might lose security if
somebody changes one password verifier but doesn't realize that the
other one is still floating around, memorializing the old password,
and still available to be used for login.

Yeah, that's certainly a risk. You wouldn't want to keep around verifiers
for authentication methods you don't use.

Yep, I cannot refute that. And there is actually the same problem with
the first version of the patch proposed on this thread if that's what
you are referring at below.

I think we should look for a solution that either (a) allows SCRAM
authentication without requiring any changes to the contents of
pg_authid, like what Heikki proposed before; or (b) forces a hard
break, where at each password change you can decide if you want the
old or new format (probably based on the current value of some
compatibility GUC).

FWIW, the patch resets all the existing entries should any
CREATE/ALTER ROLE involving a password should be run, even if
pg_auth_verifiers has entries for method not specified with PASSWORD
VERIFIERS.

Yeah, something to force a hard break when you want it would be really good.
Perhaps a command you can run to remove all MD5 hashes, or at least find all
the roles that have them. And a GUC to disallow creating new ones.

This filtering machinery definitely looks like a GUC to me, something
like password_forbidden_encryption that PASSWORD VERIFIERS looks at
and discards the methods listed in there. This definitely needs to be
separated from password_encryption.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#9)
Re: WIP: SCRAM authentication

On Fri, Aug 7, 2015 at 6:54 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

This filtering machinery definitely looks like a GUC to me, something
like password_forbidden_encryption that PASSWORD VERIFIERS looks at
and discards the methods listed in there. This definitely needs to be
separated from password_encryption.

I don't know what a "password verifier" is and I bet nobody else does
either. Well, I think I sort of know: I think it's basically an
encrypted password. Am I right? Even if I am, I bet the average user
is going to scratch their head and punt.

I don't see that there's any good reason to allow the same password to
be stored in the catalog encrypted more than one way, and I don't
think there's any good reason to introduce the PASSWORD VERIFIER
terminology. I think we should store (1) your password, either
encrypted or unencrypted; and (2) the method used to encrypt it. And
that's it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Joe Conway
mail@joeconway.com
In reply to: Robert Haas (#10)
Re: WIP: SCRAM authentication

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 08/08/2015 06:27 AM, Robert Haas wrote:

terminology. I think we should store (1) your password, either
encrypted or unencrypted; and (2) the method used to encrypt it.
And that's it.

A petty complaint, but it has always bothered me that we say the
password is encrypted when, at least currently, it is a simple hash
(cryptographic hash yes, but not encrypted, and not even an HMAC). I
think we should try to start using accurate terminology.

- --
Joe Conway
Crunchy Data
Enterprise PostgreSQL
http://crunchydata.com
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (GNU/Linux)

iQIcBAEBAgAGBQJVxhm7AAoJEDfy90M199hlWRsQAIPliVReOYRRD/BJaZlB9Vjs
4YDolZZD9zR2+VPNxG/VaGHJ68rXlnfU/P0GrIQrS67t1xgwPxbUW6TCBsXJDnIE
wo7i5mJn9yn+AowccFiZTToKK8oNjRd33OJ2q00lAGiuaksnBhcJjMCNUHqf1Oz2
rUA/YiTp7RHXOQfiAxSoMKytK2y+rnQA+rnvPiE7XLKYE9rZ5rLiGhV0MPaNOFms
aHZIcYX5Tl2I3RsCexLMMA1qM001wSTyoti7o9gL71EXLV6ea6xt10a++k6oJ19y
oU7WjwKgV2XOGlQNC3/rUEKvuAtQhTlJpx9Q6xmTYidN0QHkZDdpJUblGZoxR2Vz
lT2zZdcpDhENynFZ1nTsd+CNWsn5T5vTVgnuKpG5qIMgT+kSG2JeiS7h+RY4rRtk
bl08tZmQBUBu/3hrRxQVPrt1NISteKXem2OLGphIKQEOmu/Kf43msYHQ+1qY0FTB
TZ96tVJnYTjQZp2P0IdjMf0qpOzK8qkMx2Tb6WehMd9yD1DtxQyKmxGpvssgEmQ7
1n3L/HCKWXF0MbI8QefIsO70ft4hzib5V+G7YmF00dWQM7NhDZYf6ejn1WmCP26u
w9wOHQcCAAKPI2knh3k2Ngdynl8Gofkxr7Le+NW7TGM+bp2U5EStTEH0r70mzEIg
KvB4dWX+tlZowujUmFhL
=VDCN
-----END PGP SIGNATURE-----

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#10)
Re: WIP: SCRAM authentication

* Robert Haas (robertmhaas@gmail.com) wrote:

On Fri, Aug 7, 2015 at 6:54 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

This filtering machinery definitely looks like a GUC to me, something
like password_forbidden_encryption that PASSWORD VERIFIERS looks at
and discards the methods listed in there. This definitely needs to be
separated from password_encryption.

I don't know what a "password verifier" is and I bet nobody else does
either. Well, I think I sort of know: I think it's basically an
encrypted password. Am I right? Even if I am, I bet the average user
is going to scratch their head and punt.

Password verifier is actually a well understood term when it comes to
these protocols and their implementations. It is not an encrypted
password but rather a value which allows the server to determine if the
client knows the correct password, without having to store the password
directly, or a simple hash of the password, or have the clear password
sent from the client sent to the server.

I don't see that there's any good reason to allow the same password to
be stored in the catalog encrypted more than one way, and I don't
think there's any good reason to introduce the PASSWORD VERIFIER
terminology. I think we should store (1) your password, either
encrypted or unencrypted; and (2) the method used to encrypt it. And
that's it.

Perhaps we don't want to expose what a password verifier is to users,
but we shouldn't be missing the distinction between hashed passwords,
encrypted passwords, and password verifiers in the code and in the
implementation of SCRAM. We really shouldn't use an incorrect term for
what we're storing in pg_authid either though, which is what we do
today.

I can't see us ever storing encrypted passwords as that implies we'd
need a key stored somewhere and further that the server would be able to
get back to the user's original password, neither of which are things we
want to deal with.

You do have a good point that there is some risk associated with having
multiple values in pg_authid related to a user's password and that we
really want to help users move from the old value in pg_authid to the
new one. I don't believe we should force a hard change as it's going to
cause a lot of trouble for users. We have to also consider that clients
also have to be changed for this.

As discussed previously, in an ideal world, we would handle the old
values and the new ones while introducing password ageing, client
support for detecting that a password needs to be changed, protocol
support for changing passwords which avoids having them get logged in
cleartext to the server log, password complexity, and perhaps used
password history to boot. The main issue here is that we really don't
provide any help for large installations to get their userbase moved off
of the old style today. Password ageing (and good support for it in
clients, etc), would help that greatly.

Unfortunately, it's unlikely that we're going to get all of that done in
one release. As such, I'd suggest our next release support the existing
values in pg_authid, add the password verifier when the password is
changed, and then add a GUC in the following release which disables the
old pg_authid mechanism, defaulting to true, and the release after that
remove support for the old value and the field for it completely.

We should also provide documentation about how to check if there are any
old style values, for users who want to be proactive about moving off of
the old style.

I'm travelling and so I haven't looked over the patch yet (or even read
the entire thread in depth), so apologies if I've got something confused
about what's being proposed.

Thanks!

Stephen

#13Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Robert Haas (#10)
Re: WIP: SCRAM authentication

On 08/08/2015 04:27 PM, Robert Haas wrote:

I don't see that there's any good reason to allow the same password to
be stored in the catalog encrypted more than one way,

Sure there is. If you want to be able to authenticate using different
mechanism, you need the same password "encrypted" in different ways.
SCRAM uses verifier that's derived from the password in one way, MD5
authentication needs an MD5 hash, and yet other protocols have other
requirements.

and I don't think there's any good reason to introduce the PASSWORD
VERIFIER terminology. I think we should store (1) your password,
either encrypted or unencrypted; and (2) the method used to encrypt
it. And that's it.

Like Joe and Stephen, I actually find it highly confusing that we call
the MD5 hash an "encrypted password". The term "password verifier" is
fairly common in the specifications of authentication mechanisms. I
think we should adopt it.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Greg Stark
stark@mit.edu
In reply to: Heikki Linnakangas (#13)
Re: WIP: SCRAM authentication

On Sat, Aug 8, 2015 at 6:23 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Like Joe and Stephen, I actually find it highly confusing that we call the
MD5 hash an "encrypted password". The term "password verifier" is fairly
common in the specifications of authentication mechanisms. I think we should
adopt it.

Speaking as someone who hasn't read the specifications I found
"password verifier" surprising. I would have known what "password
hash" was but I misread "verifier" to be something functional like a
PAM plugin. I tend to agree we should just use terminology out of the
specs though even if it's a little opaque, better one opaque piece of
terminology than having to learn and translate between multiple
terminologies.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Josh Berkus
josh@agliodbs.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On 08/08/2015 10:23 AM, Heikki Linnakangas wrote:

On 08/08/2015 04:27 PM, Robert Haas wrote:

I don't see that there's any good reason to allow the same password to
be stored in the catalog encrypted more than one way,

Sure there is. If you want to be able to authenticate using different
mechanism, you need the same password "encrypted" in different ways.
SCRAM uses verifier that's derived from the password in one way, MD5
authentication needs an MD5 hash, and yet other protocols have other
requirements.

That's correct. However, one of the goals of implementing SCRAM
authentication is to allow security-conscious users to get rid of those
reusable md5 hashes, no?

Obviously the backwards-compatibility issues are pretty major ... it'll
be years before all drivers support SCRAM ... but we also want to
provide a path forwards for secure installations in which no md5 hashes
are stored.

This says "backwards-compatible GUC" to me. Here's one idea on how to
handle this:

1. we drop the parameter password_encryption

2. we add the parameter password_storage, which takes a list:
- plain : plain text
- md5 : current md5 hashes
- scram : new scram hashed passwords
This defaults to 'md5, scram' if not specified.
This list might be extended in the future.

3. All password types in the list are generated. This means having
multiple columns in pg_shadow, or an array. An array would support the
addition of future password storage methods.

4. CREATE ROLE / ALTER ROLE syntax is changed to accept a parameter to
ENCRYPTED in order to support md5, scram, and future methods. If no
parameter is supplied, ENCRYPTED will default to 'md5, scram'.

5. we add the superuser-only function pg_apply_password_policy(). This
applies the policy expressed by password_storage, generating or erasing
passwords for each user.

6. We add a new connection error for "authentication __method__ not
supported for user"

7. Two versions from now, we change the defaults.

I thought about the idea of determining password storage based on what's
in pg_hba.conf, but that seems like way too much implied authorization
to me, and liable to be a big foot-gun.

--Josh Berkus

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Michael Paquier
michael.paquier@gmail.com
In reply to: Josh Berkus (#15)
Re: WIP: SCRAM authentication

On Sun, Aug 9, 2015 at 6:51 AM, Josh Berkus <josh@agliodbs.com> wrote:

Obviously the backwards-compatibility issues are pretty major ... it'll
be years before all drivers support SCRAM ... but we also want to
provide a path forwards for secure installations in which no md5 hashes
are stored.

This says "backwards-compatible GUC" to me. Here's one idea on how to
handle this:

1. we drop the parameter password_encryption
2. we add the parameter password_storage, which takes a list:
- plain : plain text
- md5 : current md5 hashes
- scram : new scram hashed passwords
This defaults to 'md5, scram' if not specified.
This list might be extended in the future.

Perhaps using a different GUC than password_encryption is safer... I
am not that sure. Still that's how I switched password_encryption to
actually handle a list. Default is 'md5' with the first patch, and
'md5,scram' with the scram patch added and it sets the list of
password verifiers created when PASSWORD with ENCRYPTED/UNENCRYPTED is
used.

3. All password types in the list are generated. This means having
multiple columns in pg_shadow, or an array. An array would support the
addition of future password storage methods.

Yeah, the patch switches pg_shadow to an array like that, with as
elements method:value, so you get actually md5:md5blah,scram:stuff in
all the patches applied.

4. CREATE ROLE / ALTER ROLE syntax is changed to accept a parameter to
ENCRYPTED in order to support md5, scram, and future methods. If no
parameter is supplied, ENCRYPTED will default to 'md5, scram'.

Like password ENCRYPTED (md5,scram) or similar? If no method is
passed, I think that we should default to password_storage instead.
Also, I still think that something like PASSWORD VERIFIERS is needed,
users may want to set the verifier user for each method after
calculating it on client-side: we authorize that for md5 even now, and
that's not something this spec authorizes.

5. we add the superuser-only function pg_apply_password_policy(). This
applies the policy expressed by password_storage, generating or erasing
passwords for each user.

pg_upgrade could make use of that to control password aging with an
option to do the cleanup or not. Not sure what the default should be
though. pg_apply_password_policy(roleid) would be useful as well to do
it on a role base.

6. We add a new connection error for "authentication __method__ not
supported for user"

Hm? This would let any user trying to connect with a given method know
that if a method is used or not. What's wrong with failing as we do
now. In case of PASSWORD NULL for example, an attempt of connection
fails all the time with "incorrect password" or similar.

7. Two versions from now, we change the defaults.

Or three. We cannot expect users to change immediately, and it is
wiser to let dust set on the new feature in case critical bugs show up
after the first GA.

Something that sounds more like a detail in this thread by reading
other comments: I think that it is important to store password
verifiers in a different catalog than pg_authid for two reasons:
- that's more user-friendly, a sysadmin could directly join the new
catalog with pg_authid to get all the verifiers for a single user
method
- at password lookup when authorizing connection, there is no need to
fetch all the password verifiers and parse the array with all
verifiers.
- more scalable if we have many verifier methods in the future, though
we are not going to have hundreds of them. Though I am wondering about
per-method validtime and per-method authorization options.
Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Sehrope Sarkuni
sehrope@jackdb.com
In reply to: Michael Paquier (#16)
Re: WIP: SCRAM authentication

It'd be nice if the new auth mechanism supports multiple passwords in the
same format as well (not just one per format).

That way you could have two different passwords for a user that are active
at the same time. This would simplify rolling database credentials as it
wouldn't have to be done all at once. You could add the new credentials,
update your app servers one by one, then disable the old ones.

A lot of systems that use API keys let you see the last time a particular
set of keys was used. This helps answer the "Is this going to break
something if I disable it?" question. Having a last used at timestamp for
each auth mechanism (per user) would be useful.

I'm not sure how updates should work when connecting to a read-only slave
though. It would need some way of letting the master know that user X
connected using credentials Y.

Regards,
-- Sehrope Sarkuni
Founder & CEO | JackDB, Inc. | https://www.jackdb.com/

#18Josh Berkus
josh@agliodbs.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On 08/08/2015 03:21 PM, Michael Paquier wrote:

On Sun, Aug 9, 2015 at 6:51 AM, Josh Berkus <josh@agliodbs.com> wrote:

1. we drop the parameter password_encryption
2. we add the parameter password_storage, which takes a list:
- plain : plain text
- md5 : current md5 hashes
- scram : new scram hashed passwords
This defaults to 'md5, scram' if not specified.
This list might be extended in the future.

Perhaps using a different GUC than password_encryption is safer... I
am not that sure. Still that's how I switched password_encryption to
actually handle a list. Default is 'md5' with the first patch, and
'md5,scram' with the scram patch added and it sets the list of
password verifiers created when PASSWORD with ENCRYPTED/UNENCRYPTED is
used.

Well, generally I feel like if we're going to change the *type* of a GUC
parameter, we ought to change the *name*. It's far easier for users to
figure out that the contents of a parameter need to change if the name
is also changed.

In other words, I think "invalid parameter 'password_encryption'" is an
easier to understand error message than "invalid password_encryption
type 'on'". Besides which, password_encryption was always a misnomer.

Unless you're going to still accept "on, off" in some kind of wierd
backwards-compatibitlity mode? If so, how does that work?

Like password ENCRYPTED (md5,scram) or similar? If no method is
passed, I think that we should default to password_storage instead.

Make sense.

Also, I still think that something like PASSWORD VERIFIERS is needed,
users may want to set the verifier user for each method after
calculating it on client-side: we authorize that for md5 even now, and
that's not something this spec authorizes.

I don't follow this. Mind you, I'm not sure that I need to.

5. we add the superuser-only function pg_apply_password_policy(). This
applies the policy expressed by password_storage, generating or erasing
passwords for each user.

pg_upgrade could make use of that to control password aging with an
option to do the cleanup or not. Not sure what the default should be
though. pg_apply_password_policy(roleid) would be useful as well to do
it on a role base.

No objections to an optional roleid parameter, if you think people will
use it.

6. We add a new connection error for "authentication __method__ not
supported for user"

Hm? This would let any user trying to connect with a given method know
that if a method is used or not. What's wrong with failing as we do
now. In case of PASSWORD NULL for example, an attempt of connection
fails all the time with "incorrect password" or similar.

So, the DBA sets password_storage = 'scram', but doesn't take the md5
lines out of pg_hba.conf.

The app dev tries to connect using a driver which only supports md5.
What error should they get? A user/DBA who is getting "invalid
password" is going to spend a long time debugging it. Also, it would be
very useful to have a distinctive error in the log, so that DBAs could
see who is *trying* to connect with the wrong verifier.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#13)
Re: WIP: SCRAM authentication

On Sat, Aug 8, 2015 at 1:23 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 08/08/2015 04:27 PM, Robert Haas wrote:

I don't see that there's any good reason to allow the same password to
be stored in the catalog encrypted more than one way,

Sure there is. If you want to be able to authenticate using different
mechanism, you need the same password "encrypted" in different ways. SCRAM
uses verifier that's derived from the password in one way, MD5
authentication needs an MD5 hash, and yet other protocols have other
requirements.

Why do we need to be able to authenticate using more than one
mechanism? If you have some clients that can't support SCRAM yet, you
might as well continue using MD5 across the board until that changes.
You're not going to get much real security out of using MD5 for some
authentication attempts and SCRAM for other ones, and the amount of
infrastructure we're proposing to introduce to support that is pretty
substantial.

and I don't think there's any good reason to introduce the PASSWORD
VERIFIER terminology. I think we should store (1) your password,
either encrypted or unencrypted; and (2) the method used to encrypt
it. And that's it.

Like Joe and Stephen, I actually find it highly confusing that we call the
MD5 hash an "encrypted password". The term "password verifier" is fairly
common in the specifications of authentication mechanisms. I think we should
adopt it.

OK, but it sure looked from Michael's syntax description like you
wrote PASSWORD VERIFIER (md5 'the_actual_password'). Or at least
that was my impression from reading it, maybe I got it wrong. If you
want to introduce ALTER USER ... PASSWORD VERIFIER as alternative
syntax for what we now call ALTER USER ... ENCRYPTED PASSWORD, that
works for me. But a plaintext password shouldn't be called a password
verifier under the terminology you are using here, IIUC.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Josh Berkus
josh@agliodbs.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On 08/09/2015 08:09 AM, Robert Haas wrote:

Why do we need to be able to authenticate using more than one
mechanism? If you have some clients that can't support SCRAM yet, you
might as well continue using MD5 across the board until that changes.
You're not going to get much real security out of using MD5 for some
authentication attempts and SCRAM for other ones,

Speaking as someone who has sheperded several clients through
infrastructure upgrades, I have to disagree with this.

First, people don't upgrade large infrastructures with multiple
applications, ETL processes and APIs which connect with the database all
at once. They do it one component at a time, verify that component is
working, and then move on to the next one. Even within a single
application, there could be many servers to upgrade, and you can't do
them all simultaneously.

Now, for shops where they've had the foresight to set up group roles
which own objects so that a new user with SCRAM can be assigned in the
group role, this is no problem. But for the other 98% of our large-app
users, setting up that kind of infrastructure would itself require a
weekend-long downtime, due to the locking required to reassign object
permissions and all of the app testing required.

Second, you're forgetting hosted PostgreSQL, where there may be only one
user available to each database owner. So assigning a new login role
for SCRAM isn't even an option.

Plus all of the above requires that some login roles have a SCRAM
verifier, and others have MD5, for some period. Even if we don't
support multiple verifiers for one login, that still means we need to
deal with "what verifier gets created for a new role" and the required
support functions and GUCs for that. Switching "across the board" on a
per-installation basis is a complete nonstarter for any running application.

Frankly, switching on a per-postmaster basis isn't even worth discussing
in my book, because some languages/platforms will take years longer than
others to support SCRAM.

Overall, it's to the PostgreSQL project's benefit to have users switch
to SCRAM once we have it available. For that reason, we should try to
make it easy for them to make the switch.

However ...

and the amount of
infrastructure we're proposing to introduce to support that is pretty
substantial.

... during my exchange with Michael, I was thinking about the bug
potential of taking the password field and multiplexing it in some way,
which is significant. There is a definite risk of "making this too
complicated" and we'll need to contrast that against ease-of-migration,
because complicated mechanisms tend to be less secure due to user error.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Stephen Frost
sfrost@snowman.net
In reply to: Sehrope Sarkuni (#17)
Re: WIP: SCRAM authentication

* Sehrope Sarkuni (sehrope@jackdb.com) wrote:

It'd be nice if the new auth mechanism supports multiple passwords in the
same format as well (not just one per format).

That way you could have two different passwords for a user that are active
at the same time. This would simplify rolling database credentials as it
wouldn't have to be done all at once. You could add the new credentials,
update your app servers one by one, then disable the old ones.

A lot of systems that use API keys let you see the last time a particular
set of keys was used. This helps answer the "Is this going to break
something if I disable it?" question. Having a last used at timestamp for
each auth mechanism (per user) would be useful.

Excellent points and +1 to all of these ideas from me.

I'm not sure how updates should work when connecting to a read-only slave
though. It would need some way of letting the master know that user X
connected using credentials Y.

That wouldn't be all that hard to add to the protocol..

What would be nice also would be to include slave connections in
pg_stat_activity, so you could figure out what transaction on what slave
is causing your master to bloat... And then if we could send signals
from the master to those processes, it'd be even nicer..

Thanks!

Stephen

#22Michael Paquier
michael.paquier@gmail.com
In reply to: Josh Berkus (#20)
Re: WIP: SCRAM authentication

On Mon, Aug 10, 2015 at 3:42 AM, Josh Berkus <josh@agliodbs.com> wrote:

On 08/09/2015 08:09 AM, Robert Haas wrote:

Why do we need to be able to authenticate using more than one
mechanism? If you have some clients that can't support SCRAM yet, you
might as well continue using MD5 across the board until that changes.
You're not going to get much real security out of using MD5 for some
authentication attempts and SCRAM for other ones,

Speaking as someone who has sheperded several clients through
infrastructure upgrades, I have to disagree with this.

[...]

and the amount of
infrastructure we're proposing to introduce to support that is pretty
substantial.

Maybe. But that's worth it IMO. I think that we should keep in mind as
well that we may not stick with SCRAM forever either and that we may
have to do a similar recommended-protocol switch at some point. Or
that we may have to implement additional authorization protocols in
parallel to what we have which would still require manipulation of
multiple verifiers per role.

... during my exchange with Michael, I was thinking about the bug
potential of taking the password field and multiplexing it in some way,
which is significant. There is a definite risk of "making this too
complicated" and we'll need to contrast that against ease-of-migration,
because complicated mechanisms tend to be less secure due to user error.

Sure. That's why I am all in for adding a compatibility GUC or similar
that enforces the removal of old verifier types after marking those as
deprecated for a couple of years as there's surely a significant risk
to keep old passwords around or bad pg_hba entries. Still we need IMO
a way for a user to save multiple verifiers generated from a client to
manage carefully the password verifier aging, deprecations and support
removal.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Michael Paquier
michael.paquier@gmail.com
In reply to: Stephen Frost (#21)
Re: WIP: SCRAM authentication

On Mon, Aug 10, 2015 at 6:05 AM, Stephen Frost <sfrost@snowman.net> wrote:

* Sehrope Sarkuni (sehrope@jackdb.com) wrote:

It'd be nice if the new auth mechanism supports multiple passwords in the
same format as well (not just one per format).

That way you could have two different passwords for a user that are active
at the same time. This would simplify rolling database credentials as it
wouldn't have to be done all at once. You could add the new credentials,
update your app servers one by one, then disable the old ones.

A lot of systems that use API keys let you see the last time a particular
set of keys was used. This helps answer the "Is this going to break
something if I disable it?" question. Having a last used at timestamp for
each auth mechanism (per user) would be useful.

Excellent points and +1 to all of these ideas from me.

Interesting. I haven't thought of that and those are nice suggestions.
I am not convinced that this is something to tackle with a first
version of the patch though, I am sure we'll have enough problems to
deal with to get out a nice base usable for future improvements as
well.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Josh Berkus
josh@agliodbs.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On 08/09/2015 07:19 PM, Michael Paquier wrote:

... during my exchange with Michael, I was thinking about the bug

potential of taking the password field and multiplexing it in some way,
which is significant. There is a definite risk of "making this too
complicated" and we'll need to contrast that against ease-of-migration,
because complicated mechanisms tend to be less secure due to user error.

Sure. That's why I am all in for adding a compatibility GUC or similar
that enforces the removal of old verifier types after marking those as
deprecated for a couple of years as there's surely a significant risk
to keep old passwords around or bad pg_hba entries. Still we need IMO
a way for a user to save multiple verifiers generated from a client to
manage carefully the password verifier aging, deprecations and support
removal.

That still falls under the heading of "possibly too complicated" though.

As I see it, there's two potential migration paths:

1. Allow multiple verifiers for each login role (Heikki's plan).

2. Set verifier type per login role.

(2) has the advantage of not requiring a bunch of new scaffolding
(although it will require a little) and thus being less likely to
introduce new bugs. It also doesn't require adding new catalog
structures which are really only needed for the migration period, and
after which will become a wart (i.e. having multiple verifiers per login
role).

In real migration terms, though, (2) has some major drawbacks in terms
of making migration much harder.

a) it won't work for Heroku and other 1-login-per-database hosting.

b) moving to multiple roles from single roles per app is a painful
process currently.

For (a), one could argue that these are good candidates for "all at
once" migrations, and that moving to SCRAM will depend on the host
supporting it. Someone from Heroku could speak up here.

For (b), there are a lot of things we could do to make migrating to a
multiple-role infra much easier for users, which would continue to be
useful even after the migration to SCRAM is history:

* remove the role requirement for ALTER DEFAULT PRIVILEGES, and ...

* add ALTER ROLE ___ ALTER DEFAULT OWNER, a command which sets the
default owner of newly created objects by that login role to a different
role of which they are a member. Alternatively, add a way to make a
default SET ROLE whenever a login role logs in.

These two changes, or changes like them that serve the same purpose,
would allow us to prescribe the following migration path for most users:

1. add a new login role which is a member of the old login role and uses
SCRAM;

2. set the defaults for that role so that its objects and permissions
belong to the parent role;

3. move all applications to using SCRAM and the new role;

4. disable logins on the old, parent role.

It's currently (2) which is painfully difficult, and could be made less
so via the two features recommended above.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#19)
Re: WIP: SCRAM authentication

* Robert Haas (robertmhaas@gmail.com) wrote:

On Sat, Aug 8, 2015 at 1:23 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 08/08/2015 04:27 PM, Robert Haas wrote:

I don't see that there's any good reason to allow the same password to
be stored in the catalog encrypted more than one way,

Sure there is. If you want to be able to authenticate using different
mechanism, you need the same password "encrypted" in different ways. SCRAM
uses verifier that's derived from the password in one way, MD5
authentication needs an MD5 hash, and yet other protocols have other
requirements.

Why do we need to be able to authenticate using more than one
mechanism? If you have some clients that can't support SCRAM yet, you
might as well continue using MD5 across the board until that changes.
You're not going to get much real security out of using MD5 for some
authentication attempts and SCRAM for other ones, and the amount of
infrastructure we're proposing to introduce to support that is pretty
substantial.

I agree with Josh that requiring a hard switch simply won't be
acceptable to our user community and if decide "there can only be one
running at a time" then we'll never get people to move off md5 and
therefore we wouldn't ever make real progress here. Apologies to Josh
if I've misconstrued anything in his excellent response.

and I don't think there's any good reason to introduce the PASSWORD
VERIFIER terminology. I think we should store (1) your password,
either encrypted or unencrypted; and (2) the method used to encrypt
it. And that's it.

Like Joe and Stephen, I actually find it highly confusing that we call the
MD5 hash an "encrypted password". The term "password verifier" is fairly
common in the specifications of authentication mechanisms. I think we should
adopt it.

OK, but it sure looked from Michael's syntax description like you
wrote PASSWORD VERIFIER (md5 'the_actual_password'). Or at least
that was my impression from reading it, maybe I got it wrong. If you
want to introduce ALTER USER ... PASSWORD VERIFIER as alternative
syntax for what we now call ALTER USER ... ENCRYPTED PASSWORD, that
works for me. But a plaintext password shouldn't be called a password
verifier under the terminology you are using here, IIUC.

Apologies for my incomplete up-thread definition of a password verifier,
it was a bit off-the-cuff while I was out of the country and referred to
the specific password verifier in SCRAM, which isn't a cleartext
or encrypted password or a simple hash of it, or a salted hash.

To flip it around as a positive definition instead of the negative "it's
not X, Y or Z" which I provided up-thread, a "password verifier" is a
general term for anything that can be used to verify that the client
knows the right password, so it technically could be the cleartext
version of the password, or a simple hash of the password, or an
encrypted version of the password, or pretty much anything else that
works for the protocol. The reason it came about was because of this
very issue that there wasn't a general term for the value- it was
referred to as a salted hash, or encrypted, or just hashed, etc, and
those terms don't apply to everything that can be used today as a
password verifier.

As such, it's actually correct usage in this case as it's a general
term, rather than the specific and incorrect term "encrypted" which we
have today.

Thanks!

Stephen

#26Robert Haas
robertmhaas@gmail.com
In reply to: Josh Berkus (#20)
Re: WIP: SCRAM authentication

On Sun, Aug 9, 2015 at 2:42 PM, Josh Berkus <josh@agliodbs.com> wrote:

On 08/09/2015 08:09 AM, Robert Haas wrote:

Why do we need to be able to authenticate using more than one
mechanism? If you have some clients that can't support SCRAM yet, you
might as well continue using MD5 across the board until that changes.
You're not going to get much real security out of using MD5 for some
authentication attempts and SCRAM for other ones,

Speaking as someone who has sheperded several clients through
infrastructure upgrades, I have to disagree with this.

First, people don't upgrade large infrastructures with multiple
applications, ETL processes and APIs which connect with the database all
at once. They do it one component at a time, verify that component is
working, and then move on to the next one. Even within a single
application, there could be many servers to upgrade, and you can't do
them all simultaneously.

Right. So what? First, you upgrade all of the clients one by one to
a new version of the connector that supports SCRAM.
Second, once all of the clients that access a particular user account
can support SCRAM, you switch that account to use SCRAM. The problem
you're talking about here would arise if, say, the JDBC maintainers
ripped out MD5 support at the same time they added SCRAM support. But
that would be dumb.

Second, you're forgetting hosted PostgreSQL, where there may be only one
user available to each database owner. So assigning a new login role
for SCRAM isn't even an option.

Why do I care about having some of my authentication to a particular
role happen via SCRAM if I can't have it all happen via SCRAM?
Presumably, the benefit of SCRAM is that it's more secure than MD5
authentication. So, if I can log into role X via MD5 but role Y only
via SCRAM, there might be some security benefit in that: an attacker
who can subvert MD5 but cannot subvert SCRAM can hack into role X but
not into role Y. Good. But allowing someone to authenticate to role
X via *either* SCRAM or MD5 doesn't help at all. In fact it's
strictly worse, from a security perspective, than allowing someone to
authenticate via exactly one of those two methods, because now if the
attacker can subvert *either* of them he can break into that account.

In the hosted PostgreSQL situation you mention, there are really only
two cases. Either all clients that need to log into that account can
support SCRAM, in which case we can use SCRAM and shut MD5 off. Or
else they don't, and then we need to leave MD5 enabled. But if we
have to leave MD5 enabled, then what exactly do we get out of using
SCRAM for some subset of the clients that can support it?

There may be a good answer to this question, but I don't think I've
seen it spelled out clearly.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Josh Berkus
josh@agliodbs.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On 08/11/2015 07:28 AM, Robert Haas wrote:

There may be a good answer to this question, but I don't think I've
seen it spelled out clearly.

Please see my follow-up post about making by-login-role migration easier
for users.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Robert Haas
robertmhaas@gmail.com
In reply to: Josh Berkus (#27)
Re: WIP: SCRAM authentication

On Tue, Aug 11, 2015 at 12:29 PM, Josh Berkus <josh@agliodbs.com> wrote:

On 08/11/2015 07:28 AM, Robert Haas wrote:

There may be a good answer to this question, but I don't think I've
seen it spelled out clearly.

Please see my follow-up post about making by-login-role migration easier
for users.

I read it, and now I've reread it, but I don't see how it addresses
the points I raised.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Josh Berkus
josh@agliodbs.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On 08/11/2015 09:35 AM, Robert Haas wrote:

On Tue, Aug 11, 2015 at 12:29 PM, Josh Berkus <josh@agliodbs.com> wrote:

On 08/11/2015 07:28 AM, Robert Haas wrote:

There may be a good answer to this question, but I don't think I've
seen it spelled out clearly.

Please see my follow-up post about making by-login-role migration easier
for users.

I read it, and now I've reread it, but I don't see how it addresses
the points I raised.

I'm not disagreeing with your security argument, BTW, which is why I'm
trying to come up with ways that make it easy for users to switch to
SCRAM via gradual rollout.

You're suggesting, then, that the switchover should be relatively easy,
because drivers will support both MD5 and SCRAM, and once all drivers
support both, the DBA can just swap verifiers?

That makes sense if drivers go that way. I'm concerned that some
drivers will have a different call for a SCRAM connection than for an
MD5 one; we'd want to exert our project influence to prevent that from
happening.

That also makes it a bit harder to test the new auth on a few app
servers before a general rollout, but there's ways around that.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Robert Haas
robertmhaas@gmail.com
In reply to: Josh Berkus (#29)
Re: WIP: SCRAM authentication

On Tue, Aug 11, 2015 at 12:49 PM, Josh Berkus <josh@agliodbs.com> wrote:

You're suggesting, then, that the switchover should be relatively easy,
because drivers will support both MD5 and SCRAM, and once all drivers
support both, the DBA can just swap verifiers?

Yes, that's what I was imagining would happen. I can't imagine driver
authors wanting to remove support from MD5, because even if SCRAM goes
into 9.6, pre-9.6 servers are going to exist for many years to come,
and people are going to want to talk to them.

It seems to me that the protocol flow should be:

(1) Client sends StartupMessage.

(2) Server checks whether this user has an MD5 password verifier or a
SCRAM password verifier. If the former, it responds with
AuthenticationMD5Password or AuthenticationCleartextPassword just as
it would do today, I guess based on pg_hba.conf. If the latter, it
responds with a new protocol message AuthenticationScram.

So, if you switch the password verifier, the clients will all
automatically begin using SCRAM, because the server will tell them to.
And if they can't, they'll fail.

That makes sense if drivers go that way. I'm concerned that some
drivers will have a different call for a SCRAM connection than for an
MD5 one; we'd want to exert our project influence to prevent that from
happening.

I'm not sure that would be a disaster, but do any existing drivers
have a different call for a cleartext password
(pg_hba.conf='password') than they do for an MD5 password
(pg_hba.conf='md5')? If not, I'm not sure why they'd add that just
because there is now a third way of doing password-based
authentication.

That also makes it a bit harder to test the new auth on a few app
servers before a general rollout, but there's ways around that.

Well, staging servers are a good idea...

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Josh Berkus
josh@agliodbs.com
In reply to: Heikki Linnakangas (#1)
Re: WIP: SCRAM authentication

On 08/11/2015 10:06 AM, Robert Haas wrote:

On Tue, Aug 11, 2015 at 12:49 PM, Josh Berkus <josh@agliodbs.com> wrote:

That makes sense if drivers go that way. I'm concerned that some
drivers will have a different call for a SCRAM connection than for an
MD5 one; we'd want to exert our project influence to prevent that from
happening.

I'm not sure that would be a disaster, but do any existing drivers
have a different call for a cleartext password
(pg_hba.conf='password') than they do for an MD5 password
(pg_hba.conf='md5')? If not, I'm not sure why they'd add that just
because there is now a third way of doing password-based
authentication.

Well, there is a different send-and-response cycle to the SCRAM
approach, no? Plus, I've seen driver authors do strange things in the
past, including PHP's various drivers and pypgsql, which IIRC required
you to manually pick a protocol version. I'm not saying we should plan
for bad design, we should just get the word out to driver authors that
we think it would be a good idea to support both methods transparently.

That also makes it a bit harder to test the new auth on a few app
servers before a general rollout, but there's ways around that.

Well, staging servers are a good idea...

Don't get me started. :-b

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#26)
Re: WIP: SCRAM authentication

On 8/11/15 10:28 AM, Robert Haas wrote:

Right. So what? First, you upgrade all of the clients one by one to
a new version of the connector that supports SCRAM.

In my experience, upgrading clients is, in many settings, significantly
harder than upgrading servers. So I think any plan to starts like the
above isn't going to work.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#32)
Re: WIP: SCRAM authentication

On Tue, Aug 11, 2015 at 2:35 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On 8/11/15 10:28 AM, Robert Haas wrote:

Right. So what? First, you upgrade all of the clients one by one to
a new version of the connector that supports SCRAM.

In my experience, upgrading clients is, in many settings, significantly
harder than upgrading servers. So I think any plan to starts like the
above isn't going to work.

Well, the real sequence of steps is actually:

1. Get SCRAM authentication committed to 9.6, and release 9.6.
2. Get driver authors to begin supporting SCRAM in their drivers.
3. Get users to update to those new drivers.
4. Enable SCRAM authentication for any given role once all clients
that need that role are SCRAM-capable.

Since supporting a new authentication method means adding a new
protocol message, there's no feasible method for rolling out SCRAM
without users at some point updating to a newer driver. So there's
really not much choice but for (1)-(3) to happen. If you're saying
it's likely to be a really long time before steps (2) and (3) are
completed for substantially all installations, I quite agree.

The thing we're actually debating here is whether enabling SCRAM
authentication for a role should also mean disabling MD5
authentication for that same role, or whether you should be able to
have two password verifiers stored for that role, one for SCRAM and
the other MD5. If we allowed that, then you could turn SCRAM on for a
role, and only later turn MD5 off. I think that's a bad plan because,
in most scenarios, allowing two forms of authentication for the same
account is strictly less secure than allowing only one. And also
because it means adding a bunch of new system catalog machinery and
SQL syntax. Instead, I think that, for any given role, you should get
to pick the way that password authentication works for that role:
either MD5, or SCRAM, but not "whichever of those two the client
prefers".

We don't actually have any reason to believe that MD5 is insecure in
the way that we're using it. But if it turns out that somebody finds
an effective preimage attack against MD5, which I think what is what
Heikki said it would take to make our use unsafe, then it's not going
to be good enough to let people use SCRAM. They're going to need to
enforce the use of SCRAM. So I really don't understand why we want to
add a lot of complexity to let people mix the two authentication types
on the same user account. Unless SCRAM is the ONLY way to
authenticate to the server, the fact that some clients are using it
does not help you.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#6)
5 attachment(s)
Re: WIP: SCRAM authentication

On Fri, Aug 7, 2015 at 4:22 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

I have as well done the following things:
- Added PASSWORD VERIFIER (md5 = 'hoge', plain = 'hoge') which is used
as well by pg_dump all to be able to specify password verifiers one by
one.
- password check hook has been reworked as mentioned to be able to
manage a list of password verifiers instead of a single entry.
contrib/passwordcheck has been updated as well.
- Added regression tests testing UNENCRYPTED/ENCRYPTED, PASSWORD
VERIFIER, PASSWORD, etc.
- The patch does not break backward compatibility regarding CREATE
ROLE and ALTER ROLE.
- password_encryption has been changed to a list with comma-separated
elements, for now the possible elements are 'md5' and 'plain'. This
breaks backward compatibility, so if we care about it we should
consider having a new GUC password_encryption_list or similar. Default
is md5, default that does not change backward compatibility.
- Added documentation.
- pg_shadow has been updated, switching to an array with
method:password as elements.

I'll have a look more in-depth at the scram patch as well.

The SCRAM patch (0002~0004) has been rebased to use the new facility.
I have as well fixed a syscache leak... But I haven't been able to
enter much in the details yet.

The patch 0001 to add the multiple verifier facility is in a state
good enough to get some review, so I am registering an entry for it in
the next CF. And I'll continue the work on the SCRAM portion next
week, with hopefully a version ready for CF submission.

Using rfc5802 as reference (http://tools.ietf.org/html/rfc5802), we
use "scram" as a name for the authentication method, like in
pg_hba.conf, but the correct name would be "scram-sha-1". Does it
really matter? We may want to implement other scram protocols using
different hash function than sha1, no? (in case channel binding is
supported, -plus can be appended).

There are still a list of TODO items in the patch that need to be
addressed and I have spotted a couple of bugs:
- the salt used when building the SCRAM verifier is for now the MD5
salt. We definitely need to use a longer one than MD5. but how much?
For now I have bumped it to 10.
- Regarding that (got a lesson from Tom a couple of months ago):
+# FIXME: any reason not to link with libpgcommon?
+OBJS += scram-common.o sha1.o
libpq cannot be linked with libpgcommon, because libpgcommon may not
be compiled to be position-independent code (depends on platform).
- When client receives the last message from server, it did not fetch
the server proof and did not check for it. I fixed that by
implementing what was needed.
- No randomness was used when generating the frontend nonce. I
switched to a call for random() without setting up a seed, which I
think could be improved with for example something based on
gettimeofday(). Thoughts about that are welcome. I think this could be
a separate patch as well.
- fe-connect.c is broken in PQconnectPoll because of this code block
like MD5 authentication:
+                               /* Get additional payload, if any */
+                               if (msgLength > 4)
+                               {
+                                       int                     llen =
msgLength - 4;
+ [...]
A correct fix seems to me to use this code path only for AUTH_REQ_SASL
and AUTH_REQ_SASL_CONT.
- The definition of nonce and salt length used by both the frontend
and backend looks better placed in common/scram-common.h.
- For now the password is not normalized with SASLprep. Do you think
that's necessary as a first shot in the authorization protocol?

Other things:
- SASL is a superset of protocols and SCRAM-SHA1 is one of them. We
may want to split the basic stuff of SASL and SCRAM-SHA1 into two
different patches, though having SASL alone does not make much sense
as without any protocols implemented we would just have some dead code
around until a protocol is implemented. We also refer to scram in
pg_hba.conf, but that's not completely correct to me, we may want to
use scram_sha1 actually, particularly if we implement new SASL methods
based on SCRAM, like for example SCRAM-SHA256.
- RandomSalt() in postmaster.c could be refactored such as it gets the
salt length as well in input. I implemented that as a separate patch
attached.
- SCRAM authentication uses the routines for encoding/decoding in
base64 in both frontend and backend, which are located in encode.c as
static routines on HEAD. It looks to me that it is a bad idea to
duplicate it in both frontend and backend as the scram patch does now.
It also does not seem to be a problem to move them in src/common/,
though the error handling needs to be reworked as those routines use
some elog calls which need to be removed. And actually, it seems to me
that it would be better to make a larger move and move all the
encoding/decoding routines of encode.c to libpgcommon (I mean hex,
escape *and* base64). I have done nothing about that yet, and thoughts
are welcome. This should be a separate patch as well that SCRAM relies
on.

For now, I am attaching a new series of patches, and the SCRAM
authentication is still using the new catalog pg_auth_verifiers.
Switching to a one-verifier-per-role approach or similar does not seem
to be a huge task to me.

Regards,
--
Michael

Attachments:

0001-Add-facility-to-store-multiple-password-formats.patchtext/x-diff; charset=US-ASCII; name=0001-Add-facility-to-store-multiple-password-formats.patchDownload
From 30e502490276224713a0fcd533cd55e278327d2e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 7 Aug 2015 13:35:40 +0900
Subject: [PATCH 1/5] Add facility to store multiple password formats

This commit adds a new cluster-wide catalog table called pg_auth_verifiers
extending the existing one-password value per role approach into a facility
ablt to store multiple passwords formats for one user. This makes easier to
add additional password format support in the future and is a requirement
for the additional of SCRAM-SHA1.

CREATE ROLE and ALTER ROLE are extended with PASSWORD VERIFIERS that allow
a user to set a list of password identifiers at will, something particularly
useful for pg_dump that makes use of it with this commit.

password_encryption is transformed into a list able to use "md5" or "plain",
or even both when CREATE/ALTER ROLE uses neither ENCRYPTED/UNENCRYPTED.

The password check hook has been redesigned to be able to check a list
of passwords instead of a single entry, and the related contrib module
passwordcheck/ is updated respecting the new format.

Regression tests and documentation are added accordingly.
---
 contrib/passwordcheck/passwordcheck.c         | 138 +++++------
 doc/src/sgml/catalogs.sgml                    |  99 ++++++--
 doc/src/sgml/config.sgml                      |  17 +-
 doc/src/sgml/ref/create_role.sgml             |  23 +-
 src/backend/catalog/Makefile                  |   4 +-
 src/backend/catalog/catalog.c                 |   4 +
 src/backend/catalog/system_views.sql          |  11 +-
 src/backend/commands/user.c                   | 315 +++++++++++++++++---------
 src/backend/libpq/crypt.c                     |  72 ++++--
 src/backend/nodes/copyfuncs.c                 |  14 ++
 src/backend/nodes/equalfuncs.c                |  12 +
 src/backend/parser/gram.y                     |  98 +++++++-
 src/backend/utils/cache/catcache.c            |   1 +
 src/backend/utils/cache/relcache.c            |  17 +-
 src/backend/utils/cache/syscache.c            |  23 ++
 src/backend/utils/misc/guc.c                  |  65 ++++--
 src/backend/utils/misc/postgresql.conf.sample |   2 +-
 src/bin/initdb/initdb.c                       |   5 +-
 src/bin/pg_dump/pg_dumpall.c                  |  77 ++++++-
 src/include/catalog/indexing.h                |   5 +
 src/include/catalog/pg_auth_verifiers.h       |  62 +++++
 src/include/catalog/pg_authid.h               |   8 +-
 src/include/commands/user.h                   |  11 +-
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/parsenodes.h                |  11 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/utils/syscache.h                  |   2 +
 src/test/regress/expected/password.out        | 101 +++++++++
 src/test/regress/expected/rules.out           |   9 +-
 src/test/regress/expected/sanity_check.out    |   1 +
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/serial_schedule              |   1 +
 src/test/regress/sql/password.sql             |  70 ++++++
 33 files changed, 1017 insertions(+), 265 deletions(-)
 create mode 100644 src/include/catalog/pg_auth_verifiers.h
 create mode 100644 src/test/regress/expected/password.out
 create mode 100644 src/test/regress/sql/password.sql

diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 78c44b2..5ee38ed 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -20,9 +20,11 @@
 #include <crack.h>
 #endif
 
+#include "catalog/pg_auth_verifiers.h"
 #include "commands/user.h"
 #include "fmgr.h"
 #include "libpq/md5.h"
+#include "nodes/parsenodes.h"
 
 PG_MODULE_MAGIC;
 
@@ -50,87 +52,93 @@ extern void _PG_init(void);
  */
 static void
 check_password(const char *username,
-			   const char *password,
-			   int password_type,
+			   List *passwordVerifiers,
 			   Datum validuntil_time,
 			   bool validuntil_null)
 {
 	int			namelen = strlen(username);
-	int			pwdlen = strlen(password);
+	int			pwdlen;
 	char		encrypted[MD5_PASSWD_LEN + 1];
 	int			i;
 	bool		pwd_has_letter,
 				pwd_has_nonletter;
+	ListCell   *l;
 
-	switch (password_type)
+	foreach(l, passwordVerifiers)
 	{
-		case PASSWORD_TYPE_MD5:
-
-			/*
-			 * Unfortunately we cannot perform exhaustive checks on encrypted
-			 * passwords - we are restricted to guessing. (Alternatively, we
-			 * could insist on the password being presented non-encrypted, but
-			 * that has its own security disadvantages.)
-			 *
-			 * We only check for username = password.
-			 */
-			if (!pg_md5_encrypt(username, username, namelen, encrypted))
-				elog(ERROR, "password encryption failed");
-			if (strcmp(password, encrypted) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password must not contain user name")));
-			break;
-
-		case PASSWORD_TYPE_PLAINTEXT:
-
-			/*
-			 * For unencrypted passwords we can perform better checks
-			 */
-
-			/* enforce minimum length */
-			if (pwdlen < MIN_PWD_LENGTH)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password is too short")));
-
-			/* check if the password contains the username */
-			if (strstr(password, username))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password must not contain user name")));
-
-			/* check if the password contains both letters and non-letters */
-			pwd_has_letter = false;
-			pwd_has_nonletter = false;
-			for (i = 0; i < pwdlen; i++)
-			{
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		switch (spec->veriftype)
+		{
+			case AUTH_VERIFIER_MD5:
+
+				/*
+				 * Unfortunately we cannot perform exhaustive checks on encrypted
+				 * passwords - we are restricted to guessing. (Alternatively, we
+				 * could insist on the password being presented non-encrypted, but
+				 * that has its own security disadvantages.)
+				 *
+				 * We only check for username = password.
+				 */
+				if (!pg_md5_encrypt(username, username, namelen, encrypted))
+					elog(ERROR, "password encryption failed");
+				if (strcmp(spec->value, encrypted) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password must not contain user name")));
+				break;
+
+			case AUTH_VERIFIER_PLAIN:
+
 				/*
-				 * isalpha() does not work for multibyte encodings but let's
-				 * consider non-ASCII characters non-letters
+				 * For unencrypted passwords we can perform better checks
 				 */
-				if (isalpha((unsigned char) password[i]))
-					pwd_has_letter = true;
-				else
-					pwd_has_nonletter = true;
-			}
-			if (!pwd_has_letter || !pwd_has_nonletter)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				errmsg("password must contain both letters and nonletters")));
+				pwdlen = strlen(spec->value);
+
+				/* enforce minimum length */
+				if (pwdlen < MIN_PWD_LENGTH)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password is too short")));
+
+				/* check if the password contains the username */
+				if (strstr(spec->value, username))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password must not contain user name")));
+
+				/* check if the password contains both letters and non-letters */
+				pwd_has_letter = false;
+				pwd_has_nonletter = false;
+				for (i = 0; i < pwdlen; i++)
+				{
+					/*
+					 * isalpha() does not work for multibyte encodings but let's
+					 * consider non-ASCII characters non-letters
+					 */
+					if (isalpha((unsigned char) spec->value[i]))
+						pwd_has_letter = true;
+					else
+						pwd_has_nonletter = true;
+				}
+				if (!pwd_has_letter || !pwd_has_nonletter)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("password must contain both letters and nonletters")));
 
 #ifdef USE_CRACKLIB
-			/* call cracklib to check password */
-			if (FascistCheck(password, CRACKLIB_DICTPATH))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password is easily cracked")));
+				/* call cracklib to check password */
+				if (FascistCheck(spec->value, CRACKLIB_DICTPATH))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password is easily cracked")));
 #endif
-			break;
+				break;
 
-		default:
-			elog(ERROR, "unrecognized password type: %d", password_type);
-			break;
+			default:
+				elog(ERROR, "unrecognized password type: %d", spec->veriftype);
+				break;
+		}
 	}
 
 	/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7781c56..6d3323b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1360,13 +1360,6 @@
   </para>
 
   <para>
-   Since this catalog contains passwords, it must not be publicly readable.
-   <link linkend="view-pg-roles"><structname>pg_roles</structname></link>
-   is a publicly readable view on
-   <structname>pg_authid</structname> that blanks out the password field.
-  </para>
-
-  <para>
    <xref linkend="user-manag"> contains detailed information about user and
    privilege management.
   </para>
@@ -1469,21 +1462,6 @@
      </row>
 
      <row>
-      <entry><structfield>rolpassword</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>
-       Password (possibly encrypted); null if none.  If the password
-       is encrypted, this column will begin with the string <literal>md5</>
-       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
-       will be of the user's password concatenated to their user name.
-       For example, if user <literal>joe</> has password <literal>xyzzy</>,
-       <productname>PostgreSQL</> will store the md5 hash of
-       <literal>xyzzyjoe</>.  A password that does not follow that
-       format is assumed to be unencrypted.
-      </entry>
-     </row>
-
-     <row>
       <entry><structfield>rolvaliduntil</structfield></entry>
       <entry><type>timestamptz</type></entry>
       <entry>Password expiry time (only used for password authentication);
@@ -1495,6 +1473,77 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-auth-verifiers">
+  <title><structname>pg_auth_verifiers</structname></title>
+
+  <indexterm zone="catalog-pg-auth-verifiers">
+   <primary>pg_auth_verifiers</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_auth_verifiers</structname> contains password
+   information for database authorization identifiers (roles).
+  </para>
+
+  <para>
+   Since this catalog contains passwords, it must not be publicly readable.
+  </para>
+
+  <para>
+   Because user identities are cluster-wide,
+   <structname>pg_auth_verifiers</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_auth_verifiers</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_auth_verifiers</> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>roleid</type></entry>
+      <entry>Role identifier OID</entry>
+     </row>
+
+     <row>
+      <entry><structfield>verimet</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry>
+       <literal>p</> = plain format,
+       <literal>m</> = MD5-encrypted
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>text</structfield></entry>
+      <entry><type>verival</type></entry>
+      <entry>
+       Password (possibly encrypted with format defined in
+       <structfield>verimet</>).  If the password
+       is MD5-encrypted, this column will begin with the string <literal>md5</>
+       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
+       will be of the user's password concatenated to their user name.
+       For example, if user <literal>joe</> has password <literal>xyzzy</>,
+       <productname>PostgreSQL</> will store the md5 hash of
+       <literal>xyzzyjoe</>.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
 
  <sect1 id="catalog-pg-auth-members">
   <title><structname>pg_auth_members</structname></title>
@@ -9865,9 +9914,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
      </row>
 
      <row>
-      <entry><structfield>passwd</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Not the password (always reads as <literal>********</>)</entry>
+      <entry><structfield>verifiers</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry>List of password verifiers listed as method:password.</entry>
      </row>
 
      <row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e900dcc..5ae27f8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,20 +1163,29 @@ include_dir 'conf.d'
      </varlistentry>
 
      <varlistentry id="guc-password-encryption" xreflabel="password_encryption">
-      <term><varname>password_encryption</varname> (<type>boolean</type>)
+      <term><varname>password_encryption</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>password_encryption</> configuration parameter</primary>
       </indexterm>
       </term>
       <listitem>
        <para>
+        Specifies a comma-separated list of password encryption formats.
+        Supported formats are <literal>plain</> and <literal>md5</>.
+       </para>
+
+       <para>
         When a password is specified in <xref
         linkend="sql-createuser"> or
         <xref linkend="sql-alterrole">
         without writing either <literal>ENCRYPTED</> or
-        <literal>UNENCRYPTED</>, this parameter determines whether the
-        password is to be encrypted. The default is <literal>on</>
-        (encrypt the password).
+        <literal>UNENCRYPTED</>, this parameter determines the list of
+        encryption formats this password is to be stored as.
+       </para>
+
+       <para>
+        The default is <literal>md5</> (encrypt the password with MD5
+        encryption).
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index ea26027..a48d188 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -35,6 +35,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
     | BYPASSRLS | NOBYPASSRLS
     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
     | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+    | PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' [, ...] )
     | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
     | IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
     | IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -228,9 +229,9 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
         roles having the <literal>LOGIN</literal> attribute, but you
         can nonetheless define one for roles without it.)  If you do
         not plan to use password authentication you can omit this
-        option.  If no password is specified, the password will be set
-        to null and password authentication will always fail for that
-        user.  A null password can optionally be written explicitly as
+        option.  If no password is specified, no password will be set
+        and password authentication will always fail for that user.
+        A null password can optionally be written explicitly as
         <literal>PASSWORD NULL</literal>.
        </para>
       </listitem>
@@ -262,6 +263,22 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
      </varlistentry>
 
      <varlistentry>
+      <term>PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>'</term>
+      <listitem>
+       <para>
+        Sets the list of password verifiers for the role. Currently only
+        <literal>md5</>, for MD5-encrypted format, and <literal>plain</>
+        for unencrypted format, can be specified as verifier format type
+        for <literal>verifier_type</>. If the password defined with
+        <literal>plain</> type is already in MD5-encrypted format
+        the password will be stored as MD5-encrypted. If the password defined
+        is in plain-format and that <literal>verifier_type</> is set
+        to <literal>md5</>, the password will be stored as MD5-encrypted.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
       <listitem>
        <para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..2e695b8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,8 +35,8 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
 	pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
 	pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
-	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
-	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
+	pg_auth_verifiers.h pg_authid.h pg_auth_members.h pg_shdepend.h \
+	pg_shdescription.h pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 81ccebf..663e51b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -27,6 +27,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
@@ -218,6 +219,7 @@ IsSharedRelation(Oid relationId)
 {
 	/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
 	if (relationId == AuthIdRelationId ||
+		relationId == AuthVerifRelationId ||
 		relationId == AuthMemRelationId ||
 		relationId == DatabaseRelationId ||
 		relationId == PLTemplateRelationId ||
@@ -231,6 +233,8 @@ IsSharedRelation(Oid relationId)
 	/* These are their indexes (see indexing.h) */
 	if (relationId == AuthIdRolnameIndexId ||
 		relationId == AuthIdOidIndexId ||
+		relationId == AuthVerifRoleMethodIndexId ||
+		relationId == AuthVerifMethodRoleIndexId ||
 		relationId == AuthMemRoleMemIndexId ||
 		relationId == AuthMemMemRoleIndexId ||
 		relationId == DatabaseNameIndexId ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c0bd6fa..f9121e6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -32,7 +32,16 @@ CREATE VIEW pg_shadow AS
         rolsuper AS usesuper,
         rolreplication AS userepl,
         rolbypassrls AS usebypassrls,
-        rolpassword AS passwd,
+        ARRAY
+        (
+            SELECT
+                CASE verimet
+                    WHEN 'p' THEN 'plain:' || verival
+                    WHEN 'm' THEN 'md5:' || verival
+                END AS verifiers
+            FROM pg_auth_verifiers
+	    WHERE roleid = pg_authid.oid
+	) AS verifiers,
         rolvaliduntil::abstime AS valuntil,
         setconfig AS useconfig
     FROM pg_authid LEFT JOIN pg_db_role_setting s
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index afbf276..c6bf9db 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -21,9 +21,11 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "commands/seclabel.h"
@@ -42,9 +44,6 @@
 Oid			binary_upgrade_next_pg_authid_oid = InvalidOid;
 
 
-/* GUC parameter */
-extern bool Password_encryption;
-
 /* Hook to check passwords in CreateRole() and AlterRole() */
 check_password_hook_type check_password_hook = NULL;
 
@@ -54,6 +53,10 @@ static void AddRoleMems(const char *rolename, Oid roleid,
 static void DelRoleMems(const char *rolename, Oid roleid,
 			List *memberSpecs, List *memberIds,
 			bool admin_opt);
+static void FlattenPasswordIdentifiers(List *verifiers, char *rolname);
+static void InsertPasswordIdentifiers(Oid roleid, List *verifiers,
+			char *rolname);
+static void DeletePasswordVerifiers(Oid roleid);
 
 
 /* Check if current user has createrole privileges */
@@ -78,9 +81,7 @@ CreateRole(CreateRoleStmt *stmt)
 	Oid			roleid;
 	ListCell   *item;
 	ListCell   *option;
-	char	   *password = NULL;	/* user password */
-	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	List	   *passwordVerifiers = NIL;	/* password verifiers */
 	bool		issuper = false;	/* Make the user a superuser? */
 	bool		inherit = true; /* Auto inherit privileges? */
 	bool		createrole = false;		/* Can this user create roles? */
@@ -96,7 +97,7 @@ CreateRole(CreateRoleStmt *stmt)
 	char	   *validUntil = NULL;		/* time the login is valid until */
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
-	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordVerifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -128,19 +129,13 @@ CreateRole(CreateRoleStmt *stmt)
 	{
 		DefElem    *defel = (DefElem *) lfirst(option);
 
-		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+		if (strcmp(defel->defname, "passwordVerifiers") == 0)
 		{
-			if (dpassword)
+			if (dpasswordVerifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
-			dpassword = defel;
-			if (strcmp(defel->defname, "encryptedPassword") == 0)
-				encrypt_password = true;
-			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
-				encrypt_password = false;
+			dpasswordVerifiers = defel;
 		}
 		else if (strcmp(defel->defname, "sysid") == 0)
 		{
@@ -248,8 +243,8 @@ CreateRole(CreateRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
+	if (dpasswordVerifiers && dpasswordVerifiers->arg)
+		passwordVerifiers = (List *) dpasswordVerifiers->arg;
 	if (dissuper)
 		issuper = intVal(dissuper->arg) != 0;
 	if (dinherit)
@@ -340,12 +335,16 @@ CreateRole(CreateRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Flatten list of password verifiers.
+	 */
+	FlattenPasswordIdentifiers(passwordVerifiers, stmt->role);
+
+	/*
+	 * Call the password checking hook if there is one defined.
 	 */
-	if (check_password_hook && password)
+	if (check_password_hook && passwordVerifiers != NIL)
 		(*check_password_hook) (stmt->role,
-								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								passwordVerifiers,
 								validUntil_datum,
 								validUntil_null);
 
@@ -365,24 +364,6 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
 	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-	}
-	else
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
@@ -411,6 +392,10 @@ CreateRole(CreateRoleStmt *stmt)
 	roleid = simple_heap_insert(pg_authid_rel, tuple);
 	CatalogUpdateIndexes(pg_authid_rel, tuple);
 
+	/* store password verifiers */
+	if (passwordVerifiers)
+		InsertPasswordIdentifiers(roleid, passwordVerifiers, stmt->role);
+
 	/*
 	 * Advance command counter so we can see new record; else tests in
 	 * AddRoleMems may fail.
@@ -479,9 +464,7 @@ AlterRole(AlterRoleStmt *stmt)
 	Form_pg_authid authform;
 	ListCell   *option;
 	char	   *rolename = NULL;
-	char	   *password = NULL;	/* user password */
-	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	List	   *passwordVerifiers = NIL;	/* password verifiers */
 	int			issuper = -1;	/* Make the user a superuser? */
 	int			inherit = -1;	/* Auto inherit privileges? */
 	int			createrole = -1;	/* Can this user create roles? */
@@ -494,7 +477,7 @@ AlterRole(AlterRoleStmt *stmt)
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
 	bool		bypassrls = -1;
-	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordVerifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -512,19 +495,13 @@ AlterRole(AlterRoleStmt *stmt)
 	{
 		DefElem    *defel = (DefElem *) lfirst(option);
 
-		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+		if (strcmp(defel->defname, "passwordVerifiers") == 0)
 		{
-			if (dpassword)
+			if (dpasswordVerifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
-			dpassword = defel;
-			if (strcmp(defel->defname, "encryptedPassword") == 0)
-				encrypt_password = true;
-			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
-				encrypt_password = false;
+			dpasswordVerifiers = defel;
 		}
 		else if (strcmp(defel->defname, "superuser") == 0)
 		{
@@ -612,8 +589,8 @@ AlterRole(AlterRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
+	if (dpasswordVerifiers && dpasswordVerifiers->arg)
+		passwordVerifiers = (List *) dpasswordVerifiers->arg;
 	if (dissuper)
 		issuper = intVal(dissuper->arg);
 	if (dinherit)
@@ -687,7 +664,7 @@ AlterRole(AlterRoleStmt *stmt)
 			  !dconnlimit &&
 			  !rolemembers &&
 			  !validUntil &&
-			  dpassword &&
+			  dpasswordVerifiers &&
 			  roleid == GetUserId()))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -712,12 +689,16 @@ AlterRole(AlterRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Flatten list of password verifiers.
 	 */
-	if (check_password_hook && password)
+	FlattenPasswordIdentifiers(passwordVerifiers, rolename);
+
+	/*
+	 * Call the password checking hook if there is one defined.
+	 */
+	if (check_password_hook && passwordVerifiers)
 		(*check_password_hook) (rolename,
-								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								passwordVerifiers,
 								validUntil_datum,
 								validUntil_null);
 
@@ -773,30 +754,6 @@ AlterRole(AlterRoleStmt *stmt)
 		new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
 	}
 
-	/* password */
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, rolename, strlen(rolename),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
-	/* unset password */
-	if (dpassword && dpassword->arg == NULL)
-	{
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
 	/* valid until */
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -821,6 +778,21 @@ AlterRole(AlterRoleStmt *stmt)
 	heap_freetuple(new_tuple);
 
 	/*
+	 * Update password verifiers. The old entries are completely cleaned up
+	 * and are updated by what is wanted through this command.
+	 */
+	if (passwordVerifiers)
+	{
+		DeletePasswordVerifiers(roleid);
+		CommandCounterIncrement();
+		InsertPasswordIdentifiers(roleid, passwordVerifiers, rolename);
+	}
+
+	/* remove password verifiers */
+	if (dpasswordVerifiers && dpasswordVerifiers->arg == NULL)
+		DeletePasswordVerifiers(roleid);
+
+	/*
 	 * Advance command counter so we can see new record; else tests in
 	 * AddRoleMems may fail.
 	 */
@@ -1070,8 +1042,10 @@ DropRole(DropRoleStmt *stmt)
 		systable_endscan(sscan);
 
 		/*
-		 * Remove any comments or security labels on this role.
+		 * Remove any comments, password verifiers or security labels on this
+		 * role.
 		 */
+		DeletePasswordVerifiers(roleid);
 		DeleteSharedComments(roleid, AuthIdRelationId);
 		DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
 
@@ -1106,11 +1080,11 @@ ObjectAddress
 RenameRole(const char *oldname, const char *newname)
 {
 	HeapTuple	oldtuple,
-				newtuple;
+				newtuple,
+				authtuple;
 	TupleDesc	dsc;
-	Relation	rel;
-	Datum		datum;
-	bool		isnull;
+	Relation	pg_authid_rel,
+				pg_auth_verifiers_rel;
 	Datum		repl_val[Natts_pg_authid];
 	bool		repl_null[Natts_pg_authid];
 	bool		repl_repl[Natts_pg_authid];
@@ -1118,8 +1092,10 @@ RenameRole(const char *oldname, const char *newname)
 	Oid			roleid;
 	ObjectAddress address;
 
-	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
-	dsc = RelationGetDescr(rel);
+	pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+
+	dsc = RelationGetDescr(pg_authid_rel);
 
 	oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
 	if (!HeapTupleIsValid(oldtuple))
@@ -1179,22 +1155,10 @@ RenameRole(const char *oldname, const char *newname)
 												   CStringGetDatum(newname));
 	repl_null[Anum_pg_authid_rolname - 1] = false;
 
-	datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
-
-	if (!isnull && isMD5(TextDatumGetCString(datum)))
-	{
-		/* MD5 uses the username as salt, so just clear it on a rename */
-		repl_repl[Anum_pg_authid_rolpassword - 1] = true;
-		repl_null[Anum_pg_authid_rolpassword - 1] = true;
-
-		ereport(NOTICE,
-				(errmsg("MD5 password cleared because of role rename")));
-	}
-
 	newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
-	simple_heap_update(rel, &oldtuple->t_self, newtuple);
+	simple_heap_update(pg_authid_rel, &oldtuple->t_self, newtuple);
 
-	CatalogUpdateIndexes(rel, newtuple);
+	CatalogUpdateIndexes(pg_authid_rel, newtuple);
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
@@ -1202,10 +1166,23 @@ RenameRole(const char *oldname, const char *newname)
 
 	ReleaseSysCache(oldtuple);
 
+	/* look for md5 entry in pg_auth_verifiers and remove it if it exists */
+	authtuple = SearchSysCache2(AUTHVERIFROLEMETH,
+								ObjectIdGetDatum(roleid),
+								CharGetDatum(AUTH_VERIFIER_MD5));
+	if (HeapTupleIsValid(authtuple))
+	{
+		simple_heap_delete(pg_auth_verifiers_rel, &authtuple->t_self);
+		ereport(NOTICE,
+				(errmsg("MD5 password cleared because of role rename")));
+		ReleaseSysCache(authtuple);
+	}
+
 	/*
-	 * Close pg_authid, but keep lock till commit.
+	 * Close pg_authid and pg_auth_verifiers, but keep lock till commit.
 	 */
-	heap_close(rel, NoLock);
+	heap_close(pg_auth_verifiers_rel, NoLock);
+	heap_close(pg_authid_rel, NoLock);
 
 	return address;
 }
@@ -1611,3 +1588,127 @@ DelRoleMems(const char *rolename, Oid roleid,
 	 */
 	heap_close(pg_authmem_rel, NoLock);
 }
+
+/*
+ * FlattenPasswordIdentifiers
+ * Make list of password verifier types and values consistent with input.
+ */
+static void
+FlattenPasswordIdentifiers(List *verifiers, char *rolname)
+{
+	ListCell   *l;
+
+	foreach(l, verifiers)
+	{
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		if (spec->value == NULL)
+			continue;
+
+		/*
+		 * Check if given value for an MD5 verifier is adapted and
+		 * do conversion as needed. If an MD5 password is provided but
+		 * that the verifier has a plain format switch type of verifier
+		 * accordingly.
+		 */
+		if (spec->veriftype == AUTH_VERIFIER_MD5 &&
+			!isMD5(spec->value))
+		{
+			char encrypted_passwd[MD5_PASSWD_LEN + 1];
+			if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
+								encrypted_passwd))
+				elog(ERROR, "password encryption failed");
+			spec->value = pstrdup(encrypted_passwd);
+		}
+		else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
+				 isMD5(spec->value))
+			spec->veriftype = AUTH_VERIFIER_MD5;
+	}
+}
+
+/*
+ * InsertPasswordIdentifiers
+ * Add list of given identifiers into pg_auth_verifiers for given role.
+ */
+static void
+InsertPasswordIdentifiers(Oid roleid, List *verifiers, char *rolname)
+{
+	ListCell   *l;
+	Relation	pg_auth_verifiers_rel;
+	TupleDesc	pg_auth_verifiers_dsc;
+
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+	pg_auth_verifiers_dsc = RelationGetDescr(pg_auth_verifiers_rel);
+
+	foreach(l, verifiers)
+	{
+		Datum		new_record[Natts_pg_auth_verifiers];
+		bool		new_record_nulls[Natts_pg_auth_verifiers];
+		HeapTuple	tuple;
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		/* Move on if no verifier value define */
+		if (spec->value == NULL)
+			continue;
+
+		/* Build tuple and insert it */
+		MemSet(new_record, 0, sizeof(new_record));
+		MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+		new_record[Anum_pg_auth_verifiers_roleid - 1] = ObjectIdGetDatum(roleid);
+		new_record[Anum_pg_auth_verifiers_method - 1] =
+			CharGetDatum(spec->veriftype);
+
+		new_record[Anum_pg_auth_verifiers_value - 1] =
+			CStringGetTextDatum(spec->value);
+
+		tuple = heap_form_tuple(pg_auth_verifiers_dsc,
+								new_record, new_record_nulls);
+
+		simple_heap_insert(pg_auth_verifiers_rel, tuple);
+		CatalogUpdateIndexes(pg_auth_verifiers_rel, tuple);
+
+		/* CCI after each change */
+		CommandCounterIncrement();
+	}
+
+	/* Keep locks until the end of transaction */
+	heap_close(pg_auth_verifiers_rel, NoLock);
+}
+
+/*
+ * DeletePasswordVerifiers
+ * Remove all password identifiers for given role.
+ */
+static void
+DeletePasswordVerifiers(Oid roleid)
+{
+	Relation	pg_auth_verifiers_rel;
+	ScanKeyData scankey;
+	SysScanDesc	sscan;
+	HeapTuple	tmp_tuple;
+
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+	/*
+	 * Remove role entries from pg_auth_verifiers table. All the tuples that
+	 * are similar to the role dropped need to be removed.
+	 */
+	ScanKeyInit(&scankey,
+				Anum_pg_auth_verifiers_roleid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(roleid));
+
+	sscan = systable_beginscan(pg_auth_verifiers_rel,
+							   AuthVerifRoleMethodIndexId,
+							   true, NULL, 1, &scankey);
+
+	while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(pg_auth_verifiers_rel, &tmp_tuple->t_self);
+	}
+
+	systable_endscan(sscan);
+
+	/* keep lock until the end of transaction */
+	heap_close(pg_auth_verifiers_rel, NoLock);
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 97be944..f04d17a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,14 +20,46 @@
 #include <crypt.h>
 #endif
 
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_type.h"
 #include "libpq/crypt.h"
 #include "libpq/md5.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+/*
+ * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * method.
+ */
+static char *
+get_role_verifier(Oid roleid, const char method)
+{
+	HeapTuple	tuple;
+	Datum		verifier_datum;
+	char	   *verifier;
+	bool		isnull;
+
+	/* Now attempt to grab the verifier value */
+	tuple = SearchSysCache2(AUTHVERIFROLEMETH,
+							ObjectIdGetDatum(roleid),
+							CharGetDatum(method));
+	if (!HeapTupleIsValid(tuple))
+		return NULL; /* no verifier available */
+
+	verifier_datum = SysCacheGetAttr(AUTHVERIFROLEMETH, tuple,
+							   Anum_pg_auth_verifiers_value,
+							   &isnull);
+	verifier = TextDatumGetCString(verifier_datum);
+
+	ReleaseSysCache(tuple);
+
+	return verifier;
+}
 
 /*
  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,29 +71,39 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				 char **logdetail)
 {
 	int			retval = STATUS_ERROR;
-	char	   *shadow_pass,
+	char	   *verifier,
 			   *crypt_pwd;
 	TimestampTz vuntil = 0;
+	bool		verifier_is_md5;
 	char	   *crypt_client_pass = client_pass;
 	HeapTuple	roleTup;
 	Datum		datum;
 	bool		isnull;
+	Oid			roleid;
 
 	/* Get role info from pg_authid */
 	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
 	if (!HeapTupleIsValid(roleTup))
 		return STATUS_ERROR;	/* no such user */
 
-	datum = SysCacheGetAttr(AUTHNAME, roleTup,
-							Anum_pg_authid_rolpassword, &isnull);
-	if (isnull)
+	verifier_is_md5 = true;
+
+	roleid = HeapTupleGetOid(roleTup);
+	verifier = get_role_verifier(roleid, AUTH_VERIFIER_MD5);
+	if (verifier == NULL)
 	{
-		ReleaseSysCache(roleTup);
-		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
-							  role);
-		return STATUS_ERROR;	/* user has no password */
+		/* we can also use a plaintext password, by creating the hash from it */
+		verifier_is_md5 = false;
+		verifier = get_role_verifier(roleid, AUTH_VERIFIER_PLAIN);
+
+		if (verifier == NULL)
+		{
+			*logdetail = psprintf(_("User \"%s\" has no password assigned for authentication method \"%s\"."),
+								  role, "md5");
+			ReleaseSysCache(roleTup);
+			return STATUS_ERROR;
+		}
 	}
-	shadow_pass = TextDatumGetCString(datum);
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
 							Anum_pg_authid_rolvaliduntil, &isnull);
@@ -70,7 +112,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 
 	ReleaseSysCache(roleTup);
 
-	if (*shadow_pass == '\0')
+	if (*verifier == '\0')
 		return STATUS_ERROR;	/* empty password */
 
 	CHECK_FOR_INTERRUPTS();
@@ -83,10 +125,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 	{
 		case uaMD5:
 			crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* stored password already encrypted, only do salt */
-				if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+				if (!pg_md5_encrypt(verifier + strlen("md5"),
 									port->md5Salt,
 									sizeof(port->md5Salt), crypt_pwd))
 				{
@@ -99,7 +141,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				/* stored password is plain, double-encrypt */
 				char	   *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
 
-				if (!pg_md5_encrypt(shadow_pass,
+				if (!pg_md5_encrypt(verifier,
 									port->user_name,
 									strlen(port->user_name),
 									crypt_pwd2))
@@ -121,7 +163,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 			}
 			break;
 		default:
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* Encrypt user-supplied password to match stored MD5 */
 				crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
@@ -134,7 +176,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 					return STATUS_ERROR;
 				}
 			}
-			crypt_pwd = shadow_pass;
+			crypt_pwd = verifier;
 			break;
 	}
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c8425d..6034dac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2677,6 +2677,17 @@ _copyRoleSpec(const RoleSpec *from)
 	return newnode;
 }
 
+static AuthVerifierSpec *
+_copyAuthVerifierSpec(const AuthVerifierSpec *from)
+{
+	AuthVerifierSpec *newnode = makeNode(AuthVerifierSpec);
+
+	COPY_SCALAR_FIELD(veriftype);
+	COPY_STRING_FIELD(value);
+
+	return newnode;
+}
+
 static Query *
 _copyQuery(const Query *from)
 {
@@ -4964,6 +4975,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_AuthVerifierSpec:
+			retval = _copyAuthVerifierSpec(from);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1d6c43c..935b8dd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2581,6 +2581,15 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalAuthVerifierSpec(const AuthVerifierSpec *a, const AuthVerifierSpec *b)
+{
+	COMPARE_SCALAR_FIELD(veriftype);
+	COMPARE_STRING_FIELD(value);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3321,6 +3330,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_AuthVerifierSpec:
+			retval = _equalAuthVerifierSpec(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6b02cec..5b080c2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,15 +51,18 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
+#include "commands/user.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/gramparse.h"
 #include "parser/parser.h"
 #include "parser/parse_expr.h"
 #include "storage/lmgr.h"
+#include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/datetime.h"
 #include "utils/numeric.h"
@@ -145,6 +148,7 @@ static Node *makeNullAConst(int location);
 static Node *makeAConst(Value *v, int location);
 static Node *makeBoolAConst(bool state, int location);
 static Node *makeRoleSpec(RoleSpecType type, int location);
+static Node *makeAuthVerifierSpec(char type, char *password);
 static void check_qualified_name(List *names, core_yyscan_t yyscanner);
 static List *check_func_name(List *names, core_yyscan_t yyscanner);
 static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -370,6 +374,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				create_generic_options alter_generic_options
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
+				auth_verifier_list
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -497,6 +502,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		createdb_opt_name
 %type <node>	var_value zone_value
 %type <node>	auth_ident RoleSpec opt_granted_by
+%type <node>	AuthVerifierSpec
 
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
@@ -638,7 +644,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERIFIERS VERSION_P VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -919,25 +925,86 @@ AlterOptRoleList:
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
+auth_verifier_list:
+			AuthVerifierSpec
+				{ $$ = list_make1((Node*)$1); }
+			| auth_verifier_list ',' AuthVerifierSpec
+				{ $$ = lappend($1, (Node *)$3); }
+
+AuthVerifierSpec:
+			NonReservedWord '=' Sconst
+				{
+					char	type;
+
+					if (strcmp($1, "md5") == 0)
+						type = AUTH_VERIFIER_MD5;
+					else if (strcmp($1, "plain") == 0)
+						type = AUTH_VERIFIER_PLAIN;
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized authorization verifier option \"%s\"", $1),
+									 parser_errposition(@1)));
+					$$ = (Node *) makeAuthVerifierSpec(type, $3);
+				}
+
 AlterOptRoleElem:
 			PASSWORD Sconst
 				{
-					$$ = makeDefElem("password",
-									 (Node *)makeString($2));
+					char	   *rawstring = pstrdup(Password_encryption);
+					List	   *elemlist;
+					ListCell   *l;
+					List	   *result = NIL;
+
+					if (!SplitIdentifierString(rawstring, ',', &elemlist))
+						Assert(false); /* should not happen */
+
+					foreach(l, elemlist)
+					{
+						char	   *meth_name = (char *) lfirst(l);
+						char		veriftype;
+						AuthVerifierSpec *n;
+
+						if (strcmp(meth_name, "md5") == 0)
+							veriftype = AUTH_VERIFIER_MD5;
+						else if (strcmp(meth_name, "plain") == 0)
+							veriftype = AUTH_VERIFIER_PLAIN;
+						else
+							Assert(false);	/* should not happen */
+						n = (AuthVerifierSpec *)
+							makeAuthVerifierSpec(veriftype, $2);
+						result = lappend(result, (Node *)n);
+					}
+					pfree(rawstring);
+					list_free(elemlist);
+
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *) result);
 				}
 			| PASSWORD NULL_P
 				{
-					$$ = makeDefElem("password", NULL);
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, NULL);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
 				}
 			| ENCRYPTED PASSWORD Sconst
 				{
-					$$ = makeDefElem("encryptedPassword",
-									 (Node *)makeString($3));
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_MD5, $3);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
 				}
 			| UNENCRYPTED PASSWORD Sconst
 				{
-					$$ = makeDefElem("unencryptedPassword",
-									 (Node *)makeString($3));
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, $3);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
+				}
+			| PASSWORD VERIFIERS '(' auth_verifier_list ')'
+				{
+					$$ = makeDefElem("passwordVerifiers", (Node *)$4);
 				}
 			| INHERIT
 				{
@@ -13878,6 +13945,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARYING
+			| VERIFIERS
 			| VERSION_P
 			| VIEW
 			| VIEWS
@@ -14272,6 +14340,20 @@ makeRoleSpec(RoleSpecType type, int location)
 	return (Node *) spec;
 }
 
+/* makeAuthVerifierSpec
+ * Create a AuthVerifierSpec for the given type.
+ */
+static Node *
+makeAuthVerifierSpec(char type, char *password)
+{
+	AuthVerifierSpec *spec = makeNode(AuthVerifierSpec);
+
+	spec->veriftype = type;
+	spec->value = password;
+
+	return (Node *) spec;
+}
+
 /* check_qualified_name --- check the result of qualified_name production
  *
  * It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 577c059..9befb84 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1073,6 +1073,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
 		case AUTHNAME:
 		case AUTHOID:
 		case AUTHMEMMEMROLE:
+		case AUTHVERIFROLEMETH:
 
 			/*
 			 * Protect authentication lookups occurring before relcache has
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 44e9509..0990e71 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
@@ -97,6 +98,7 @@ static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type
 static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
 static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
 static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
+static const FormData_pg_attribute Desc_pg_auth_verifiers[Natts_pg_auth_verifiers] = {Schema_pg_auth_verifiers};
 static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
 
 /*
@@ -1539,7 +1541,7 @@ LookupOpclassInfo(Oid operatorClassOid,
  *		catalogs.
  *
  * formrdesc is currently used for: pg_database, pg_authid, pg_auth_members,
- * pg_class, pg_attribute, pg_proc, and pg_type
+ * pg_auth_verifiers, pg_class, pg_attribute, pg_proc, and pg_type
  * (see RelationCacheInitializePhase2/3).
  *
  * Note that these catalogs can't have constraints (except attnotnull),
@@ -2816,6 +2818,7 @@ RelationBuildLocalRelation(const char *relname,
 		case DatabaseRelationId:
 		case AuthIdRelationId:
 		case AuthMemRelationId:
+		case AuthVerifRelationId:
 		case RelationRelationId:
 		case AttributeRelationId:
 		case ProcedureRelationId:
@@ -3202,8 +3205,10 @@ RelationCacheInitializePhase2(void)
 				  true, Natts_pg_authid, Desc_pg_authid);
 		formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
 				  false, Natts_pg_auth_members, Desc_pg_auth_members);
+		formrdesc("pg_auth_verifiers", AuthVerifRelation_Rowtype_Id, true,
+				  false, Natts_pg_auth_verifiers, Desc_pg_auth_verifiers);
 
-#define NUM_CRITICAL_SHARED_RELS	3	/* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS	4	/* fix if you change list above */
 	}
 
 	MemoryContextSwitchTo(oldcxt);
@@ -3323,8 +3328,8 @@ RelationCacheInitializePhase3(void)
 	 * initial lookup of MyDatabaseId, without which we'll never find any
 	 * non-shared catalogs at all.  Autovacuum calls InitPostgres with a
 	 * database OID, so it instead depends on DatabaseOidIndexId.  We also
-	 * need to nail up some indexes on pg_authid and pg_auth_members for use
-	 * during client authentication.
+	 * need to nail up some indexes on pg_authid, pg_auth_verifiers and
+	 * pg_auth_members for use during client authentication.
 	 */
 	if (!criticalSharedRelcachesBuilt)
 	{
@@ -3338,8 +3343,10 @@ RelationCacheInitializePhase3(void)
 							AuthIdRelationId);
 		load_critical_index(AuthMemMemRoleIndexId,
 							AuthMemRelationId);
+		load_critical_index(AuthVerifRoleMethodIndexId,
+							AuthVerifRelationId);
 
-#define NUM_CRITICAL_SHARED_INDEXES 5	/* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 6	/* fix if you change list above */
 
 		criticalSharedRelcachesBuilt = true;
 	}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index efce7b9..5294558 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
@@ -247,6 +248,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{AuthVerifRelationId,		/* AUTHVERIFMETHROLE */
+	 AuthVerifMethodRoleIndexId,
+		2,
+		{
+			Anum_pg_auth_verifiers_method,
+			Anum_pg_auth_verifiers_roleid,
+			0,
+			0
+		},
+		4
+	},
+	{AuthVerifRelationId,		/* AUTHVERIFROLEMETH */
+	 AuthVerifRoleMethodIndexId,
+		2,
+		{
+			Anum_pg_auth_verifiers_roleid,
+			Anum_pg_auth_verifiers_method,
+			0,
+			0
+		},
+		4
+	},
 	{
 		CastRelationId,			/* CASTSOURCETARGET */
 		CastSourceTargetIndexId,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b3dac51..082108e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -179,6 +179,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
 static bool check_application_name(char **newval, void **extra, GucSource source);
 static void assign_application_name(const char *newval, void *extra);
 static bool check_cluster_name(char **newval, void **extra, GucSource source);
+static bool check_password_encryption(char **newval, void **extra,
+									  GucSource source);
 static const char *show_unix_socket_permissions(void);
 static const char *show_log_file_mode(void);
 
@@ -425,8 +427,6 @@ bool		check_function_bodies = true;
 bool		default_with_oids = false;
 bool		SQL_inheritance = true;
 
-bool		Password_encryption = true;
-
 int			log_min_error_statement = ERROR;
 int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
@@ -438,6 +438,8 @@ int			temp_file_limit = -1;
 
 int			num_temp_buffers = 1024;
 
+char	   *Password_encryption;
+
 char	   *cluster_name = "";
 char	   *ConfigFileName;
 char	   *HbaFileName;
@@ -1304,17 +1306,6 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
-			gettext_noop("Encrypt passwords."),
-			gettext_noop("When a password is specified in CREATE USER or "
-			   "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
-						 "this parameter determines whether the password is to be encrypted.")
-		},
-		&Password_encryption,
-		true,
-		NULL, NULL, NULL
-	},
-	{
 		{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
 			gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
 			gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3262,6 +3253,19 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
+		{"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+			gettext_noop("List of password encryption methods."),
+			gettext_noop("When a password is specified in CREATE USER or "
+				"ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+				"this parameter determines how the password is to be encrypted."),
+			GUC_LIST_INPUT
+		},
+		&Password_encryption,
+		"md5",
+		check_password_encryption, NULL, NULL
+	},
+
+	{
 		{"ssl_cert_file", PGC_POSTMASTER, CONN_AUTH_SECURITY,
 			gettext_noop("Location of the SSL server certificate file."),
 			NULL
@@ -10116,6 +10120,41 @@ check_cluster_name(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+static bool
+check_password_encryption(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring = pstrdup(*newval);	/* get copy of list string */
+	List	   *elemlist;
+	ListCell   *l;
+
+	if (!SplitIdentifierString(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		pfree(rawstring);
+		list_free(elemlist);
+		Assert(false);
+		return false;	/* GUC machinery should have already complained */
+	}
+
+	/* Check that only supported formats are listed */
+	foreach(l, elemlist)
+	{
+		char	   *encryption_name = (char *) lfirst(l);
+
+		if (strcmp(encryption_name, "md5") != 0 &&
+			strcmp(encryption_name, "plain") != 0)
+		{
+			pfree(rawstring);
+			list_free(elemlist);
+			return false;
+		}
+	}
+
+	pfree(rawstring);
+	list_free(elemlist);
+	return true;
+}
+
 static const char *
 show_unix_socket_permissions(void)
 {
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e5d275d..bc0efbf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -87,7 +87,7 @@
 #ssl_key_file = 'server.key'		# (change requires restart)
 #ssl_ca_file = ''			# (change requires restart)
 #ssl_crl_file = ''			# (change requires restart)
-#password_encryption = on
+#password_encryption = 'md5'
 #db_user_namespace = off
 #row_security = on
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index feeff9e..9ae22bc 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1551,10 +1551,11 @@ setup_auth(void)
 	const char **line;
 	static const char *pg_authid_setup[] = {
 		/*
-		 * The authid table shouldn't be readable except through views, to
-		 * ensure passwords are not publicly visible.
+		 * The authorization tables shouldn't be readable except through
+		 * views, to ensure password data are not publicly visible.
 		 */
 		"REVOKE ALL on pg_authid FROM public;\n",
+		"REVOKE ALL on pg_auth_verifiers FROM public;\n",
 		NULL
 	};
 
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c4b6ae8..b4d09e5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -663,8 +663,22 @@ dumpRoles(PGconn *conn)
 				i_is_current_user;
 	int			i;
 
-	/* note: rolconfig is dumped later */
-	if (server_version >= 90500)
+	/*
+	 * Note: rolconfig is dumped later. In 9.6 and above, password
+	 * information is dumped later on.
+	 */
+	if (server_version >= 90600)
+		printfPQExpBuffer(buf,
+						  "SELECT oid, rolname, rolsuper, rolinherit, "
+						  "rolcreaterole, rolcreatedb, "
+						  "rolcanlogin, rolconnlimit, "
+						  "null::text as rolpassword, "
+						  "rolvaliduntil, rolreplication, rolbypassrls, "
+			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+						  "rolname = current_user AS is_current_user "
+						  "FROM pg_authid "
+						  "ORDER BY 2");
+	else if (server_version >= 90500)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
 						  "rolcreaterole, rolcreatedb, "
@@ -869,6 +883,65 @@ dumpRoles(PGconn *conn)
 
 	PQclear(res);
 
+	/*
+	 * Dump password configuration for all roles.
+	 */
+	if (server_version >= 90600)
+	{
+		char   *current_user = NULL;
+		bool	first_elt = true;
+		res = executeQuery(conn,
+						   "SELECT a.rolname, v.verimet, v.verival "
+						   "FROM pg_auth_verifiers AS v "
+						   "LEFT JOIN pg_authid AS a ON (v.roleid = a.oid) "
+						   "ORDER BY rolname;");
+
+		for (i = 0; i < PQntuples(res); i++)
+		{
+			char   *user_name = PQgetvalue(res, i, 0);
+			char	verifier_meth = *PQgetvalue(res, i, 1);
+			char   *verifier_value = PQgetvalue(res, i, 2);
+
+			/* Switch to new ALTER ROLE query when a different user is found */
+			if (current_user == NULL ||
+				strcmp(user_name, current_user) != 0)
+			{
+				/* Finish last query */
+				if (current_user != NULL)
+				{
+					appendPQExpBufferStr(buf, ");\n");
+					fprintf(OPF, "%s", buf->data);
+				}
+
+				resetPQExpBuffer(buf);
+
+				if (current_user)
+					pg_free(current_user);
+				current_user = pg_strdup(user_name);
+				first_elt = true;
+				appendPQExpBuffer(buf, "ALTER ROLE %s PASSWORD VERIFIERS (",
+					current_user);
+			}
+
+			if (first_elt)
+				first_elt = false;
+			else
+				appendPQExpBufferStr(buf, ", ");
+
+			if (verifier_meth == 'm')
+				appendPQExpBufferStr(buf, "md5 = ");
+			else if (verifier_meth == 'p')
+				appendPQExpBufferStr(buf, "plain = ");
+			appendStringLiteralConn(buf, verifier_value, conn);
+		}
+		if (current_user != NULL)
+		{
+			appendPQExpBufferStr(buf, ");\n");
+			fprintf(OPF, "%s", buf->data);
+		}
+	}
+
+
 	fprintf(OPF, "\n\n");
 
 	destroyPQExpBuffer(buf);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index c38958d..cd262a2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -97,6 +97,11 @@ DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, on pg_auth_members
 DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
 #define AuthMemMemRoleIndexId	2695
 
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_role_method_index, 3315, on pg_auth_verifiers using btree(roleid oid_ops, verimet char_ops));
+#define AuthVerifRoleMethodIndexId	3315
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_method_role_index, 3316, on pg_auth_verifiers using btree(verimet char_ops, roleid oid_ops));
+#define AuthVerifMethodRoleIndexId	3316
+
 DECLARE_UNIQUE_INDEX(pg_cast_oid_index, 2660, on pg_cast using btree(oid oid_ops));
 #define CastOidIndexId	2660
 DECLARE_UNIQUE_INDEX(pg_cast_source_target_index, 2661, on pg_cast using btree(castsource oid_ops, casttarget oid_ops));
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
new file mode 100644
index 0000000..daef049
--- /dev/null
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_verifiers.h
+ *	  definition of the system "authorization password hashes" relation
+ *	  (pg_auth_verifiers) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_auth_verifiers.h
+ *
+ * NOTES
+ *	  the genbki.pl script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_VERIFIERS_H
+#define PG_AUTH_VERIFIERS_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_auth_verifiers definition.  cpp turns this into
+ *		typedef struct FormData_pg_auth_verifiers
+ * ----------------
+ */
+#define AuthVerifRelationId	3300
+#define AuthVerifRelation_Rowtype_Id	3308
+
+CATALOG(pg_auth_verifiers,3300) BKI_SHARED_RELATION BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(3308) BKI_SCHEMA_MACRO
+{
+	Oid			roleid;					/* ID of the role using this hash */
+	char		verimet;				/* Method used to generate the hash *
+										 * See AUTH_VERIFIER_xxx below */
+
+#ifdef CATALOG_VARLEN					/* variable-length fields start here */
+	text verival BKI_FORCE_NOT_NULL;	/* Hash value */
+#endif
+} FormData_pg_auth_verifiers;
+
+/* ----------------
+ *		Form_pg_auth_verifiers corresponds to a pointer to a tuple with
+ *		the format of pg_auth_verifiers relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
+
+/* ----------------
+ *		compiler constants for pg_auth_verifiers
+ * ----------------
+ */
+#define Natts_pg_auth_verifiers				3
+#define Anum_pg_auth_verifiers_roleid		1
+#define Anum_pg_auth_verifiers_method		2
+#define Anum_pg_auth_verifiers_value		3
+
+#define AUTH_VERIFIER_PLAIN	'p'		/* plain verifier */
+#define AUTH_VERIFIER_MD5	'm'		/* md5 verifier */
+
+#endif   /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index d5f19d6..623dc1d 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,6 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
 
 	/* remaining fields may be null; use heap_getattr to read them! */
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
-	text		rolpassword;	/* password, if any */
 	timestamptz rolvaliduntil;	/* password expiration time, if any */
 #endif
 } FormData_pg_authid;
@@ -75,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  *		compiler constants for pg_authid
  * ----------------
  */
-#define Natts_pg_authid					11
+#define Natts_pg_authid					10
 #define Anum_pg_authid_rolname			1
 #define Anum_pg_authid_rolsuper			2
 #define Anum_pg_authid_rolinherit		3
@@ -85,8 +84,7 @@ typedef FormData_pg_authid *Form_pg_authid;
 #define Anum_pg_authid_rolreplication	7
 #define Anum_pg_authid_rolbypassrls		8
 #define Anum_pg_authid_rolconnlimit		9
-#define Anum_pg_authid_rolpassword		10
-#define Anum_pg_authid_rolvaliduntil	11
+#define Anum_pg_authid_rolvaliduntil	10
 
 /* ----------------
  *		initial contents of pg_authid
@@ -95,7 +93,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  * user choices.
  * ----------------
  */
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_));
 
 #define BOOTSTRAP_SUPERUSERID 10
 
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..636e8ac 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,12 +14,13 @@
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
+/* GUC parameter */
+extern char *Password_encryption;
 
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT		0
-#define PASSWORD_TYPE_MD5			1
-
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username,
+								List *passwordVerifiers,
+								Datum validuntil_time,
+								bool validuntil_null);
 
 extern PGDLLIMPORT check_password_hook_type check_password_hook;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 748e434..b060ce8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -427,6 +427,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_AuthVerifierSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151c93a..6ac716d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -309,6 +309,17 @@ typedef struct RoleSpec
 } RoleSpec;
 
 /*
+ * AuthVerifierSpec - a password verifier with a some dedicated values.
+ */
+typedef struct AuthVerifierSpec
+{
+	NodeTag		type;
+	char		veriftype;		/* type of this verifier, as listed in *
+								 * pg_auth_verifiers.h */
+	char	   *value;			/* value specified by user */
+} AuthVerifierSpec;
+
+/*
  * FuncCall - a function or aggregate invocation
  *
  * agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)', or if
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 2414069..c8201b4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -415,6 +415,7 @@ PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("verifiers", VERIFIERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 18404e2..0d6c29e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,8 @@ enum SysCacheIdentifier
 	AUTHMEMROLEMEM,
 	AUTHNAME,
 	AUTHOID,
+	AUTHVERIFMETHROLE,
+	AUTHVERIFROLEMETH,
 	CASTSOURCETARGET,
 	CLAAMNAMENSP,
 	CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..5639a37
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,101 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR:  invalid value for parameter "password_encryption": "novalue"
+SET password_encryption = true; -- error
+ERROR:  invalid value for parameter "password_encryption": "true"
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+   rolname    | verimet | substr 
+--------------+---------+--------
+ role_passwd1 | p       | rol
+ role_passwd2 | m       | md5
+ role_passwd3 | m       | md5
+ role_passwd3 | p       | rol
+(4 rows)
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+NOTICE:  MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname = 'role_passwd3_new'
+    ORDER BY a.rolname, v.verimet;
+     rolname      | verimet | substr 
+------------------+---------+--------
+ role_passwd3_new | p       | rol
+(1 row)
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 ENCRYPTED PASSWORD 'po'; -- encrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'po'; -- unencrypted
+ALTER ROLE role_passwd3_new UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+     rolname      | verimet | substr 
+------------------+---------+--------
+ role_passwd1     | m       | md5
+ role_passwd2     | p       | po
+ role_passwd3_new | m       | md5
+(3 rows)
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ERROR:  unrecognized authorization verifier option "unexistent_verif"
+LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
+                                                    ^
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd3_new PASSWORD VERIFIERS (plain = 'foo', md5 = 'foo2'); -- ok
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+     rolname      | verimet | substr 
+------------------+---------+--------
+ role_passwd1     | m       | md5
+ role_passwd2     | p       | foo
+ role_passwd3_new | m       | md5
+ role_passwd3_new | p       | foo
+(4 rows)
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3_new;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
+ rolname | verimet 
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6206c81..8a34d54 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1627,7 +1627,14 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.rolsuper AS usesuper,
     pg_authid.rolreplication AS userepl,
     pg_authid.rolbypassrls AS usebypassrls,
-    pg_authid.rolpassword AS passwd,
+    ARRAY( SELECT
+                CASE pg_auth_verifiers.verimet
+                    WHEN 'p'::"char" THEN ('plain:'::text || pg_auth_verifiers.verival)
+                    WHEN 'm'::"char" THEN ('md5:'::text || pg_auth_verifiers.verival)
+                    ELSE NULL::text
+                END AS verifiers
+           FROM pg_auth_verifiers
+          WHERE (pg_auth_verifiers.roleid = pg_authid.oid)) AS verifiers,
     (pg_authid.rolvaliduntil)::abstime AS valuntil,
     s.setconfig AS useconfig
    FROM (pg_authid
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..d4b3f36 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -91,6 +91,7 @@ pg_amproc|t
 pg_attrdef|t
 pg_attribute|t
 pg_auth_members|t
+pg_auth_verifiers|t
 pg_authid|t
 pg_cast|t
 pg_class|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4df15de..c24420b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets password
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 15d74d4..88d10cf 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -109,6 +109,7 @@ test: matview
 test: lock
 test: replica_identity
 test: rowsecurity
+test: password
 test: object_address
 test: tablesample
 test: alter_generic
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4ebc7ce
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,70 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- error
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname = 'role_passwd3_new'
+    ORDER BY a.rolname, v.verimet;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 ENCRYPTED PASSWORD 'po'; -- encrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'po'; -- unencrypted
+ALTER ROLE role_passwd3_new UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd3_new PASSWORD VERIFIERS (plain = 'foo', md5 = 'foo2'); -- ok
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3_new;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
-- 
2.5.0

0002-Move-sha1.c-to-src-common.patchtext/x-diff; charset=US-ASCII; name=0002-Move-sha1.c-to-src-common.patchDownload
From 7fd549eb80ff67246973f3ce0d3dbbb87028d778 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 7 Aug 2015 13:42:53 +0900
Subject: [PATCH 2/5] Move sha1.c to src/common

---
 contrib/pgcrypto/Makefile                       | 4 ++--
 contrib/pgcrypto/internal.c                     | 2 +-
 src/common/Makefile                             | 2 +-
 {contrib/pgcrypto => src/common}/sha1.c         | 4 ++--
 {contrib/pgcrypto => src/include/common}/sha1.h | 2 +-
 src/tools/msvc/Mkvcbuild.pm                     | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)
 rename {contrib/pgcrypto => src/common}/sha1.c (99%)
 rename {contrib/pgcrypto => src/include/common}/sha1.h (98%)

diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 18bad1a..bb5118e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,6 +1,6 @@
 # contrib/pgcrypto/Makefile
 
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
+INT_SRCS = md5.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
 		fortuna.c random.c pgp-mpi-internal.c imath.c
 INT_TESTS = sha2
 
@@ -30,7 +30,7 @@ DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
 	pgcrypto--unpackaged--1.0.sql
 PGFILEDESC = "pgcrypto - cryptographic functions"
 
-REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
+REGRESS = init md5 hmac-md5 hmac-sha1 blowfish rijndael \
 	$(CF_TESTS) \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
 	pgp-armor pgp-decrypt pgp-encrypt $(CF_PGP_TESTS) \
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..9f42955 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -35,7 +35,7 @@
 
 #include "px.h"
 #include "md5.h"
-#include "sha1.h"
+#include "common/sha1.h"
 #include "blf.h"
 #include "rijndael.h"
 #include "fortuna.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index c47445e..d6c2a57 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = exec.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o string.o username.o wait_error.o
+	rmtree.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
diff --git a/contrib/pgcrypto/sha1.c b/src/common/sha1.c
similarity index 99%
rename from contrib/pgcrypto/sha1.c
rename to src/common/sha1.c
index 0e753ce..4d9a325 100644
--- a/contrib/pgcrypto/sha1.c
+++ b/src/common/sha1.c
@@ -28,7 +28,7 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * contrib/pgcrypto/sha1.c
+ * src/common/sha1.c
  */
 /*
  * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
@@ -40,7 +40,7 @@
 
 #include <sys/param.h>
 
-#include "sha1.h"
+#include "common/sha1.h"
 
 /* constant table */
 static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
diff --git a/contrib/pgcrypto/sha1.h b/src/include/common/sha1.h
similarity index 98%
rename from contrib/pgcrypto/sha1.h
rename to src/include/common/sha1.h
index 5532ca1..d5ff296 100644
--- a/contrib/pgcrypto/sha1.h
+++ b/src/include/common/sha1.h
@@ -1,4 +1,4 @@
-/*	contrib/pgcrypto/sha1.h */
+/*	src/include/common/sha1.h */
 /*	   $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $    */
 
 /*
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index d70db40..bef8bf3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -106,7 +106,7 @@ sub mkvcbuild
 
 	our @pgcommonallfiles = qw(
 	  exec.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
-	  string.c username.c wait_error.c);
+	  sha1.c string.c username.c wait_error.c);
 
 	our @pgcommonfrontendfiles = (
 		@pgcommonallfiles, qw(fe_memutils.c
-- 
2.5.0

0003-Refactor-sendAuthRequest.patchtext/x-diff; charset=US-ASCII; name=0003-Refactor-sendAuthRequest.patchDownload
From 124cb3c128030a3f1fe2e75ae783bee698487898 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 3 Aug 2015 15:42:49 +0900
Subject: [PATCH 3/5] Refactor sendAuthRequest

---
 src/backend/libpq/auth.c | 65 ++++++++++++++++++++++++------------------------
 1 file changed, 32 insertions(+), 33 deletions(-)

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 4699efa..1b28722 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -36,7 +36,8 @@
  * Global authentication functions
  *----------------------------------------------------------------
  */
-static void sendAuthRequest(Port *port, AuthRequest areq);
+static void sendAuthRequest(Port *port, AuthRequest areq, char *extradata,
+				int extralen);
 static void auth_failed(Port *port, int status, char *logdetail);
 static char *recv_password_packet(Port *port);
 static int	recv_and_check_password_packet(Port *port, char **logdetail);
@@ -479,7 +480,7 @@ ClientAuthentication(Port *port)
 
 		case uaGSS:
 #ifdef ENABLE_GSS
-			sendAuthRequest(port, AUTH_REQ_GSS);
+			sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
 			status = pg_GSS_recvauth(port);
 #else
 			Assert(false);
@@ -488,7 +489,7 @@ ClientAuthentication(Port *port)
 
 		case uaSSPI:
 #ifdef ENABLE_SSPI
-			sendAuthRequest(port, AUTH_REQ_SSPI);
+			sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
 			status = pg_SSPI_recvauth(port);
 #else
 			Assert(false);
@@ -512,12 +513,13 @@ ClientAuthentication(Port *port)
 				ereport(FATAL,
 						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
 						 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
-			sendAuthRequest(port, AUTH_REQ_MD5);
+			/* Add the salt for encrypted passwords. */
+			sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
 		case uaPassword:
-			sendAuthRequest(port, AUTH_REQ_PASSWORD);
+			sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
@@ -556,7 +558,7 @@ ClientAuthentication(Port *port)
 		(*ClientAuthentication_hook) (port, status);
 
 	if (status == STATUS_OK)
-		sendAuthRequest(port, AUTH_REQ_OK);
+		sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
 	else
 		auth_failed(port, status, logdetail);
 }
@@ -566,7 +568,7 @@ ClientAuthentication(Port *port)
  * Send an authentication request packet to the frontend.
  */
 static void
-sendAuthRequest(Port *port, AuthRequest areq)
+sendAuthRequest(Port *port, AuthRequest areq, char *extradata, int extralen)
 {
 	StringInfoData buf;
 
@@ -575,27 +577,8 @@ sendAuthRequest(Port *port, AuthRequest areq)
 	pq_beginmessage(&buf, 'R');
 	pq_sendint(&buf, (int32) areq, sizeof(int32));
 
-	/* Add the salt for encrypted passwords. */
-	if (areq == AUTH_REQ_MD5)
-		pq_sendbytes(&buf, port->md5Salt, 4);
-
-#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
-
-	/*
-	 * Add the authentication data for the next step of the GSSAPI or SSPI
-	 * negotiation.
-	 */
-	else if (areq == AUTH_REQ_GSS_CONT)
-	{
-		if (port->gss->outbuf.length > 0)
-		{
-			elog(DEBUG4, "sending GSS token of length %u",
-				 (unsigned int) port->gss->outbuf.length);
-
-			pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
-		}
-	}
-#endif
+	if (extralen > 0)
+		pq_sendbytes(&buf, extradata, extralen);
 
 	pq_endmessage(&buf);
 
@@ -908,7 +891,15 @@ pg_GSS_recvauth(Port *port)
 			elog(DEBUG4, "sending GSS response token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			gss_release_buffer(&lmin_s, &port->gss->outbuf);
 		}
@@ -1151,7 +1142,15 @@ pg_SSPI_recvauth(Port *port)
 			port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
 			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
 		}
@@ -1672,7 +1671,7 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
 					 * let's go ask the client to send a password, which we
 					 * then stuff into PAM.
 					 */
-					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
+					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
 					passwd = recv_password_packet(pam_port_cludge);
 					if (passwd == NULL)
 					{
@@ -1947,7 +1946,7 @@ CheckLDAPAuth(Port *port)
 	if (port->hba->ldapport == 0)
 		port->hba->ldapport = LDAP_PORT;
 
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
@@ -2303,7 +2302,7 @@ CheckRADIUSAuth(Port *port)
 		identifier = port->hba->radiusidentifier;
 
 	/* Send regular password request to client, and get the response */
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
-- 
2.5.0

0004-Refactor-RandomSalt-to-handle-salts-of-different-len.patchtext/x-diff; charset=US-ASCII; name=0004-Refactor-RandomSalt-to-handle-salts-of-different-len.patchDownload
From 8c9f62e92bb500b3c0716979ea305155892c304e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 11 Aug 2015 20:43:26 +0900
Subject: [PATCH 4/5] Refactor RandomSalt to handle salts of different lengths

---
 src/backend/postmaster/postmaster.c | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 000524d..c2d5b0e 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -401,7 +401,7 @@ static int	initMasks(fd_set *rmask);
 static void report_fork_failure_to_client(Port *port, int errnum);
 static CAC_state canAcceptConnections(void);
 static long PostmasterRandom(void);
-static void RandomSalt(char *md5Salt);
+static void RandomSalt(char *salt, int len);
 static void signal_child(pid_t pid, int signal);
 static bool SignalSomeChildren(int signal, int targets);
 static bool SignalUnconnectedWorkers(int signal);
@@ -2282,7 +2282,7 @@ ConnCreate(int serverFd)
 	 * after.  Else the postmaster's random sequence won't get advanced, and
 	 * all backends would end up using the same salt...
 	 */
-	RandomSalt(port->md5Salt);
+	RandomSalt(port->md5Salt, sizeof(port->md5Salt));
 
 	/*
 	 * Allocate GSSAPI specific state struct
@@ -5039,23 +5039,21 @@ StartupPacketTimeoutHandler(void)
  * RandomSalt
  */
 static void
-RandomSalt(char *md5Salt)
+RandomSalt(char *md5Salt, int len)
 {
 	long		rand;
+	int			i;
 
 	/*
 	 * We use % 255, sacrificing one possible byte value, so as to ensure that
 	 * all bits of the random() value participate in the result. While at it,
 	 * add one to avoid generating any null bytes.
 	 */
-	rand = PostmasterRandom();
-	md5Salt[0] = (rand % 255) + 1;
-	rand = PostmasterRandom();
-	md5Salt[1] = (rand % 255) + 1;
-	rand = PostmasterRandom();
-	md5Salt[2] = (rand % 255) + 1;
-	rand = PostmasterRandom();
-	md5Salt[3] = (rand % 255) + 1;
+	for (i = 0; i < len; i++)
+	{
+		rand = PostmasterRandom();
+		md5Salt[i] = (rand % 255) + 1;
+	}
 }
 
 /*
-- 
2.5.0

0005-SCRAM-authentication.patchtext/x-diff; charset=US-ASCII; name=0005-SCRAM-authentication.patchDownload
From ea46790edf57b7852649a8f74c44767e45f9cdb2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 12 Aug 2015 13:09:06 +0900
Subject: [PATCH 5/5] SCRAM authentication

---
 contrib/passwordcheck/passwordcheck.c   |   3 +
 doc/src/sgml/catalogs.sgml              |   3 +-
 doc/src/sgml/config.sgml                |   3 +-
 doc/src/sgml/protocol.sgml              | 148 +++++++-
 src/backend/catalog/system_views.sql    |   1 +
 src/backend/commands/user.c             |   8 +
 src/backend/libpq/Makefile              |   2 +-
 src/backend/libpq/auth-scram.c          | 614 ++++++++++++++++++++++++++++++++
 src/backend/libpq/auth.c                | 125 ++++++-
 src/backend/libpq/crypt.c               |   4 +-
 src/backend/libpq/hba.c                 |  14 +
 src/backend/libpq/pg_hba.conf.sample    |   2 +-
 src/backend/parser/gram.y               |   4 +
 src/backend/postmaster/postmaster.c     |   1 +
 src/backend/utils/adt/encode.c          |   8 +-
 src/backend/utils/misc/guc.c            |   3 +-
 src/bin/pg_dump/pg_dumpall.c            |   2 +
 src/common/Makefile                     |   2 +-
 src/common/scram-common.c               | 161 +++++++++
 src/include/catalog/pg_auth_verifiers.h |   1 +
 src/include/common/scram-common.h       |  40 +++
 src/include/libpq/auth.h                |   5 +
 src/include/libpq/crypt.h               |   1 +
 src/include/libpq/hba.h                 |   1 +
 src/include/libpq/libpq-be.h            |   3 +-
 src/include/libpq/pqcomm.h              |   2 +
 src/include/libpq/scram.h               |  23 ++
 src/include/utils/builtins.h            |   4 +
 src/interfaces/libpq/.gitignore         |   1 +
 src/interfaces/libpq/Makefile           |   7 +-
 src/interfaces/libpq/fe-auth-scram.c    | 524 +++++++++++++++++++++++++++
 src/interfaces/libpq/fe-auth.c          |  95 +++++
 src/interfaces/libpq/fe-auth.h          |   8 +
 src/interfaces/libpq/fe-connect.c       |  51 +++
 src/interfaces/libpq/libpq-int.h        |   5 +
 src/interfaces/libpq/sha1.c             |   1 +
 36 files changed, 1856 insertions(+), 24 deletions(-)
 create mode 100644 src/backend/libpq/auth-scram.c
 create mode 100644 src/common/scram-common.c
 create mode 100644 src/include/common/scram-common.h
 create mode 100644 src/include/libpq/scram.h
 create mode 100644 src/interfaces/libpq/fe-auth-scram.c
 create mode 120000 src/interfaces/libpq/sha1.c

diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 5ee38ed..10e1ca9 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -135,6 +135,9 @@ check_password(const char *username,
 #endif
 				break;
 
+			case AUTH_VERIFIER_SCRAM:
+				/* unfortunately not much can be done here */
+				break;
 			default:
 				elog(ERROR, "unrecognized password type: %d", spec->veriftype);
 				break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6d3323b..796290b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1522,7 +1522,8 @@
       <entry><type>char</type></entry>
       <entry>
        <literal>p</> = plain format,
-       <literal>m</> = MD5-encrypted
+       <literal>m</> = MD5-encrypted,
+       <literal>s</> = SCRAM-SHA1-encrypted
       </entry>
      </row>
 
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5ae27f8..dbcb985 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1171,7 +1171,8 @@ include_dir 'conf.d'
       <listitem>
        <para>
         Specifies a comma-separated list of password encryption formats.
-        Supported formats are <literal>plain</> and <literal>md5</>.
+        Supported formats are <literal>plain</>,<literal>md5</> and
+        <literal>scram</>.
        </para>
 
        <para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 42e9497..2a9b0e4 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -228,11 +228,11 @@
     The server then sends an appropriate authentication request message,
     to which the frontend must reply with an appropriate authentication
     response message (such as a password).
-    For all authentication methods except GSSAPI and SSPI, there is at most
-    one request and one response. In some methods, no response
+    For all authentication methods except GSSAPI, SSPI and SASL, there is at
+    most one request and one response. In some methods, no response
     at all is needed from the frontend, and so no authentication request
-    occurs. For GSSAPI and SSPI, multiple exchanges of packets may be needed
-    to complete the authentication.
+    occurs. For GSSAPI, SSPI and SASL, multiple exchanges of packets may be
+    needed to complete the authentication.
    </para>
 
    <para>
@@ -366,6 +366,35 @@
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <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.
+       </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.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
 
@@ -2572,6 +2601,115 @@ AuthenticationGSSContinue (B)
 </listitem>
 </varlistentry>
 
+<varlistentry>
+<term>
+AuthenticationSASL (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(10)
+</term>
+<listitem>
+<para>
+                Specifies that SASL authentication is started.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of a SASL authentication mechanism.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+AuthenticationSASLContinue (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(11)
+</term>
+<listitem>
+<para>
+                Specifies that this message contains SASL-mechanism specific
+                data.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+                SASL data, specific to the SASL mechanism being used.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
 
 <varlistentry>
 <term>
@@ -4334,7 +4472,7 @@ PasswordMessage (F)
 <listitem>
 <para>
                 Identifies the message as a password response. Note that
-                this is also used for GSSAPI and SSPI response messages
+                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).
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f9121e6..6de4821 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -38,6 +38,7 @@ CREATE VIEW pg_shadow AS
                 CASE verimet
                     WHEN 'p' THEN 'plain:' || verival
                     WHEN 'm' THEN 'md5:' || verival
+                    WHEN 's' THEN 'scram:' || verival
                 END AS verifiers
             FROM pg_auth_verifiers
 	    WHERE roleid = pg_authid.oid
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index c6bf9db..33c84e6 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -31,6 +31,7 @@
 #include "commands/seclabel.h"
 #include "commands/user.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
@@ -1623,6 +1624,13 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
 		else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
 				 isMD5(spec->value))
 			spec->veriftype = AUTH_VERIFIER_MD5;
+		else if (spec->veriftype == AUTH_VERIFIER_SCRAM)
+		{
+			if (isMD5(spec->value))
+				elog(ERROR, "Cannot use MD5-encrypted value as SCRAM verifier");
+			/* create SCRAM verifier */
+			spec->value = scram_build_verifier(rolname, spec->value, 0);
+		}
 	}
 }
 
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..3dd60e1 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
 # be-fsstubs is here for historical reasons, probably belongs elsewhere
 
 OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \
-       pqformat.o pqmq.o pqsignal.o
+       pqformat.o pqmq.o pqsignal.o auth-scram.o
 
 ifeq ($(with_openssl),yes)
 OBJS += be-secure-openssl.o
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
new file mode 100644
index 0000000..048b1c4
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,614 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ *	  Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See RFC 5802. Some differences:
+ *
+ * - Username from the authentication exchange is not used. The client
+ *   should send an empty string as the username.
+ *
+ * - Password is not processed with the SASLprep algorithm.
+ *
+ * - Channel binding is not supported.
+ *
+ * The verifier stored in pg_auth_verifiers consists of the salt, iteration
+ * count, StoredKey, and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_authid.h"
+#include "common/scram-common.h"
+#include "common/sha1.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		SALT_SENT,
+		FINISHED
+	} state;
+
+	const char *username;	/* username from startup packet */
+	char	   *salt;		/* base64-encoded */
+	int			iterations;
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+
+	/* These come from the client-first message */
+	char	   *client_first_message_bare;
+	char	   *client_username;	/* username from client-first message */
+	char	   *client_authzid;
+	char	   *client_nonce;
+
+	/* These come from the client-final message */
+	char	   *client_final_message_without_proof;
+	char	   *client_final_nonce;
+	char		ClientProof[SCRAM_KEY_LEN];
+
+	char	   *server_first_message;
+	char	   *server_nonce;		/* base64-encoded */
+	char	   *server_signature;
+
+} scram_state;
+
+static void read_client_first_message(scram_state *state, char *input);
+static void read_client_final_message(scram_state *state, char *input);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+
+static void generate_nonce(char *out, int len);
+
+/*
+ * Initialize a new SCRAM authentication exchange, with given username and
+ * its stored verifier.
+ */
+void *
+scram_init(const char *username, const char *verifier)
+{
+	scram_state *state;
+	char	   *v;
+	char	   *p;
+
+	state = (scram_state *) palloc0(sizeof(scram_state));
+	state->state = INIT;
+	state->username = username;
+
+	/*
+	 * The verifier is of form:
+	 *
+	 * salt:iterations:storedkey:serverkey
+	 */
+	v = pstrdup(verifier);
+
+	/* salt */
+	if ((p = strtok(v, ":")) == NULL)
+		goto invalid_verifier;
+	state->salt = pstrdup(p);
+
+	/* iterations */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	errno = 0;
+	state->iterations = strtol(p, &p, 10);
+	if (*p || errno != 0)
+		goto invalid_verifier;
+
+	/* storedkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+	hex_decode(p, SCRAM_KEY_LEN * 2, (char *) state->StoredKey);
+
+	/* serverkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+	hex_decode(p, SCRAM_KEY_LEN * 2, (char *) state->ServerKey);
+
+	pfree(v);
+
+	return state;
+
+invalid_verifier:
+	elog(ERROR, "invalid SCRAM verifier");
+	return NULL;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ */
+int
+scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen)
+{
+	scram_state *state = (scram_state *) opaq;
+	int			result;
+
+	*output = NULL;
+	*outputlen = 0;
+
+	if (inputlen > 0)
+		elog(DEBUG4, "got SCRAM message: %s", input);
+
+	switch (state->state)
+	{
+		case INIT:
+			/* receive username and client nonce, send challenge */
+			read_client_first_message(state, input);
+			*output = build_server_first_message(state);
+			*outputlen = strlen(*output);
+			result = SASL_EXCHANGE_CONTINUE;
+			state->state = SALT_SENT;
+			break;
+
+		case SALT_SENT:
+			/* receive response to challenge and verify it */
+			read_client_final_message(state, input);
+			if (verify_final_nonce(state) && verify_client_proof(state))
+			{
+				*output = build_server_final_message(state);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_SUCCESS;
+			}
+			else
+			{
+				result = SASL_EXCHANGE_FAILURE;
+			}
+			state->state = FINISHED;
+			break;
+
+		default:
+			elog(ERROR, "invalid SCRAM exchange state");
+			result = 0;
+	}
+
+	return result;
+}
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolverifiers.
+ *
+ * If salt is NULL, a random salt is generated. If iterations is 0, default
+ * number of iterations is used;
+ */
+char *
+scram_build_verifier(char *username, char *password, int iterations)
+{
+	uint8		keybuf[SCRAM_KEY_LEN + 1];
+	char		storedkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		serverkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		salt[SCRAM_SALT_LEN];
+	char	   *encoded_salt;
+	int			encoded_len;
+
+	if (iterations <= 0)
+		iterations = 4096;
+
+	generate_nonce(salt, SCRAM_SALT_LEN);
+
+	encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+	encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+	encoded_salt[encoded_len] = '\0';
+
+	/* Calculate StoredKey, and encode it in hex */
+	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+							iterations, "Client Key", keybuf);
+	scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, storedkey_hex);
+	storedkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	/* And same for ServerKey */
+	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN, iterations,
+							"Server Key", keybuf);
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
+	serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	return psprintf("%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
+}
+
+
+static char *
+read_attr_value(char **input, char attr)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		elog(ERROR, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+read_any_attr(char **input, char *attr_p)
+{
+	char *begin = *input;
+	char *end;
+	char attr = *begin;
+
+	if (!((attr >= 'A' && attr <= 'Z') ||
+		  (attr >= 'a' && attr <= 'z')))
+		elog(ERROR, "malformed SCRAM message (invalid attribute char)");
+	if (attr_p)
+		*attr_p = attr;
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static void
+read_client_first_message(scram_state *state, char *input)
+{
+	input = pstrdup(input);
+
+	/*
+	 * saslname        = 1*(value-safe-char / "=2C" / "=3D")
+	 *              ;; Conforms to <value>.
+	 *
+	 * authzid         = "a=" saslname
+	 *              ;; Protocol specific.
+	 *
+	 * username        = "n=" saslname
+	 *               ;; Usernames are prepared using SASLprep.
+	 *
+	 * gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
+	 *               ;; "n" -> client doesn't support channel binding.
+	 *               ;; "y" -> client does support channel binding
+	 *               ;;        but thinks the server does not.
+	 *               ;; "p" -> client requires channel binding.
+	 *               ;; The selected channel binding follows "p=".
+	 *
+	 * gs2-header      = gs2-cbind-flag "," [ authzid ] ","
+	 *               ;; GS2 header for SCRAM
+	 *               ;; (the actual GS2 header includes an optional
+	 *               ;; flag to indicate that the GSS mechanism is not
+	 *               ;; "standard", but since SCRAM is "standard", we
+	 *               ;; don't include that flag).
+	 *
+	 *   client-first-message-bare =
+	 *               [reserved-mext ","]
+	 *               username "," nonce ["," extensions]
+	 *
+	 *   client-first-message =
+	 *                gs2-header client-first-message-bare
+	 *
+	 *
+	 * For example:
+	 * n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+	 */
+
+	/* read gs2-cbind-flag */
+	switch (*input)
+	{
+		case 'n':
+			/* client does not support channel binding */
+			input++;
+			break;
+		case 'y':
+			/* client supports channel binding, but we're not doing it today */
+			input++;
+			break;
+		case 'p':
+			/* client requires channel binding. We don't support it */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("channel binding not supported")));
+	}
+
+	/* any mandatory extensions would go here. */
+	if (*input != ',')
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mandatory extension %c not supported", *input)));
+	input++;
+
+	/* read optional authzid (authorization identity) */
+	if (*input != ',')
+		state->client_authzid = read_attr_value(&input, 'a');
+	else
+		input++;
+
+	state->client_first_message_bare = pstrdup(input);
+
+	/* read username (FIXME: unescape) */
+	state->client_username = read_attr_value(&input, 'n');
+
+	/* read nonce */
+	state->client_nonce = read_attr_value(&input, 'r');
+
+	/*
+	 * There can be any number of optional extensions after this. We don't
+	 * support any extensions, so ignore them.
+	 */
+	while (*input != '\0')
+		read_any_attr(&input, NULL);
+
+	/* success! */
+}
+
+static bool
+verify_final_nonce(scram_state *state)
+{
+	int			client_nonce_len = strlen(state->client_nonce);
+	int			server_nonce_len = strlen(state->server_nonce);
+	int			final_nonce_len = strlen(state->client_final_nonce);
+
+	if (final_nonce_len != client_nonce_len + server_nonce_len)
+		return false;
+	if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0)
+		return false;
+	if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0)
+		return false;
+
+	return true;
+}
+
+static bool
+verify_client_proof(scram_state *state)
+{
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		client_StoredKey[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+	int			i;
+
+	/* calculate ClientSignature */
+	scram_HMAC_init(&ctx, state->StoredKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+	elog(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+	elog(DEBUG4, "AuthMessage: %s,%s,%s", state->client_first_message_bare,
+		 state->server_first_message, state->client_final_message_without_proof);
+
+	/* Extract the ClientKey that the client calculated from the proof */
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+
+	/* Hash it one more time, and compare with StoredKey */
+	scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey);
+	elog(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+	elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+	elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+	if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+		return false;
+
+	return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+	char		nonce[SCRAM_NONCE_LEN];
+	int			encoded_len;
+
+	/*
+	 * server-first-message =
+	 *                   [reserved-mext ","] nonce "," salt ","
+	 *                   iteration-count ["," extensions]
+	 *
+	 *   nonce           = "r=" c-nonce [s-nonce]
+	 *               ;; Second part provided by server.
+	 *
+	 * c-nonce         = printable
+	 *
+	 * s-nonce         = printable
+	 *
+	 * salt            = "s=" base64
+	 *
+	 * iteration-count = "i=" posit-number
+	 *              ;; A positive number.
+	 *
+	 * Example:
+	 *
+	 * r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+	 */
+	generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+	state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+	encoded_len = b64_encode(nonce, SCRAM_NONCE_LEN, state->server_nonce);
+
+	state->server_nonce[encoded_len] = '\0';
+	state->server_first_message =
+		psprintf("r=%s%s,s=%s,i=%u",
+				 state->client_nonce, state->server_nonce,
+				 state->salt, state->iterations);
+
+	return state->server_first_message;
+}
+
+static void
+read_client_final_message(scram_state *state, char *input)
+{
+	char		attr;
+	char	   *channel_binding;
+	char	   *value;
+	char	   *begin, *proof;
+	char	   *p;
+	char	   *client_proof;
+
+	begin = p = pstrdup(input);
+
+	/*
+	 *
+	 * cbind-input   = gs2-header [ cbind-data ]
+	 *               ;; cbind-data MUST be present for
+	 *               ;; gs2-cbind-flag of "p" and MUST be absent
+	 *               ;; for "y" or "n".
+	 *
+	 * channel-binding = "c=" base64
+	 *               ;; base64 encoding of cbind-input.
+	 *
+	 * proof           = "p=" base64
+	 *
+	 * client-final-message-without-proof =
+	 *               channel-binding "," nonce ["," extensions]
+	 *
+	 * client-final-message =
+	 *              client-final-message-without-proof "," proof
+	 */
+	channel_binding = read_attr_value(&p, 'c');
+	if (strcmp(channel_binding, "biws") != 0)
+		elog(ERROR, "invalid channel binding input");
+	state->client_final_nonce = read_attr_value(&p, 'r');
+
+	/* ignore optional extensions */
+	do
+	{
+		proof = p - 1;
+		value = read_any_attr(&p, &attr);
+	} while (attr != 'p');
+
+	client_proof = palloc(b64_dec_len(value, strlen(value)));
+	if (b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+		elog(ERROR, "invalid ClientProof");
+	memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+	pfree(client_proof);
+
+	if (*p != '\0')
+		elog(ERROR, "malformed SCRAM message (garbage at end of message %c)", attr);
+
+	state->client_final_message_without_proof = palloc(proof - begin + 1);
+	memcpy(state->client_final_message_without_proof, input, proof - begin);
+	state->client_final_message_without_proof[proof - begin] = '\0';
+
+	/* FIXME: check channel_binding field */
+}
+
+
+static char *
+build_server_final_message(scram_state *state)
+{
+	uint8		ServerSignature[SCRAM_KEY_LEN];
+	char	   *server_signature_base64;
+	int			siglen;
+	scram_HMAC_ctx ctx;
+
+	/* calculate ServerSignature */
+	scram_HMAC_init(&ctx, state->ServerKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ServerSignature, &ctx);
+
+	server_signature_base64 = palloc(b64_enc_len((const char *) ServerSignature,
+												 SCRAM_KEY_LEN) + 1);
+	siglen = b64_encode((const char *) ServerSignature,
+						SCRAM_KEY_LEN, server_signature_base64);
+	server_signature_base64[siglen] = '\0';
+
+	/*
+	 *
+	 * server-error = "e=" server-error-value
+	 *
+	 * server-error-value = "invalid-encoding" /
+	 *           "extensions-not-supported" /  ; unrecognized 'm' value
+	 *            "invalid-proof" /
+	 *            "channel-bindings-dont-match" /
+	 *            "server-does-support-channel-binding" /
+	 *              ; server does not support channel binding
+	 *            "channel-binding-not-supported" /
+	 *            "unsupported-channel-binding-type" /
+	 *            "unknown-user" /
+	 *            "invalid-username-encoding" /
+	 *              ; invalid username encoding (invalid UTF-8 or
+	 *              ; SASLprep failed)
+	 *            "no-resources" /
+	 *            "other-error" /
+	 *            server-error-value-ext
+	 *     ; Unrecognized errors should be treated as "other-error".
+	 *     ; In order to prevent information disclosure, the server
+	 *     ; may substitute the real reason with "other-error".
+	 *
+	 * server-error-value-ext = value
+	 *     ; Additional error reasons added by extensions
+	 *     ; to this document.
+	 *
+	 * verifier        = "v=" base64
+	 *               ;; base-64 encoded ServerSignature.
+	 *
+	 * server-final-message = (server-error / verifier)
+	 *                ["," extensions]
+	 */
+	return psprintf("v=%s", server_signature_base64);
+}
+
+static void
+generate_nonce(char *result, int len)
+{
+	/* Use the salt generated for SASL authentication */
+	memset(result, 0, len);
+	memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 1b28722..05bba8f 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,16 +21,19 @@
 #include <arpa/inet.h>
 #include <unistd.h>
 
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "libpq/auth.h"
 #include "libpq/crypt.h"
 #include "libpq/ip.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
-
+#include "utils/syscache.h"
 
 /*----------------------------------------------------------------
  * Global authentication functions
@@ -185,6 +188,12 @@ static int	CheckRADIUSAuth(Port *port);
 
 
 /*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
  * Global authentication functions
  *----------------------------------------------------------------
  */
@@ -246,6 +255,7 @@ auth_failed(Port *port, int status, char *logdetail)
 			break;
 		case uaPassword:
 		case uaMD5:
+		case uaSASL:
 			errstr = gettext_noop("password authentication failed for user \"%s\"");
 			/* We use it to indicate if a .pgpass password failed. */
 			errcode_return = ERRCODE_INVALID_PASSWORD;
@@ -523,6 +533,10 @@ ClientAuthentication(Port *port)
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
+		case uaSASL:
+			status = CheckSASLAuth(port, &logdetail);
+			break;
+
 		case uaPAM:
 #ifdef USE_PAM
 			status = CheckPAMAuth(port, port->user_name, "");
@@ -691,6 +705,107 @@ recv_and_check_password_packet(Port *port, char **logdetail)
 	return result;
 }
 
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+	int			mtype;
+	StringInfoData buf;
+	void	   *scram_opaq;
+	char	   *verifier;
+	char	   *output = NULL;
+	int			outputlen = 0;
+	int			result;
+	HeapTuple	roleTup;
+
+	/*
+	 * SASL auth is not supported for protocol versions before 3, because it
+	 * relies on the overall message length word to determine the SASL payload
+	 * size in AuthenticationSASLContinue and PasswordMessage messages. (We
+	 * used to have a hard rule that protocol messages must be parsable
+	 * without relying on the length word, but we hardly care about protocol
+	 * version or older anymore.)
+	 *
+	 * FIXME: the FE/BE docs need to updated.
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SASL authentication is not supported in protocol version 2")));
+
+	/* Get role info from pg_authid */
+	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(port->user_name));
+	if (!HeapTupleIsValid(roleTup))
+		return STATUS_ERROR;
+
+	/* lookup verifier */
+	verifier = get_role_verifier(HeapTupleGetOid(roleTup), AUTH_VERIFIER_SCRAM);
+	if (verifier == NULL)
+	{
+		ReleaseSysCache(roleTup);
+		return STATUS_ERROR;
+	}
+
+	ReleaseSysCache(roleTup);
+
+	sendAuthRequest(port, AUTH_REQ_SASL, "SCRAM-SHA-1", strlen("SCRAM-SHA-1") + 1);
+
+	scram_opaq = scram_init(port->user_name, verifier);
+
+	/*
+	 * Loop through SASL message exchange. This exchange can consist of
+	 * multiple messags sent in both directions. First message is always from
+	 * the client. All messages from client to server are password packets
+	 * (type 'p').
+	 */
+	do
+	{
+		pq_startmsgread();
+		mtype = pq_getbyte();
+		if (mtype != 'p')
+		{
+			/* Only log error if client didn't disconnect. */
+			if (mtype != EOF)
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("expected SASL response, got message type %d",
+								mtype)));
+			return STATUS_ERROR;
+		}
+
+		/* Get the actual SASL token */
+		initStringInfo(&buf);
+		if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
+		{
+			/* EOF - pq_getmessage already logged error */
+			pfree(buf.data);
+			return STATUS_ERROR;
+		}
+
+		elog(DEBUG4, "Processing received SASL token of length %d", buf.len);
+
+		result = scram_exchange(scram_opaq, buf.data, buf.len,
+								&output, &outputlen);
+
+		/* input buffer no longer used */
+		pfree(buf.data);
+
+		if (outputlen > 0)
+		{
+			/*
+			 * Negotiation generated data to be sent to the client.
+			 */
+			elog(DEBUG4, "sending SASL response token of length %u", outputlen);
+
+			sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+		}
+	} while (result == SASL_EXCHANGE_CONTINUE);
+
+	return (result == SASL_EXCHANGE_SUCCESS) ? STATUS_OK : STATUS_ERROR;
+}
 
 
 /*----------------------------------------------------------------
@@ -892,8 +1007,8 @@ pg_GSS_recvauth(Port *port)
 				 (unsigned int) port->gss->outbuf.length);
 
 			/*
-			 * Add the authentication data for the next step of the GSSAPI or
-			 * SSPI negotiation.
+			 * Add the authentication data for the next step of the GSSAPI
+			 * negotiation.
 			 */
 			elog(DEBUG4, "sending GSS token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
@@ -1143,8 +1258,8 @@ pg_SSPI_recvauth(Port *port)
 			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
 
 			/*
-			 * Add the authentication data for the next step of the GSSAPI or
-			 * SSPI negotiation.
+			 * Add the authentication data for the next step of the SSPI
+			 * negotiation.
 			 */
 			elog(DEBUG4, "sending GSS token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index f04d17a..4140dd8 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -33,10 +33,10 @@
 #include "utils/timestamp.h"
 
 /*
- * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * Get verifier stored in pg_auth_verifiers, for given authentication
  * method.
  */
-static char *
+char *
 get_role_verifier(Oid roleid, const char method)
 {
 	HeapTuple	tuple;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 23c8b5d..646898b 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,20 @@ parse_hba_line(List *line, int line_num, char *raw_line)
 		}
 		parsedline->auth_method = uaMD5;
 	}
+	else if (strcmp(token->string, "scram") == 0)
+	{
+		/* FIXME: could we support Db_user_namespace with SCRAM? */
+		if (Db_user_namespace)
+		{
+			ereport(LOG,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("SCRAM authentication is not supported when \"db_user_namespace\" is enabled"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			return NULL;
+		}
+		parsedline->auth_method = uaSASL;
+	}
 	else if (strcmp(token->string, "pam") == 0)
 #ifdef USE_PAM
 		parsedline->auth_method = uaPAM;
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index 86a89ed..dc3ce2f 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,7 +42,7 @@
 # or "samenet" to match any address in any subnet that the server is
 # directly connected to.
 #
-# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
+# METHOD can be "trust", "reject", "md5", "password", "scram", "gss", "sspi",
 # "ident", "peer", "pam", "ldap", "radius" or "cert".  Note that
 # "password" sends passwords in clear text; "md5" is preferred since
 # it sends encrypted passwords.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5b080c2..37997d9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -940,6 +940,8 @@ AuthVerifierSpec:
 						type = AUTH_VERIFIER_MD5;
 					else if (strcmp($1, "plain") == 0)
 						type = AUTH_VERIFIER_PLAIN;
+					else if (strcmp($1, "scram") == 0)
+						type = AUTH_VERIFIER_SCRAM;
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
@@ -969,6 +971,8 @@ AlterOptRoleElem:
 							veriftype = AUTH_VERIFIER_MD5;
 						else if (strcmp(meth_name, "plain") == 0)
 							veriftype = AUTH_VERIFIER_PLAIN;
+						else if (strcmp(meth_name, "scram") == 0)
+							veriftype = AUTH_VERIFIER_SCRAM;
 						else
 							Assert(false);	/* should not happen */
 						n = (AuthVerifierSpec *)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index c2d5b0e..0d5ee09 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2283,6 +2283,7 @@ ConnCreate(int serverFd)
 	 * all backends would end up using the same salt...
 	 */
 	RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+	RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
 
 	/*
 	 * Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index 4b32b6c..c414e86 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -214,7 +214,7 @@ static const int8 b64lookup[128] = {
 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
 };
 
-static unsigned
+unsigned
 b64_encode(const char *src, unsigned len, char *dst)
 {
 	char	   *p,
@@ -261,7 +261,7 @@ b64_encode(const char *src, unsigned len, char *dst)
 	return p - dst;
 }
 
-static unsigned
+unsigned
 b64_decode(const char *src, unsigned len, char *dst)
 {
 	const char *srcend = src + len,
@@ -331,14 +331,14 @@ b64_decode(const char *src, unsigned len, char *dst)
 }
 
 
-static unsigned
+unsigned
 b64_enc_len(const char *src, unsigned srclen)
 {
 	/* 3 bytes will be converted to 4, linefeed after 76 chars */
 	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
 }
 
-static unsigned
+unsigned
 b64_dec_len(const char *src, unsigned srclen)
 {
 	return (srclen * 3) >> 2;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 082108e..c45ca09 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10142,7 +10142,8 @@ check_password_encryption(char **newval, void **extra, GucSource source)
 		char	   *encryption_name = (char *) lfirst(l);
 
 		if (strcmp(encryption_name, "md5") != 0 &&
-			strcmp(encryption_name, "plain") != 0)
+			strcmp(encryption_name, "plain") != 0 &&
+			strcmp(encryption_name, "scram") != 0)
 		{
 			pfree(rawstring);
 			list_free(elemlist);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index b4d09e5..f5c0b1a 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -932,6 +932,8 @@ dumpRoles(PGconn *conn)
 				appendPQExpBufferStr(buf, "md5 = ");
 			else if (verifier_meth == 'p')
 				appendPQExpBufferStr(buf, "plain = ");
+			else if (verifier_meth == 's')
+				appendPQExpBufferStr(buf, "scram = ");
 			appendStringLiteralConn(buf, verifier_value, conn);
 		}
 		if (current_user != NULL)
diff --git a/src/common/Makefile b/src/common/Makefile
index d6c2a57..8d3a6a4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = exec.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o sha1.o string.o username.o wait_error.o
+	rmtree.o scram-common.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..269a272
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,161 @@
+/*-------------------------------------------------------------------------
+ * scram-common.c
+ *		Shared frontend/backend code for SCRAM authentication
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the Salted Challenge Response Authentication
+ * Mechansim (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-1.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+	uint8		k_ipad[SHA1_HMAC_B];
+	int			i;
+	uint8		keybuf[SHA1_RESULTLEN];
+
+	/*
+	 * If the key is longer than the block size (64 bytes for SHA-1),
+	 * pass it through SHA-1 once to shrink it down
+	 */
+	if (keylen > SHA1_HMAC_B)
+	{
+		SHA1_CTX	sha1_ctx;
+
+		SHA1Init(&sha1_ctx);
+		SHA1Update(&sha1_ctx, key, keylen);
+		SHA1Final(keybuf, &sha1_ctx);
+		key = keybuf;
+		keylen = SHA1_RESULTLEN;
+	}
+
+	memset(k_ipad, 0x36, SHA1_HMAC_B);
+	memset(ctx->k_opad, 0x5C, SHA1_HMAC_B);
+	for (i = 0; i < keylen; i++)
+	{
+		k_ipad[i] ^= key[i];
+		ctx->k_opad[i] ^= key[i];
+	}
+
+	/* tmp = H(K XOR ipad, text) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, k_ipad, SHA1_HMAC_B);
+}
+
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+	SHA1Update(&ctx->sha1ctx, (const uint8 *) str, slen);
+}
+
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+	uint8		h[SHA1_RESULTLEN];
+
+	SHA1Final(h, &ctx->sha1ctx);
+
+	/* H(K XOR opad, tmp) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, ctx->k_opad, SHA1_HMAC_B);
+	SHA1Update(&ctx->sha1ctx, h, SHA1_RESULTLEN);
+	SHA1Final(result, &ctx->sha1ctx);
+}
+
+static void
+scram_Hi(const char *str, const char *salt, int saltlen, int iterations, uint8 *result)
+{
+	int			str_len = strlen(str);
+	uint32		one = htonl(1);
+	int			i, j;
+	uint8		Ui[SCRAM_KEY_LEN];
+	uint8		Ui_prev[SCRAM_KEY_LEN];
+	scram_HMAC_ctx hmac_ctx;
+
+	/* First iteration */
+	scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+	scram_HMAC_update(&hmac_ctx, salt, saltlen);
+	scram_HMAC_update(&hmac_ctx, (char *) &one, sizeof(uint32));
+	scram_HMAC_final(Ui_prev, &hmac_ctx);
+	memcpy(result, Ui_prev, SCRAM_KEY_LEN);
+
+	/* Subsequent iterations */
+	for (i = 2; i <= iterations; i++)
+	{
+		scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+		scram_HMAC_update(&hmac_ctx, (const char *) Ui_prev, SCRAM_KEY_LEN);
+		scram_HMAC_final(Ui, &hmac_ctx);
+		for (j = 0; j < SCRAM_KEY_LEN; j++)
+			result[j] ^= Ui[j];
+		memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
+	}
+}
+
+
+/*
+ * Calculate SHA-1 hash for a NULL-terminated string. (The NULL terminator is
+ * not included in the hash).
+ */
+void
+scram_H(const uint8 *input, int len, uint8 *result)
+{
+	SHA1_CTX	ctx;
+
+	SHA1Init(&ctx);
+	SHA1Update(&ctx, input, len);
+	SHA1Final(result, &ctx);
+}
+
+static void
+scram_Normalize(const char *password, char *result)
+{
+	/* TODO: need to apply SASLprep, see RFC5802 */
+	strlcpy(result, password, 20);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+					 uint8 *result)
+{
+	/* FIXME: pwbuf needs to be sized properly */
+	char		pwbuf[20];
+
+	scram_Normalize(password, pwbuf);
+	scram_Hi(pwbuf, salt, saltlen, iterations, result);
+}
+
+/*
+ * Calculate ClientKey or ServerKey.
+ */
+void
+scram_ClientOrServerKey(const char *password,
+						const char *salt, int saltlen, int iterations,
+						const char *keystr, uint8 *result)
+{
+	uint8		keybuf[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+
+	scram_SaltedPassword(password, salt, saltlen, iterations, keybuf);
+	scram_HMAC_init(&ctx, keybuf, 20);
+	scram_HMAC_update(&ctx, keystr, strlen(keystr));
+	scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
index daef049..5b72e40 100644
--- a/src/include/catalog/pg_auth_verifiers.h
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -58,5 +58,6 @@ typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
 
 #define AUTH_VERIFIER_PLAIN	'p'		/* plain verifier */
 #define AUTH_VERIFIER_MD5	'm'		/* md5 verifier */
+#define AUTH_VERIFIER_SCRAM	's'		/* SCRAM verifier */
 
 #endif   /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..6076c53
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,40 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ *		Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/relpath.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCRAM_COMMON_H
+#define SCRAM_COMMON_H
+
+#include "common/sha1.h"
+
+#define SCRAM_KEY_LEN	SHA1_RESULTLEN
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN	10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN	10
+
+#define SHA1_HMAC_B		64
+
+typedef struct
+{
+	SHA1_CTX	sha1ctx;
+	uint8		k_opad[SHA1_HMAC_B];
+} scram_HMAC_ctx;
+
+extern void scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen);
+extern void scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen);
+extern void scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx);
+
+extern void scram_H(const uint8 *str, int len, uint8 *result);
+extern void scram_ClientOrServerKey(const char *password, const char *salt, int saltlen, int iterations, const char *keystr, uint8 *result);
+
+#endif
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 80f26a8..4469565 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -22,6 +22,11 @@ extern char *pg_krb_realm;
 
 extern void ClientAuthentication(Port *port);
 
+/* Return codes for SASL authentication functions */
+#define	SASL_EXCHANGE_CONTINUE		0
+#define	SASL_EXCHANGE_SUCCESS		1
+#define	SASL_EXCHANGE_FAILURE		2
+
 /* Hook for plugins to get control in ClientAuthentication() */
 typedef void (*ClientAuthentication_hook_type) (Port *, int);
 extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index dfab8f3..1dcf955 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,7 @@
 
 #include "libpq/libpq-be.h"
 
+extern char *get_role_verifier(Oid roleid, char method);
 extern int md5_crypt_verify(const Port *port, const char *role,
 				 char *client_pass, char **logdetail);
 
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 68a953a..a73d2f9 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -24,6 +24,7 @@ typedef enum UserAuth
 	uaIdent,
 	uaPassword,
 	uaMD5,
+	uaSASL,
 	uaGSS,
 	uaSSPI,
 	uaPAM,
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index caaa8b5..80fd226 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,8 @@ typedef struct Port
 	 * Information that needs to be held during the authentication cycle.
 	 */
 	HbaLine    *hba;
-	char		md5Salt[4];		/* Password salt */
+	char		md5Salt[4];		/* MD5 password salt */
+	char		SASLSalt[10];	/* SASL password salt */
 
 	/*
 	 * Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 639bf72..462a5dd 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -172,6 +172,8 @@ 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 */
 
 typedef uint32 AuthRequest;
 
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
new file mode 100644
index 0000000..f273392
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ *	  Interface to libpq/scram.c
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/scram.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SCRAM_H
+#define PG_SCRAM_H
+
+extern void *scram_init(const char *username, const char *verifier);
+extern int scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen);
+extern char *scram_build_verifier(char *username, char *password,
+					 int iterations);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fc1679e..295d1c6 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -157,6 +157,10 @@ extern Datum binary_encode(PG_FUNCTION_ARGS);
 extern Datum binary_decode(PG_FUNCTION_ARGS);
 extern unsigned hex_encode(const char *src, unsigned len, char *dst);
 extern unsigned hex_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_encode(const char *src, unsigned len, char *dst);
+extern unsigned b64_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_enc_len(const char *src, unsigned srclen);
+extern unsigned b64_dec_len(const char *src, unsigned srclen);
 
 /* enum.c */
 extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..f52dcc2 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -9,6 +9,7 @@
 /open.c
 /pgstrcasecmp.c
 /pqsignal.c
+/scram-common.c
 /snprintf.c
 /strerror.c
 /strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c2105f1..890b931 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,7 @@ LIBS := $(LIBS:-lpgport=)
 
 # We can't use Makefile variables here because the MSVC build system scrapes
 # OBJS from this file.
-OBJS=	fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+OBJS=	fe-auth.o fe-auth-scram.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
 	fe-protocol2.o fe-protocol3.o pqexpbuffer.o fe-secure.o \
 	libpq-events.o
 # libpgport C files we always use
@@ -43,6 +43,8 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
 OBJS += ip.o md5.o
 # utils/mb
 OBJS += encnames.o wchar.o
+# common/
+OBJS += scram-common.o sha1.o
 
 ifeq ($(with_openssl),yes)
 OBJS += fe-secure-openssl.o
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
 encnames.c wchar.c: % : $(backend_src)/utils/mb/%
 	rm -f $@ && $(LN_S) $< .
 
+scram-common.c sha1.c: % : $(top_srcdir)/src/common/%
+	rm -f $@ && $(LN_S) $< .
+
 
 distprep: libpq-dist.rc
 
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
new file mode 100644
index 0000000..4b35a77
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,524 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ *	   The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		NONCE_SENT,
+		PROOF_SENT,
+		FINISHED
+	} state;
+
+	const char *username;
+	const char *password;
+
+	char	   *client_first_message_bare;
+	char	   *client_final_message_without_proof;
+
+	/* These come from the server-first message */
+	char	   *server_first_message;
+	char	   *salt;
+	int			saltlen;
+	int			iterations;
+	char	   *server_nonce;
+
+	/* These come from the server-final message */
+	char	   *server_final_message;
+	char		ServerProof[SCRAM_KEY_LEN];
+} fe_scram_state;
+
+static bool read_server_first_message(fe_scram_state *state,
+									  char *input,
+									  PQExpBuffer errormessage);
+static bool read_server_final_message(fe_scram_state *state,
+									  char *input,
+									  PQExpBuffer errormessage);
+static char *build_client_first_message(fe_scram_state *state);
+static char *build_client_final_message(fe_scram_state *state);
+static bool verify_server_proof(fe_scram_state *state);
+static void generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state,
+					const char *client_final_message_without_proof,
+					uint8 *result);
+static unsigned b64_decode(const char *src, unsigned len, char *dst);
+static unsigned b64_encode(const char *src, unsigned len, char *dst);
+static unsigned b64_enc_len(const char *src, unsigned srclen);
+static unsigned b64_dec_len(const char *src, unsigned srclen);
+
+void *
+pg_fe_scram_init(const char *username, const char *password)
+{
+	fe_scram_state *state;
+
+	state = (fe_scram_state *) malloc(sizeof(fe_scram_state));
+	if (!state)
+		return NULL;
+	memset(state, 0, sizeof(fe_scram_state));
+	state->state = INIT;
+	state->username = username;
+	state->password = password;
+
+	return state;
+}
+
+void
+pg_fe_scram_free(void *opaq)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	if (state->client_first_message_bare)
+		free(state->client_first_message_bare);
+	if (state->client_final_message_without_proof)
+		free(state->client_final_message_without_proof);
+
+	if (state->server_first_message)
+		free(state->server_first_message);
+	if (state->salt)
+		free(state->salt);
+	if (state->server_nonce)
+		free(state->server_nonce);
+
+	free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+void
+pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	*done = false;
+	*success = false;
+	*output = NULL;
+	*outputlen = 0;
+
+	switch (state->state)
+	{
+		case INIT:
+			/* send client nonce */
+			*output = build_client_first_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = NONCE_SENT;
+			break;
+
+		case NONCE_SENT:
+			/* receive salt and server nonce, send response */
+			read_server_first_message(state, input, errorMessage);
+			*output = build_client_final_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = PROOF_SENT;
+			break;
+
+		case PROOF_SENT:
+			/* receive server proof, and verify it */
+			read_server_final_message(state, input, errorMessage);
+			*success = verify_server_proof(state);
+			*done = true;
+			state->state = FINISHED;
+			break;
+
+		default:
+			/* shouldn't happen */
+			*done = true;
+			*success = false;
+			printfPQExpBuffer(errorMessage, "invalid SCRAM exchange state");
+	}
+}
+
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+	char		nonce[SCRAM_NONCE_LEN + 1];
+	char	   *buf;
+	char		msglen;
+
+	generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+	msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+
+	buf = malloc(msglen + 1);
+	snprintf(buf, msglen + 1, "n,,n=%s,r=%s", state->username, nonce);
+
+	state->client_first_message_bare = strdup(buf + 3);
+	if (!state->client_first_message_bare)
+		return NULL;
+
+	return buf;
+}
+
+static bool
+read_server_first_message(fe_scram_state *state,
+						  char *input,
+						  PQExpBuffer errormessage)
+{
+	char	   *iterations_str;
+	char	   *endptr;
+	char	   *encoded_salt;
+
+	state->server_first_message = strdup(input);
+	if (!state->server_first_message)
+		return false;
+
+	/* parse the message */
+	state->server_nonce = strdup(read_attr_value(&input, 'r', errormessage));
+	if (state->server_nonce == NULL)
+		return false;
+
+	encoded_salt = read_attr_value(&input, 's', errormessage);
+	if (encoded_salt == NULL)
+		return false;
+	state->salt = malloc(b64_dec_len(encoded_salt, strlen(encoded_salt)));
+	if (state->salt == NULL)
+		return false;
+	state->saltlen = b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+	if (state->saltlen != SCRAM_SALT_LEN)
+		return false;
+
+	iterations_str = read_attr_value(&input, 'i', errormessage);
+	if (iterations_str == NULL)
+		return false;
+	state->iterations = strtol(iterations_str, &endptr, 10);
+	if (*endptr != '\0')
+		return false;
+
+	if (*input != '\0')
+		return false;
+
+	return true;
+}
+
+static bool
+read_server_final_message(fe_scram_state *state,
+						  char *input,
+						  PQExpBuffer errormessage)
+{
+	char	   *encoded_server_proof;
+	int			server_proof_len;
+
+	state->server_final_message = strdup(input);
+	if (!state->server_final_message)
+		return false;
+
+	/* parse the message */
+	encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+	if (encoded_server_proof == NULL)
+		return false;
+
+	server_proof_len = b64_decode(encoded_server_proof,
+								  strlen(encoded_server_proof),
+								  state->ServerProof);
+	if (server_proof_len != SCRAM_KEY_LEN)
+	{
+		printfPQExpBuffer(errormessage, "invalid ServerProof");
+		return false;
+	}
+
+	if (*input != '\0')
+		return false;
+
+	return true;
+}
+
+static char *
+build_client_final_message(fe_scram_state *state)
+{
+	char		client_final_message_without_proof[200];
+	uint8		client_proof[SCRAM_KEY_LEN];
+	char		client_proof_base64[SCRAM_KEY_LEN * 2 + 1];
+	int			client_proof_len;
+	char		buf[300];
+
+	snprintf(client_final_message_without_proof, sizeof(client_final_message_without_proof),
+			 "c=biws,r=%s", state->server_nonce);
+
+	calculate_client_proof(state,
+						   client_final_message_without_proof,
+						   client_proof);
+	if (b64_enc_len((char *) client_proof, SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+		return NULL;
+
+	client_proof_len = b64_encode((char *) client_proof, SCRAM_KEY_LEN, client_proof_base64);
+	client_proof_base64[client_proof_len] = '\0';
+
+	state->client_final_message_without_proof =
+		strdup(client_final_message_without_proof);
+	snprintf(buf, sizeof(buf), "%s,p=%s",
+			 client_final_message_without_proof,
+			 client_proof_base64);
+
+	return strdup(buf);
+}
+
+static void
+calculate_client_proof(fe_scram_state *state,
+					   const char *client_final_message_without_proof,
+					   uint8 *result)
+{
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	int			i;
+	scram_HMAC_ctx ctx;
+
+	scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+							state->iterations, "Client Key", ClientKey);
+	scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+	scram_HMAC_init(&ctx, StoredKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  client_final_message_without_proof,
+					  strlen(client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+	uint8		ServerSignature[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+
+	scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+							state->iterations, "Server Key", ServerKey);
+
+	/* calculate ServerSignature */
+	scram_HMAC_init(&ctx, ServerKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ServerSignature, &ctx);
+
+	if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+		return false;
+
+	return true;
+}
+
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+	int			i;
+
+	for (i = 0; i < len; i++)
+		buf[i] = random() % 255 + 1;
+
+	buf[len] = '\0';
+}
+
+
+/*
+ * BASE64
+ */
+
+static const char _base64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static const int8 b64lookup[128] = {
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+};
+
+static unsigned
+b64_encode(const char *src, unsigned len, char *dst)
+{
+	char	   *p,
+			   *lend = dst + 76;
+	const char *s,
+			   *end = src + len;
+	int			pos = 2;
+	uint32		buf = 0;
+
+	s = src;
+	p = dst;
+
+	while (s < end)
+	{
+		buf |= (unsigned char) *s << (pos << 3);
+		pos--;
+		s++;
+
+		/* write it out */
+		if (pos < 0)
+		{
+			*p++ = _base64[(buf >> 18) & 0x3f];
+			*p++ = _base64[(buf >> 12) & 0x3f];
+			*p++ = _base64[(buf >> 6) & 0x3f];
+			*p++ = _base64[buf & 0x3f];
+
+			pos = 2;
+			buf = 0;
+		}
+		if (p >= lend)
+		{
+			*p++ = '\n';
+			lend = p + 76;
+		}
+	}
+	if (pos != 2)
+	{
+		*p++ = _base64[(buf >> 18) & 0x3f];
+		*p++ = _base64[(buf >> 12) & 0x3f];
+		*p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+		*p++ = '=';
+	}
+
+	return p - dst;
+}
+
+static unsigned
+b64_decode(const char *src, unsigned len, char *dst)
+{
+	const char *srcend = src + len,
+			   *s = src;
+	char	   *p = dst;
+	char		c;
+	int			b = 0;
+	uint32		buf = 0;
+	int			pos = 0,
+				end = 0;
+
+	while (s < srcend)
+	{
+		c = *s++;
+
+		if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+			continue;
+
+		if (c == '=')
+		{
+			/* end sequence */
+			if (!end)
+			{
+				if (pos == 2)
+					end = 1;
+				else if (pos == 3)
+					end = 2;
+				else
+					return 0;
+			}
+			b = 0;
+		}
+		else
+		{
+			b = -1;
+			if (c > 0 && c < 127)
+				b = b64lookup[(unsigned char) c];
+			if (b < 0)
+				return 0;
+		}
+		/* add it to buffer */
+		buf = (buf << 6) + b;
+		pos++;
+		if (pos == 4)
+		{
+			*p++ = (buf >> 16) & 255;
+			if (end == 0 || end > 1)
+				*p++ = (buf >> 8) & 255;
+			if (end == 0 || end > 2)
+				*p++ = buf & 255;
+			buf = 0;
+			pos = 0;
+		}
+	}
+
+	if (pos != 0)
+		return 0;
+
+	return p - dst;
+}
+
+
+static unsigned
+b64_enc_len(const char *src, unsigned srclen)
+{
+	/* 3 bytes will be converted to 4, linefeed after 76 chars */
+	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
+}
+
+static unsigned
+b64_dec_len(const char *src, unsigned srclen)
+{
+	return (srclen * 3) >> 2;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 5891c75..6e3c921 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -428,6 +428,74 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
 }
 #endif   /* ENABLE_SSPI */
 
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+	/*
+	 * Check the authentication mechanism (only SCRAM-SHA-1 is supported at
+	 * the moment.)
+	 */
+	if (strcmp(conn->auth_req_inbuf, "SCRAM-SHA-1") == 0)
+	{
+		conn->password_needed = true;
+		if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  PQnoPasswordSupplied);
+			return STATUS_ERROR;
+		}
+		conn->sasl_state = pg_fe_scram_init(conn->pguser, conn->pgpass);
+		if (!conn->sasl_state)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("out of memory\n"));
+			return STATUS_ERROR;
+		}
+		else
+			return STATUS_OK;
+	}
+	else
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("SASL authentication mechanism %s not supported\n"),
+						  (char *) conn->auth_req_inbuf);
+		return STATUS_ERROR;
+	}
+}
+
+static int
+pg_SASL_exchange(PGconn *conn)
+{
+	char	   *output;
+	int			outputlen;
+	bool		done;
+	bool		success;
+	int			res;
+
+	pg_fe_scram_exchange(conn->sasl_state,
+						 conn->auth_req_inbuf, conn->auth_req_inlen,
+						 &output, &outputlen,
+						 &done, &success, &conn->errorMessage);
+	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);
+
+		if (res != STATUS_OK)
+			return STATUS_ERROR;
+	}
+
+	if (done && !success)
+		return STATUS_ERROR;
+
+	return STATUS_OK;
+}
+
 /*
  * Respond to AUTH_REQ_SCM_CREDS challenge.
  *
@@ -696,6 +764,33 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
 			}
 			break;
 
+		case AUTH_REQ_SASL:
+			/*
+			 * The request contains the name (as assigned by IANA) of the
+			 * authentication mechanism.
+			 */
+			if (pg_SASL_init(conn, conn->auth_req_inbuf) != STATUS_OK)
+			{
+				/* pg_SASL_init already set the error message */
+				return STATUS_ERROR;
+			}
+			/* fall through */
+
+		case AUTH_REQ_SASL_CONT:
+			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) != STATUS_OK)
+			{
+				printfPQExpBuffer(&conn->errorMessage,
+					 "fe_sendauth: error sending password authentication\n");
+				return STATUS_ERROR;
+			}
+			break;
+
 		case AUTH_REQ_SCM_CREDS:
 			if (pg_local_sendauth(conn) != STATUS_OK)
 				return STATUS_ERROR;
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 8d35767..b1b0294 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -18,7 +18,15 @@
 #include "libpq-int.h"
 
 
+/* Prototypes for functions in fe-auth.c */
 extern int	pg_fe_sendauth(AuthRequest areq, PGconn *conn);
 extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 
+/* Prototypes for functions in fe-auth-scram.c */
+extern void *pg_fe_scram_init(const char *username, const char *password);
+extern void pg_fe_scram_free(void *opaq);
+extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage);
+
 #endif   /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a45f4cb..d69ec96 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2479,6 +2479,48 @@ keep_going:						/* We will come back to here until there is
 					}
 				}
 #endif
+				/* Get additional payload for SASL, if any */
+				if (msgLength > 4 &&
+					(areq == AUTH_REQ_SASL ||
+					 areq == AUTH_REQ_SASL_CONT))
+				{
+					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. */
+						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
@@ -3035,6 +3077,15 @@ closePGconn(PGconn *conn)
 		conn->sspictx = NULL;
 	}
 #endif
+	if (conn->sasl_state)
+	{
+		/*
+		 * XXX: if we add support for more authentication mechanisms, this
+		 * needs to call the right 'free' function.
+		 */
+		pg_fe_scram_free(conn->sasl_state);
+		conn->sasl_state = NULL;
+	}
 }
 
 /*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 2175957..391192b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -421,7 +421,12 @@ 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 SSL, GSS, etc */
+	void	   *sasl_state;
 
 #ifdef USE_SSL
 	bool		allow_ssl_try;	/* Allowed to try SSL negotiation */
diff --git a/src/interfaces/libpq/sha1.c b/src/interfaces/libpq/sha1.c
new file mode 120000
index 0000000..cd1c060
--- /dev/null
+++ b/src/interfaces/libpq/sha1.c
@@ -0,0 +1 @@
+../../../src/common/sha1.c
\ No newline at end of file
-- 
2.5.0

#35Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#33)
Re: WIP: SCRAM authentication

On 8/11/15 5:18 PM, Robert Haas wrote:

The thing we're actually debating here is whether enabling SCRAM
authentication for a role should also mean disabling MD5
authentication for that same role, or whether you should be able to
have two password verifiers stored for that role, one for SCRAM and
the other MD5. If we allowed that, then you could turn SCRAM on for a
role, and only later turn MD5 off. I think that's a bad plan because,
in most scenarios, allowing two forms of authentication for the same
account is strictly less secure than allowing only one. And also
because it means adding a bunch of new system catalog machinery and
SQL syntax. Instead, I think that, for any given role, you should get
to pick the way that password authentication works for that role:
either MD5, or SCRAM, but not "whichever of those two the client
prefers".

I understand this idea, but I think it's not practical for many uses.
There is no way to find out, on the server, whether all current clients
would support a switch to SCRAM. Let alone all not-current clients.
The only way to do such a switch would be to do the switch and then
check for connection failures in the log, which is not good.

It would be better if we allowed both methods side by side. Then an
administrator can check in the logs which clients are using an old
method and track those down without interrupting production.

(Now that I think about this, to counter my point, this is very similar
to the switch from crypt to md5. You couldn't turn that on until you
were sure that all clients would support it. I don't remember the
experience from back then, though.)

Also, correct me if I'm wrong, but I believe using SCRAM would also make
it harder to use the password exchange sniffed from the wire. So there
might be a benefit to using SCRAM even if you have to keep old and
supposedly insecure md5 hashes around for a while.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#35)
Re: WIP: SCRAM authentication

On Wed, Aug 12, 2015 at 10:50 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

I understand this idea, but I think it's not practical for many uses.
There is no way to find out, on the server, whether all current clients
would support a switch to SCRAM. Let alone all not-current clients.
The only way to do such a switch would be to do the switch and then
check for connection failures in the log, which is not good.

Well, number one, I don't think we log anything currently that would
let you figure that out; and number two, I'm not sure I believe that's
the only way to make sure you've updated your clients.

It would be better if we allowed both methods side by side. Then an
administrator can check in the logs which clients are using an old
method and track those down without interrupting production.

(Now that I think about this, to counter my point, this is very similar
to the switch from crypt to md5. You couldn't turn that on until you
were sure that all clients would support it. I don't remember the
experience from back then, though.)

Maybe we should try to find out how that played out. It could inform
the current discussion.

Also, correct me if I'm wrong, but I believe using SCRAM would also make
it harder to use the password exchange sniffed from the wire. So there
might be a benefit to using SCRAM even if you have to keep old and
supposedly insecure md5 hashes around for a while.

Yeah. I guess there's the scenario where you use SCRAM with clients
outside the firewall and MD5 with clients inside the firewall. But,
meh. For every person who benefits from the ability to configure
things that way, there will be 3 or 4 or 10 who enable SCRAM and never
get rid of their old password verifiers. That will open up a
vulnerability for people to attack the old verifiers, or perhaps allow
some kind of attack where they can triangulate based on knowing that
the MD5 verifiers and the SCRAM verifier are based on the same
underlying password.

Another thing we might want to try to find out is: if we add SCRAM
authentication to 9.6, how committed are drivers authors to adding
that support to their drivers? If we poll the maintainers of the
drivers for Perl, Python, Ruby, Node.JS, Java, ODBC, etc. and involve
them in this conversation, we might learn useful things. This is a
big change we're talking about, and it's only to work (regardless of
the details) if the driver authors are on board. We haven't, AFAIK,
talked to them about this at all.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#36)
Re: WIP: SCRAM authentication

On 8/12/15 12:19 PM, Robert Haas wrote:

On Wed, Aug 12, 2015 at 10:50 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

I understand this idea, but I think it's not practical for many uses.
There is no way to find out, on the server, whether all current clients
would support a switch to SCRAM. Let alone all not-current clients.
The only way to do such a switch would be to do the switch and then
check for connection failures in the log, which is not good.

Well, number one, I don't think we log anything currently that would
let you figure that out;

We have options to log details about authenticated connections. This
kind of thing could easily be added there.

and number two, I'm not sure I believe that's
the only way to make sure you've updated your clients.

Well, if there is another one, I haven't heard about it so far.

(Now that I think about this, to counter my point, this is very similar
to the switch from crypt to md5. You couldn't turn that on until you
were sure that all clients would support it. I don't remember the
experience from back then, though.)

Maybe we should try to find out how that played out. It could inform
the current discussion.

I think in those days, installations weren't very long-lived. md5 was
introduced in version 7.2, which is also the first version with lazy vacuum.

Another thing we might want to try to find out is: if we add SCRAM
authentication to 9.6, how committed are drivers authors to adding
that support to their drivers? If we poll the maintainers of the
drivers for Perl, Python, Ruby, Node.JS, Java, ODBC, etc. and involve
them in this conversation, we might learn useful things. This is a
big change we're talking about, and it's only to work (regardless of
the details) if the driver authors are on board. We haven't, AFAIK,
talked to them about this at all.

I'm not concerned about whether drivers are willing to support this.
These sort of things have usually worked out in the past. But just
because a driver supports a new authentication method, that doesn't mean
everyone can or will upgrade to it right away.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Stephen Frost
sfrost@snowman.net
In reply to: Peter Eisentraut (#37)
Re: WIP: SCRAM authentication

All,

* Peter Eisentraut (peter_e@gmx.net) wrote:

On 8/12/15 12:19 PM, Robert Haas wrote:

On Wed, Aug 12, 2015 at 10:50 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

I understand this idea, but I think it's not practical for many uses.
There is no way to find out, on the server, whether all current clients
would support a switch to SCRAM. Let alone all not-current clients.
The only way to do such a switch would be to do the switch and then
check for connection failures in the log, which is not good.

Well, number one, I don't think we log anything currently that would
let you figure that out;

We have options to log details about authenticated connections. This
kind of thing could easily be added there.

and number two, I'm not sure I believe that's
the only way to make sure you've updated your clients.

Well, if there is another one, I haven't heard about it so far.

Agreed.

(Now that I think about this, to counter my point, this is very similar
to the switch from crypt to md5. You couldn't turn that on until you
were sure that all clients would support it. I don't remember the
experience from back then, though.)

Maybe we should try to find out how that played out. It could inform
the current discussion.

I think in those days, installations weren't very long-lived. md5 was
introduced in version 7.2, which is also the first version with lazy vacuum.

I don't believe that's any basis for comparison given that it was well
over 10 years ago that we released 7.2.

Another thing we might want to try to find out is: if we add SCRAM
authentication to 9.6, how committed are drivers authors to adding
that support to their drivers? If we poll the maintainers of the
drivers for Perl, Python, Ruby, Node.JS, Java, ODBC, etc. and involve
them in this conversation, we might learn useful things. This is a
big change we're talking about, and it's only to work (regardless of
the details) if the driver authors are on board. We haven't, AFAIK,
talked to them about this at all.

I'm not concerned about whether drivers are willing to support this.
These sort of things have usually worked out in the past. But just
because a driver supports a new authentication method, that doesn't mean
everyone can or will upgrade to it right away.

I certainly want driver authors to support this and would be willing to
work with them to make it happen. Discussing the change with them
certainly makes sense, in general.

As for the notion of dropping md5 from 9.6 or even forcing it to be
one-or-the-other on a per-role basis, I'm surprised we're even
discussing it. We've already got people who aren't upgrading to newer,
supported, versions of PG because the application they have won't work
for some reason, this is creating a much harder upgrade path for those
users, and I don't believe that the security concern holds any water
compared to that issue.

For starters, if the attacker has access to the md5 hash in the DB, the
game is over already, full stop, when it comes to accessing the database
as that user. We won't fix that until everyone's on SCRAM, so we should
be making it as easy as possible to get there. I don't believe having
the SCRAM password verifier in the DB will seriously improve an
attacker's chances of getting the plaintext password simply because our
md5 "salt" completely sucks (no one serious about security would use it)
and md5 simply doesn't stand up to attacks very well these days.

I'm all for having an eventual flag day where we ship a release that
doesn't support md5 on the server side as an authentication mechanism
and pg_upgrade has a check which prevents upgrades if there are any md5
entries left, but that's at least two and probably three releases out.
What we need to be doing now is getting that ball rolling or we're never
going to actually get there.

Thanks!

Stephen

#39Robert Haas
robertmhaas@gmail.com
In reply to: Stephen Frost (#38)
Re: WIP: SCRAM authentication

On Wed, Aug 12, 2015 at 4:09 PM, Stephen Frost <sfrost@snowman.net> wrote:

As for the notion of dropping md5 from 9.6 or even forcing it to be
one-or-the-other on a per-role basis, ...

Please don't conflate those two things. They are radically different
in terms of the amount of upgrade pain that they cause. The first one
would be completely insane.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#39)
Re: WIP: SCRAM authentication

Robert,

* Robert Haas (robertmhaas@gmail.com) wrote:

On Wed, Aug 12, 2015 at 4:09 PM, Stephen Frost <sfrost@snowman.net> wrote:

As for the notion of dropping md5 from 9.6 or even forcing it to be
one-or-the-other on a per-role basis, ...

Please don't conflate those two things. They are radically different
in terms of the amount of upgrade pain that they cause. The first one
would be completely insane.

Thanks for the clarification. I had gotten the (apparently mistaken)
impression[1]CA+TgmoYQ=8BR87xgGkEWs8HJSe3KQh5v4fq+bZ2SAZhAnH3MtQ@mail.gmail.com that there was some consideration for a hard break from
one release to the next to move from md5 to SCRAM.

Would be great to get comments on the other comments, specifically that
adding SCRAM's password verifier won't seriously change the security of
a user's account or password based on an attack vector where the
contents of pg_authid is compromised. I do agree with the general
concern that the additional complexity involved in supporting multiple
password verifiers may result in bugs, and likely security ones, but I
really expect the larger risk to be from the SCRAM implementation itself
than how we get data into and back out of our own catalogs.

Thanks!

Stephen

[1]: CA+TgmoYQ=8BR87xgGkEWs8HJSe3KQh5v4fq+bZ2SAZhAnH3MtQ@mail.gmail.com

#41Josh Berkus
josh@agliodbs.com
In reply to: Josh Berkus (#20)
Re: WIP: SCRAM authentication

On 08/12/2015 01:37 PM, Stephen Frost wrote:

Would be great to get comments on the other comments, specifically that
adding SCRAM's password verifier won't seriously change the security of
a user's account or password based on an attack vector where the
contents of pg_authid is compromised. I do agree with the general
concern that the additional complexity involved in supporting multiple
password verifiers may result in bugs, and likely security ones, but I
really expect the larger risk to be from the SCRAM implementation itself
than how we get data into and back out of our own catalogs.

There's also the concern that the additional complexity will cause
*users* to make security-compromising mistakes, which I think is the
greater risk. Robert has mostly won me over to his point of view on this.

The only case where I can see multiple verifiers per role making a real
difference in migrations is for PGAAS hosting. But the folks from
Heroku and AWS have been notably silent on this; lemme ping them.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42Robert Haas
robertmhaas@gmail.com
In reply to: Stephen Frost (#40)
Re: WIP: SCRAM authentication

On Wed, Aug 12, 2015 at 4:37 PM, Stephen Frost <sfrost@snowman.net> wrote:

Please don't conflate those two things. They are radically different
in terms of the amount of upgrade pain that they cause. The first one
would be completely insane.

Thanks for the clarification. I had gotten the (apparently mistaken)
impression[1] that there was some consideration for a hard break from
one release to the next to move from md5 to SCRAM.

Gosh, no way. Sorry if I gave the contrary impression; that was
certainly not my intention. As I said before, I have no reason to
believe that MD5 is insecure in the way we are using it. Unless much
better attacks are published, I don't really care if people are still
using it 5 years from now, or even 10. I just want to give people an
*option* to move away from it, because:

(1) It's possible that better attacks may be published, and
(2) Even if they aren't, some people don't want to use anything that
says "MD5" on the tin.

Would be great to get comments on the other comments, specifically that
adding SCRAM's password verifier won't seriously change the security of
a user's account or password based on an attack vector where the
contents of pg_authid is compromised. I do agree with the general
concern that the additional complexity involved in supporting multiple
password verifiers may result in bugs, and likely security ones, but I
really expect the larger risk to be from the SCRAM implementation itself
than how we get data into and back out of our own catalogs.

Yes, the SCRAM implementation could be buggy. But also, OpenSSL has
repeatedly had security bugs that were due to forcing servers to
downgrade to older protocols. I wouldn't like us to start growing
similar vulnerabilities, where SCRAM would have been just fine but an
attack that involves forcing a downgrade to MD5 is possible.

I don't think you are quite correct about the scenario where pg_authid
is compromised. Even if the hash stored there is functionally
equivalent to the password itself as far as this instance of
PostgreSQL is concerned, the same password may have been used for
other services, so cracking it has a purpose.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Michael Paquier
michael.paquier@gmail.com
In reply to: Josh Berkus (#41)
Re: WIP: SCRAM authentication

On Thu, Aug 13, 2015 at 6:21 AM, Josh Berkus wrote:

The only case where I can see multiple verifiers per role making a real
difference in migrations is for PGAAS hosting. But the folks from
Heroku and AWS have been notably silent on this; lemme ping them.

Yes, I would be curious to hear from their requirements in this area.
I think it is actually a big deal for those folks to be able to get
multiple verifiers living in parallel for one role.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Stephen Frost
sfrost@snowman.net
In reply to: Josh Berkus (#41)
Re: WIP: SCRAM authentication

* Josh Berkus (josh@agliodbs.com) wrote:

On 08/12/2015 01:37 PM, Stephen Frost wrote:

Would be great to get comments on the other comments, specifically that
adding SCRAM's password verifier won't seriously change the security of
a user's account or password based on an attack vector where the
contents of pg_authid is compromised. I do agree with the general
concern that the additional complexity involved in supporting multiple
password verifiers may result in bugs, and likely security ones, but I
really expect the larger risk to be from the SCRAM implementation itself
than how we get data into and back out of our own catalogs.

There's also the concern that the additional complexity will cause
*users* to make security-compromising mistakes, which I think is the
greater risk. Robert has mostly won me over to his point of view on this.

That is certainly an issue to address- but that's one which I believe we
can address a great deal better than what we're doing with the currently
proposed patch. I do feel we need to provide flexibility and options,
but we also need to consider the simple case and make sure that it
remains simple.

The only case where I can see multiple verifiers per role making a real
difference in migrations is for PGAAS hosting. But the folks from
Heroku and AWS have been notably silent on this; lemme ping them.

While their insight is certainly valuable, they are certainly not the
only cases of one-user-to-rule-them-all environments. Further, there's
going to be cases where multiple applications from different languages
are accessing the database through the same account because there's only
one account.

I'd rather not punt on those cases and, further, assume that we'll
always be able to keep it to only one password verifier per account. As
I tried to outline up-thread, there are a set of features which would be
very nice for us to have which require further information to be saved
beyond even these different password verifiers for each.

As mentioned elsewhere, even SCRAM is possible of having multiple
password verifiers based on the various algorithms used. In other
words, I doubt the 'only one password verifier per role' approach is
going to work out for us long term in any case.

Thanks!

Stephen

#45Michael Paquier
michael.paquier@gmail.com
In reply to: Stephen Frost (#44)
Re: WIP: SCRAM authentication

On Thu, Aug 13, 2015 at 10:22 AM, Stephen Frost wrote:

The only case where I can see multiple verifiers per role making a real
difference in migrations is for PGAAS hosting. But the folks from
Heroku and AWS have been notably silent on this; lemme ping them.

While their insight is certainly valuable, they are certainly not the
only cases of one-user-to-rule-them-all environments. Further, there's
going to be cases where multiple applications from different languages
are accessing the database through the same account because there's only
one account.

I'd rather not punt on those cases and, further, assume that we'll
always be able to keep it to only one password verifier per account. As
I tried to outline up-thread, there are a set of features which would be
very nice for us to have which require further information to be saved
beyond even these different password verifiers for each.

While looking at this stuff, I have been wondering as well about
moving the validutil field into the verifier catalog as well for
example. That's one.

As mentioned elsewhere, even SCRAM is possible of having multiple
password verifiers based on the various algorithms used. In other
words, I doubt the 'only one password verifier per role' approach is
going to work out for us long term in any case.

SCRAM is a definition for an authorization protocol which includes
many verifiers, and the minimal requirement to consider that SCRAM is
implemented in a system is to have SCRAM-SHA1, per here:
http://tools.ietf.org/html/rfc5802
For example we may want to have in parallel one verifier for
SCRAM-SHA1 and one for SCRAM-SHA256 for the same user, and I think
that we cannot close the door either to other SASL protocols, which is
why it may make sense to split the SCRAM patch into two with the basic
message protocol infrastructure in place.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#42)
Re: WIP: SCRAM authentication

Robert,

* Robert Haas (robertmhaas@gmail.com) wrote:

On Wed, Aug 12, 2015 at 4:37 PM, Stephen Frost <sfrost@snowman.net> wrote:

Please don't conflate those two things. They are radically different
in terms of the amount of upgrade pain that they cause. The first one
would be completely insane.

Thanks for the clarification. I had gotten the (apparently mistaken)
impression[1] that there was some consideration for a hard break from
one release to the next to move from md5 to SCRAM.

Gosh, no way. Sorry if I gave the contrary impression; that was
certainly not my intention. As I said before, I have no reason to
believe that MD5 is insecure in the way we are using it. Unless much
better attacks are published, I don't really care if people are still
using it 5 years from now, or even 10. I just want to give people an
*option* to move away from it, because:

No worries, thanks again for the clarification and my apologies for
misunderstanding and jumping to an incorrect assumption.

As for the question about MD5, I do feel that we're using it in about
the best way possible, but I don't feel it's a terribly secure algorithm
today. I'd really like to see people moving away from it sooner rather
than later and that our defaults move to much more secure solutions.

(1) It's possible that better attacks may be published, and
(2) Even if they aren't, some people don't want to use anything that
says "MD5" on the tin.

Would be great to get comments on the other comments, specifically that
adding SCRAM's password verifier won't seriously change the security of
a user's account or password based on an attack vector where the
contents of pg_authid is compromised. I do agree with the general
concern that the additional complexity involved in supporting multiple
password verifiers may result in bugs, and likely security ones, but I
really expect the larger risk to be from the SCRAM implementation itself
than how we get data into and back out of our own catalogs.

Yes, the SCRAM implementation could be buggy. But also, OpenSSL has
repeatedly had security bugs that were due to forcing servers to
downgrade to older protocols. I wouldn't like us to start growing
similar vulnerabilities, where SCRAM would have been just fine but an
attack that involves forcing a downgrade to MD5 is possible.

I agree that such similar vulnerabilities would be unfortunate, but
the way to avoid that is to not implement the actual hashing or
encryption algorithms ourselves and to stick to the protocol as defined
in the specification.

I don't think you are quite correct about the scenario where pg_authid
is compromised. Even if the hash stored there is functionally
equivalent to the password itself as far as this instance of
PostgreSQL is concerned, the same password may have been used for
other services, so cracking it has a purpose.

I attempted to address that also by stating that, should an attacker
compromise a system with the goal of gaining the cleartext password,
they would attempt the following, in order:

1) attempt to compromise a superuser account, if not already done, and
then modify the system to get the 'password' auth mechanism to be used
whereby the password is sent in the clear

2) change the existing password, or encourge the user to do so and
somehow capture that activity

3) social engineering attacks

4) attempt to crack the md5 hash

5) attempt to crack the SCRAM password verifier

6) try to work out a way to use both the md5 hash and the SCRAM password
verifier to figure out the password

In terms of difficulty, while I believe the above is the right overall
ordering, the level of difficulty goes up seriously between 4 and 5,
with 6 requiring a level of expertise in hash algorithms and SCRAM that
I have a very hard time seeing anyone bothering to go there, rather than
just throw more CPUs at the md5 hash.

Thanks!

Stephen

#47Stephen Frost
sfrost@snowman.net
In reply to: Michael Paquier (#45)
Re: WIP: SCRAM authentication

* Michael Paquier (michael.paquier@gmail.com) wrote:

On Thu, Aug 13, 2015 at 10:22 AM, Stephen Frost wrote:

The only case where I can see multiple verifiers per role making a real
difference in migrations is for PGAAS hosting. But the folks from
Heroku and AWS have been notably silent on this; lemme ping them.

While their insight is certainly valuable, they are certainly not the
only cases of one-user-to-rule-them-all environments. Further, there's
going to be cases where multiple applications from different languages
are accessing the database through the same account because there's only
one account.

I'd rather not punt on those cases and, further, assume that we'll
always be able to keep it to only one password verifier per account. As
I tried to outline up-thread, there are a set of features which would be
very nice for us to have which require further information to be saved
beyond even these different password verifiers for each.

While looking at this stuff, I have been wondering as well about
moving the validutil field into the verifier catalog as well for
example. That's one.

Agreed.

As mentioned elsewhere, even SCRAM is possible of having multiple
password verifiers based on the various algorithms used. In other
words, I doubt the 'only one password verifier per role' approach is
going to work out for us long term in any case.

SCRAM is a definition for an authorization protocol which includes
many verifiers, and the minimal requirement to consider that SCRAM is
implemented in a system is to have SCRAM-SHA1, per here:
http://tools.ietf.org/html/rfc5802
For example we may want to have in parallel one verifier for
SCRAM-SHA1 and one for SCRAM-SHA256 for the same user, and I think
that we cannot close the door either to other SASL protocols, which is
why it may make sense to split the SCRAM patch into two with the basic
message protocol infrastructure in place.

and further agreed here. In addition, I'd point out that this is not a
novel area in secure applications- Kerberos/GSSAPI have long been doing
this, with both MIT Kerberos and Heimdal providing multiple algorithms
and supporting multiple password verifiers per user. Active Directory
also, actually. When it comes to enterprise authentication systems and
administrators who work with them, this is not overly complex nor
suprising.

Thanks!

Stephen

#48Robert Haas
robertmhaas@gmail.com
In reply to: Stephen Frost (#46)
Re: WIP: SCRAM authentication

On Wed, Aug 12, 2015 at 9:36 PM, Stephen Frost <sfrost@snowman.net> wrote:

Yes, the SCRAM implementation could be buggy. But also, OpenSSL has
repeatedly had security bugs that were due to forcing servers to
downgrade to older protocols. I wouldn't like us to start growing
similar vulnerabilities, where SCRAM would have been just fine but an
attack that involves forcing a downgrade to MD5 is possible.

I agree that such similar vulnerabilities would be unfortunate, but
the way to avoid that is to not implement the actual hashing or
encryption algorithms ourselves and to stick to the protocol as defined
in the specification.

Nothing in that will protect us if the client can request a non-SCRAM
form of authentication.

I don't think you are quite correct about the scenario where pg_authid
is compromised. Even if the hash stored there is functionally
equivalent to the password itself as far as this instance of
PostgreSQL is concerned, the same password may have been used for
other services, so cracking it has a purpose.

I attempted to address that also by stating that, should an attacker
compromise a system with the goal of gaining the cleartext password,
they would attempt the following, in order:

What if they steal a pg_dump? All of the password verifiers are
there, but the live system is not.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#48)
Re: WIP: SCRAM authentication

* Robert Haas (robertmhaas@gmail.com) wrote:

On Wed, Aug 12, 2015 at 9:36 PM, Stephen Frost <sfrost@snowman.net> wrote:

Yes, the SCRAM implementation could be buggy. But also, OpenSSL has
repeatedly had security bugs that were due to forcing servers to
downgrade to older protocols. I wouldn't like us to start growing
similar vulnerabilities, where SCRAM would have been just fine but an
attack that involves forcing a downgrade to MD5 is possible.

I agree that such similar vulnerabilities would be unfortunate, but
the way to avoid that is to not implement the actual hashing or
encryption algorithms ourselves and to stick to the protocol as defined
in the specification.

Nothing in that will protect us if the client can request a non-SCRAM
form of authentication.

The exact same risk exists for OpenSSL, as you mention, but also for
Kerberos-based authentication systems as well. The way to address those
risks is to not have an md5-based password verifier for the role. If
only one password verifier exists per role then it makes moving off of
md5 more difficult. I am not proposing a solution where both verifiers
exist forever and I specifically proposed that we even remove all MD5
based verifiers in the future, similar to how older encryption
algorithms are no longer supported by modern releases of MIT Kerberos.

I don't think you are quite correct about the scenario where pg_authid
is compromised. Even if the hash stored there is functionally
equivalent to the password itself as far as this instance of
PostgreSQL is concerned, the same password may have been used for
other services, so cracking it has a purpose.

I attempted to address that also by stating that, should an attacker
compromise a system with the goal of gaining the cleartext password,
they would attempt the following, in order:

What if they steal a pg_dump? All of the password verifiers are
there, but the live system is not.

From my previous list, 4, 5, and 6 are all data-at-rest attacks. My
comments below the list also indicated my feeling about the data-at-rest
attacks, but to clarify, I strongly feel that an attacker would go after
the md5 hash far before even considering attacking either the SCRAM
password verifier or trying to use the combination of the two in a novel
way. That isn't to say that there's no way the combination couldn't be
combined or that having both doesn't increase the risk- it certainly
does, but that exposure risk is really that we would then have two
algorithms on which we depend on to not be broken, for the period of
time that both are current.

More generally, however, the way to address these kinds of data-at-rest
attacks and loss of backup data is to have a password aging system where
the passwords are changed on a regular basis. I certainly feel that we
should be looking to add that functionality and that we need to step up
and seriously move forward on implementing these capabilities that other
systems have had for decades or more. We have excellent examples of
what enterprise authentication systems do and the kinds of capabilities
which they provide, both directly from examples such as Active Directory
and PAM, but also from requirements definitions used around the world
(eg: PCI compliance, NIST standards, and similar standards used by other
governments).

Thanks,

Stephen

#50Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#26)
Re: WIP: SCRAM authentication

On 08/12/2015 06:36 PM, Stephen Frost wrote:

I attempted to address that also by stating that, should an attacker
compromise a system with the goal of gaining the cleartext password,
they would attempt the following, in order:

1) attempt to compromise a superuser account, if not already done, and
then modify the system to get the 'password' auth mechanism to be used
whereby the password is sent in the clear

2) change the existing password, or encourge the user to do so and
somehow capture that activity

3) social engineering attacks

4) attempt to crack the md5 hash

5) attempt to crack the SCRAM password verifier

6) try to work out a way to use both the md5 hash and the SCRAM password
verifier to figure out the password

I don't feel like you've correctly assessed the risk inherent in the
md5 auth method, which is that, having captured an md5auth string by
whatever means, and attacker can reuse that md5 string on other
databases in the network *without* cracking it. That's the biggest risk
as long as md5 is present.

Aside from code complexity, the user security concern with a "multiple
verifier per role" approach is that the DBAs would never remember to
completely disable md5auth and would capture md5 hashes either in flight
or from backups. This approach can be used to capture an md5hash from a
non-critical database which is poorly secured, and then re-use it
against an important database.

Now, the counter-argument to this is that a DBA is just as likely to
rememeber to remove md5 verifiers as she is to remember to remove roles
with md5auth.

Regardless of the approach we take, encouraging users to migrate is
going to be more of a matter of documentation, publicity, and
administrative tools than one of multiple verifiers vs. multiple roles.
That is, giving DBAs the ability to see and log who's using what kind
of verifier, and what account has what verifier(s) available, will make
more of a difference.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#34)
6 attachment(s)
Re: WIP: SCRAM authentication

On Wed, Aug 12, 2015 at 1:37 PM, Michael Paquier wrote:

For now, I am attaching a new series of patches, and the SCRAM
authentication is still using the new catalog pg_auth_verifiers.
Switching to a one-verifier-per-role approach or similar does not seem
to be a huge task to me.

Attached is a new series of patches. Compared to the last version
submitted, encoding routines for base64, escape and hex have been
moved to src/common to avoid duplication between frontend and backend.
I have as well fix a couple of issues in the SCRAM implementation and
made some cosmetic changes to make the code easier to read and more
understandable.
Regards,
--
Michael

Attachments:

0001-Add-facility-to-store-multiple-password-formats.patchtext/x-diff; charset=US-ASCII; name=0001-Add-facility-to-store-multiple-password-formats.patchDownload
From 493fb51f55ccb58da86333d47f3323339aeb6624 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 18 Aug 2015 10:49:37 +0900
Subject: [PATCH 1/6] Add facility to store multiple password formats

This commit adds a new cluster-wide catalog table called pg_auth_verifiers
extending the existing one-password value per role approach into a facility
ablt to store multiple passwords formats for one user. This makes easier to
add additional password format support in the future and is a requirement
for the additional of SCRAM-SHA1.

CREATE ROLE and ALTER ROLE are extended with PASSWORD VERIFIERS that allow
a user to set a list of password identifiers at will, something particularly
useful for pg_dump that makes use of it with this commit.

password_encryption is transformed into a list able to use "md5" or "plain",
or even both when CREATE/ALTER ROLE uses neither ENCRYPTED/UNENCRYPTED.

The password check hook has been redesigned to be able to check a list
of passwords instead of a single entry, and the related contrib module
passwordcheck/ is updated respecting the new format.

Regression tests and documentation are added accordingly.
---
 contrib/passwordcheck/passwordcheck.c         | 138 +++++------
 doc/src/sgml/catalogs.sgml                    |  99 ++++++--
 doc/src/sgml/config.sgml                      |  17 +-
 doc/src/sgml/ref/create_role.sgml             |  23 +-
 src/backend/catalog/Makefile                  |   4 +-
 src/backend/catalog/catalog.c                 |   4 +
 src/backend/catalog/system_views.sql          |  11 +-
 src/backend/commands/user.c                   | 315 +++++++++++++++++---------
 src/backend/libpq/crypt.c                     |  72 ++++--
 src/backend/nodes/copyfuncs.c                 |  14 ++
 src/backend/nodes/equalfuncs.c                |  12 +
 src/backend/parser/gram.y                     |  98 +++++++-
 src/backend/utils/cache/catcache.c            |   1 +
 src/backend/utils/cache/relcache.c            |  17 +-
 src/backend/utils/cache/syscache.c            |  23 ++
 src/backend/utils/misc/guc.c                  |  65 ++++--
 src/backend/utils/misc/postgresql.conf.sample |   2 +-
 src/bin/initdb/initdb.c                       |   5 +-
 src/bin/pg_dump/pg_dumpall.c                  |  77 ++++++-
 src/include/catalog/indexing.h                |   5 +
 src/include/catalog/pg_auth_verifiers.h       |  62 +++++
 src/include/catalog/pg_authid.h               |   8 +-
 src/include/commands/user.h                   |  11 +-
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/parsenodes.h                |  11 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/utils/syscache.h                  |   2 +
 src/test/regress/expected/password.out        | 105 +++++++++
 src/test/regress/expected/rules.out           |   9 +-
 src/test/regress/expected/sanity_check.out    |   1 +
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/serial_schedule              |   1 +
 src/test/regress/sql/password.sql             |  72 ++++++
 33 files changed, 1023 insertions(+), 265 deletions(-)
 create mode 100644 src/include/catalog/pg_auth_verifiers.h
 create mode 100644 src/test/regress/expected/password.out
 create mode 100644 src/test/regress/sql/password.sql

diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 78c44b2..5ee38ed 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -20,9 +20,11 @@
 #include <crack.h>
 #endif
 
+#include "catalog/pg_auth_verifiers.h"
 #include "commands/user.h"
 #include "fmgr.h"
 #include "libpq/md5.h"
+#include "nodes/parsenodes.h"
 
 PG_MODULE_MAGIC;
 
@@ -50,87 +52,93 @@ extern void _PG_init(void);
  */
 static void
 check_password(const char *username,
-			   const char *password,
-			   int password_type,
+			   List *passwordVerifiers,
 			   Datum validuntil_time,
 			   bool validuntil_null)
 {
 	int			namelen = strlen(username);
-	int			pwdlen = strlen(password);
+	int			pwdlen;
 	char		encrypted[MD5_PASSWD_LEN + 1];
 	int			i;
 	bool		pwd_has_letter,
 				pwd_has_nonletter;
+	ListCell   *l;
 
-	switch (password_type)
+	foreach(l, passwordVerifiers)
 	{
-		case PASSWORD_TYPE_MD5:
-
-			/*
-			 * Unfortunately we cannot perform exhaustive checks on encrypted
-			 * passwords - we are restricted to guessing. (Alternatively, we
-			 * could insist on the password being presented non-encrypted, but
-			 * that has its own security disadvantages.)
-			 *
-			 * We only check for username = password.
-			 */
-			if (!pg_md5_encrypt(username, username, namelen, encrypted))
-				elog(ERROR, "password encryption failed");
-			if (strcmp(password, encrypted) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password must not contain user name")));
-			break;
-
-		case PASSWORD_TYPE_PLAINTEXT:
-
-			/*
-			 * For unencrypted passwords we can perform better checks
-			 */
-
-			/* enforce minimum length */
-			if (pwdlen < MIN_PWD_LENGTH)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password is too short")));
-
-			/* check if the password contains the username */
-			if (strstr(password, username))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password must not contain user name")));
-
-			/* check if the password contains both letters and non-letters */
-			pwd_has_letter = false;
-			pwd_has_nonletter = false;
-			for (i = 0; i < pwdlen; i++)
-			{
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		switch (spec->veriftype)
+		{
+			case AUTH_VERIFIER_MD5:
+
+				/*
+				 * Unfortunately we cannot perform exhaustive checks on encrypted
+				 * passwords - we are restricted to guessing. (Alternatively, we
+				 * could insist on the password being presented non-encrypted, but
+				 * that has its own security disadvantages.)
+				 *
+				 * We only check for username = password.
+				 */
+				if (!pg_md5_encrypt(username, username, namelen, encrypted))
+					elog(ERROR, "password encryption failed");
+				if (strcmp(spec->value, encrypted) == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password must not contain user name")));
+				break;
+
+			case AUTH_VERIFIER_PLAIN:
+
 				/*
-				 * isalpha() does not work for multibyte encodings but let's
-				 * consider non-ASCII characters non-letters
+				 * For unencrypted passwords we can perform better checks
 				 */
-				if (isalpha((unsigned char) password[i]))
-					pwd_has_letter = true;
-				else
-					pwd_has_nonletter = true;
-			}
-			if (!pwd_has_letter || !pwd_has_nonletter)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				errmsg("password must contain both letters and nonletters")));
+				pwdlen = strlen(spec->value);
+
+				/* enforce minimum length */
+				if (pwdlen < MIN_PWD_LENGTH)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password is too short")));
+
+				/* check if the password contains the username */
+				if (strstr(spec->value, username))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password must not contain user name")));
+
+				/* check if the password contains both letters and non-letters */
+				pwd_has_letter = false;
+				pwd_has_nonletter = false;
+				for (i = 0; i < pwdlen; i++)
+				{
+					/*
+					 * isalpha() does not work for multibyte encodings but let's
+					 * consider non-ASCII characters non-letters
+					 */
+					if (isalpha((unsigned char) spec->value[i]))
+						pwd_has_letter = true;
+					else
+						pwd_has_nonletter = true;
+				}
+				if (!pwd_has_letter || !pwd_has_nonletter)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("password must contain both letters and nonletters")));
 
 #ifdef USE_CRACKLIB
-			/* call cracklib to check password */
-			if (FascistCheck(password, CRACKLIB_DICTPATH))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("password is easily cracked")));
+				/* call cracklib to check password */
+				if (FascistCheck(spec->value, CRACKLIB_DICTPATH))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("password is easily cracked")));
 #endif
-			break;
+				break;
 
-		default:
-			elog(ERROR, "unrecognized password type: %d", password_type);
-			break;
+			default:
+				elog(ERROR, "unrecognized password type: %d", spec->veriftype);
+				break;
+		}
 	}
 
 	/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4ea227..13cc7cb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1360,13 +1360,6 @@
   </para>
 
   <para>
-   Since this catalog contains passwords, it must not be publicly readable.
-   <link linkend="view-pg-roles"><structname>pg_roles</structname></link>
-   is a publicly readable view on
-   <structname>pg_authid</structname> that blanks out the password field.
-  </para>
-
-  <para>
    <xref linkend="user-manag"> contains detailed information about user and
    privilege management.
   </para>
@@ -1469,21 +1462,6 @@
      </row>
 
      <row>
-      <entry><structfield>rolpassword</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>
-       Password (possibly encrypted); null if none.  If the password
-       is encrypted, this column will begin with the string <literal>md5</>
-       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
-       will be of the user's password concatenated to their user name.
-       For example, if user <literal>joe</> has password <literal>xyzzy</>,
-       <productname>PostgreSQL</> will store the md5 hash of
-       <literal>xyzzyjoe</>.  A password that does not follow that
-       format is assumed to be unencrypted.
-      </entry>
-     </row>
-
-     <row>
       <entry><structfield>rolvaliduntil</structfield></entry>
       <entry><type>timestamptz</type></entry>
       <entry>Password expiry time (only used for password authentication);
@@ -1495,6 +1473,77 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-auth-verifiers">
+  <title><structname>pg_auth_verifiers</structname></title>
+
+  <indexterm zone="catalog-pg-auth-verifiers">
+   <primary>pg_auth_verifiers</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_auth_verifiers</structname> contains password
+   information for database authorization identifiers (roles).
+  </para>
+
+  <para>
+   Since this catalog contains passwords, it must not be publicly readable.
+  </para>
+
+  <para>
+   Because user identities are cluster-wide,
+   <structname>pg_auth_verifiers</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_auth_verifiers</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_auth_verifiers</> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>roleid</type></entry>
+      <entry>Role identifier OID</entry>
+     </row>
+
+     <row>
+      <entry><structfield>verimet</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry>
+       <literal>p</> = plain format,
+       <literal>m</> = MD5-encrypted
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>text</structfield></entry>
+      <entry><type>verival</type></entry>
+      <entry>
+       Password (possibly encrypted with format defined in
+       <structfield>verimet</>).  If the password
+       is MD5-encrypted, this column will begin with the string <literal>md5</>
+       followed by a 32-character hexadecimal MD5 hash.  The MD5 hash
+       will be of the user's password concatenated to their user name.
+       For example, if user <literal>joe</> has password <literal>xyzzy</>,
+       <productname>PostgreSQL</> will store the md5 hash of
+       <literal>xyzzyjoe</>.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
 
  <sect1 id="catalog-pg-auth-members">
   <title><structname>pg_auth_members</structname></title>
@@ -9877,9 +9926,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
      </row>
 
      <row>
-      <entry><structfield>passwd</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Not the password (always reads as <literal>********</>)</entry>
+      <entry><structfield>verifiers</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry>List of password verifiers listed as method:password.</entry>
      </row>
 
      <row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e900dcc..5ae27f8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,20 +1163,29 @@ include_dir 'conf.d'
      </varlistentry>
 
      <varlistentry id="guc-password-encryption" xreflabel="password_encryption">
-      <term><varname>password_encryption</varname> (<type>boolean</type>)
+      <term><varname>password_encryption</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>password_encryption</> configuration parameter</primary>
       </indexterm>
       </term>
       <listitem>
        <para>
+        Specifies a comma-separated list of password encryption formats.
+        Supported formats are <literal>plain</> and <literal>md5</>.
+       </para>
+
+       <para>
         When a password is specified in <xref
         linkend="sql-createuser"> or
         <xref linkend="sql-alterrole">
         without writing either <literal>ENCRYPTED</> or
-        <literal>UNENCRYPTED</>, this parameter determines whether the
-        password is to be encrypted. The default is <literal>on</>
-        (encrypt the password).
+        <literal>UNENCRYPTED</>, this parameter determines the list of
+        encryption formats this password is to be stored as.
+       </para>
+
+       <para>
+        The default is <literal>md5</> (encrypt the password with MD5
+        encryption).
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index ea26027..a48d188 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -35,6 +35,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
     | BYPASSRLS | NOBYPASSRLS
     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
     | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+    | PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' [, ...] )
     | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
     | IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
     | IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -228,9 +229,9 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
         roles having the <literal>LOGIN</literal> attribute, but you
         can nonetheless define one for roles without it.)  If you do
         not plan to use password authentication you can omit this
-        option.  If no password is specified, the password will be set
-        to null and password authentication will always fail for that
-        user.  A null password can optionally be written explicitly as
+        option.  If no password is specified, no password will be set
+        and password authentication will always fail for that user.
+        A null password can optionally be written explicitly as
         <literal>PASSWORD NULL</literal>.
        </para>
       </listitem>
@@ -262,6 +263,22 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
      </varlistentry>
 
      <varlistentry>
+      <term>PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>'</term>
+      <listitem>
+       <para>
+        Sets the list of password verifiers for the role. Currently only
+        <literal>md5</>, for MD5-encrypted format, and <literal>plain</>
+        for unencrypted format, can be specified as verifier format type
+        for <literal>verifier_type</>. If the password defined with
+        <literal>plain</> type is already in MD5-encrypted format
+        the password will be stored as MD5-encrypted. If the password defined
+        is in plain-format and that <literal>verifier_type</> is set
+        to <literal>md5</>, the password will be stored as MD5-encrypted.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
       <listitem>
        <para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..2e695b8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,8 +35,8 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
 	pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
 	pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
-	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
-	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
+	pg_auth_verifiers.h pg_authid.h pg_auth_members.h pg_shdepend.h \
+	pg_shdescription.h pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 81ccebf..663e51b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -27,6 +27,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
@@ -218,6 +219,7 @@ IsSharedRelation(Oid relationId)
 {
 	/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
 	if (relationId == AuthIdRelationId ||
+		relationId == AuthVerifRelationId ||
 		relationId == AuthMemRelationId ||
 		relationId == DatabaseRelationId ||
 		relationId == PLTemplateRelationId ||
@@ -231,6 +233,8 @@ IsSharedRelation(Oid relationId)
 	/* These are their indexes (see indexing.h) */
 	if (relationId == AuthIdRolnameIndexId ||
 		relationId == AuthIdOidIndexId ||
+		relationId == AuthVerifRoleMethodIndexId ||
+		relationId == AuthVerifMethodRoleIndexId ||
 		relationId == AuthMemRoleMemIndexId ||
 		relationId == AuthMemMemRoleIndexId ||
 		relationId == DatabaseNameIndexId ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..15001df 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -32,7 +32,16 @@ CREATE VIEW pg_shadow AS
         rolsuper AS usesuper,
         rolreplication AS userepl,
         rolbypassrls AS usebypassrls,
-        rolpassword AS passwd,
+        ARRAY
+        (
+            SELECT
+                CASE verimet
+                    WHEN 'p' THEN 'plain:' || verival
+                    WHEN 'm' THEN 'md5:' || verival
+                END AS verifiers
+            FROM pg_auth_verifiers
+	    WHERE roleid = pg_authid.oid
+	) AS verifiers,
         rolvaliduntil::abstime AS valuntil,
         setconfig AS useconfig
     FROM pg_authid LEFT JOIN pg_db_role_setting s
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index afbf276..c6bf9db 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -21,9 +21,11 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "commands/seclabel.h"
@@ -42,9 +44,6 @@
 Oid			binary_upgrade_next_pg_authid_oid = InvalidOid;
 
 
-/* GUC parameter */
-extern bool Password_encryption;
-
 /* Hook to check passwords in CreateRole() and AlterRole() */
 check_password_hook_type check_password_hook = NULL;
 
@@ -54,6 +53,10 @@ static void AddRoleMems(const char *rolename, Oid roleid,
 static void DelRoleMems(const char *rolename, Oid roleid,
 			List *memberSpecs, List *memberIds,
 			bool admin_opt);
+static void FlattenPasswordIdentifiers(List *verifiers, char *rolname);
+static void InsertPasswordIdentifiers(Oid roleid, List *verifiers,
+			char *rolname);
+static void DeletePasswordVerifiers(Oid roleid);
 
 
 /* Check if current user has createrole privileges */
@@ -78,9 +81,7 @@ CreateRole(CreateRoleStmt *stmt)
 	Oid			roleid;
 	ListCell   *item;
 	ListCell   *option;
-	char	   *password = NULL;	/* user password */
-	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	List	   *passwordVerifiers = NIL;	/* password verifiers */
 	bool		issuper = false;	/* Make the user a superuser? */
 	bool		inherit = true; /* Auto inherit privileges? */
 	bool		createrole = false;		/* Can this user create roles? */
@@ -96,7 +97,7 @@ CreateRole(CreateRoleStmt *stmt)
 	char	   *validUntil = NULL;		/* time the login is valid until */
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
-	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordVerifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -128,19 +129,13 @@ CreateRole(CreateRoleStmt *stmt)
 	{
 		DefElem    *defel = (DefElem *) lfirst(option);
 
-		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+		if (strcmp(defel->defname, "passwordVerifiers") == 0)
 		{
-			if (dpassword)
+			if (dpasswordVerifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
-			dpassword = defel;
-			if (strcmp(defel->defname, "encryptedPassword") == 0)
-				encrypt_password = true;
-			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
-				encrypt_password = false;
+			dpasswordVerifiers = defel;
 		}
 		else if (strcmp(defel->defname, "sysid") == 0)
 		{
@@ -248,8 +243,8 @@ CreateRole(CreateRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
+	if (dpasswordVerifiers && dpasswordVerifiers->arg)
+		passwordVerifiers = (List *) dpasswordVerifiers->arg;
 	if (dissuper)
 		issuper = intVal(dissuper->arg) != 0;
 	if (dinherit)
@@ -340,12 +335,16 @@ CreateRole(CreateRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Flatten list of password verifiers.
+	 */
+	FlattenPasswordIdentifiers(passwordVerifiers, stmt->role);
+
+	/*
+	 * Call the password checking hook if there is one defined.
 	 */
-	if (check_password_hook && password)
+	if (check_password_hook && passwordVerifiers != NIL)
 		(*check_password_hook) (stmt->role,
-								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								passwordVerifiers,
 								validUntil_datum,
 								validUntil_null);
 
@@ -365,24 +364,6 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
 	new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-	}
-	else
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
@@ -411,6 +392,10 @@ CreateRole(CreateRoleStmt *stmt)
 	roleid = simple_heap_insert(pg_authid_rel, tuple);
 	CatalogUpdateIndexes(pg_authid_rel, tuple);
 
+	/* store password verifiers */
+	if (passwordVerifiers)
+		InsertPasswordIdentifiers(roleid, passwordVerifiers, stmt->role);
+
 	/*
 	 * Advance command counter so we can see new record; else tests in
 	 * AddRoleMems may fail.
@@ -479,9 +464,7 @@ AlterRole(AlterRoleStmt *stmt)
 	Form_pg_authid authform;
 	ListCell   *option;
 	char	   *rolename = NULL;
-	char	   *password = NULL;	/* user password */
-	bool		encrypt_password = Password_encryption; /* encrypt password? */
-	char		encrypted_password[MD5_PASSWD_LEN + 1];
+	List	   *passwordVerifiers = NIL;	/* password verifiers */
 	int			issuper = -1;	/* Make the user a superuser? */
 	int			inherit = -1;	/* Auto inherit privileges? */
 	int			createrole = -1;	/* Can this user create roles? */
@@ -494,7 +477,7 @@ AlterRole(AlterRoleStmt *stmt)
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
 	bool		bypassrls = -1;
-	DefElem    *dpassword = NULL;
+	DefElem    *dpasswordVerifiers = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
@@ -512,19 +495,13 @@ AlterRole(AlterRoleStmt *stmt)
 	{
 		DefElem    *defel = (DefElem *) lfirst(option);
 
-		if (strcmp(defel->defname, "password") == 0 ||
-			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+		if (strcmp(defel->defname, "passwordVerifiers") == 0)
 		{
-			if (dpassword)
+			if (dpasswordVerifiers)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("conflicting or redundant options")));
-			dpassword = defel;
-			if (strcmp(defel->defname, "encryptedPassword") == 0)
-				encrypt_password = true;
-			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
-				encrypt_password = false;
+			dpasswordVerifiers = defel;
 		}
 		else if (strcmp(defel->defname, "superuser") == 0)
 		{
@@ -612,8 +589,8 @@ AlterRole(AlterRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
+	if (dpasswordVerifiers && dpasswordVerifiers->arg)
+		passwordVerifiers = (List *) dpasswordVerifiers->arg;
 	if (dissuper)
 		issuper = intVal(dissuper->arg);
 	if (dinherit)
@@ -687,7 +664,7 @@ AlterRole(AlterRoleStmt *stmt)
 			  !dconnlimit &&
 			  !rolemembers &&
 			  !validUntil &&
-			  dpassword &&
+			  dpasswordVerifiers &&
 			  roleid == GetUserId()))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -712,12 +689,16 @@ AlterRole(AlterRoleStmt *stmt)
 	}
 
 	/*
-	 * Call the password checking hook if there is one defined
+	 * Flatten list of password verifiers.
 	 */
-	if (check_password_hook && password)
+	FlattenPasswordIdentifiers(passwordVerifiers, rolename);
+
+	/*
+	 * Call the password checking hook if there is one defined.
+	 */
+	if (check_password_hook && passwordVerifiers)
 		(*check_password_hook) (rolename,
-								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								passwordVerifiers,
 								validUntil_datum,
 								validUntil_null);
 
@@ -773,30 +754,6 @@ AlterRole(AlterRoleStmt *stmt)
 		new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
 	}
 
-	/* password */
-	if (password)
-	{
-		if (!encrypt_password || isMD5(password))
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(password);
-		else
-		{
-			if (!pg_md5_encrypt(password, rolename, strlen(rolename),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
-			new_record[Anum_pg_authid_rolpassword - 1] =
-				CStringGetTextDatum(encrypted_password);
-		}
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
-	/* unset password */
-	if (dpassword && dpassword->arg == NULL)
-	{
-		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
-		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-	}
-
 	/* valid until */
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -821,6 +778,21 @@ AlterRole(AlterRoleStmt *stmt)
 	heap_freetuple(new_tuple);
 
 	/*
+	 * Update password verifiers. The old entries are completely cleaned up
+	 * and are updated by what is wanted through this command.
+	 */
+	if (passwordVerifiers)
+	{
+		DeletePasswordVerifiers(roleid);
+		CommandCounterIncrement();
+		InsertPasswordIdentifiers(roleid, passwordVerifiers, rolename);
+	}
+
+	/* remove password verifiers */
+	if (dpasswordVerifiers && dpasswordVerifiers->arg == NULL)
+		DeletePasswordVerifiers(roleid);
+
+	/*
 	 * Advance command counter so we can see new record; else tests in
 	 * AddRoleMems may fail.
 	 */
@@ -1070,8 +1042,10 @@ DropRole(DropRoleStmt *stmt)
 		systable_endscan(sscan);
 
 		/*
-		 * Remove any comments or security labels on this role.
+		 * Remove any comments, password verifiers or security labels on this
+		 * role.
 		 */
+		DeletePasswordVerifiers(roleid);
 		DeleteSharedComments(roleid, AuthIdRelationId);
 		DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
 
@@ -1106,11 +1080,11 @@ ObjectAddress
 RenameRole(const char *oldname, const char *newname)
 {
 	HeapTuple	oldtuple,
-				newtuple;
+				newtuple,
+				authtuple;
 	TupleDesc	dsc;
-	Relation	rel;
-	Datum		datum;
-	bool		isnull;
+	Relation	pg_authid_rel,
+				pg_auth_verifiers_rel;
 	Datum		repl_val[Natts_pg_authid];
 	bool		repl_null[Natts_pg_authid];
 	bool		repl_repl[Natts_pg_authid];
@@ -1118,8 +1092,10 @@ RenameRole(const char *oldname, const char *newname)
 	Oid			roleid;
 	ObjectAddress address;
 
-	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
-	dsc = RelationGetDescr(rel);
+	pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+
+	dsc = RelationGetDescr(pg_authid_rel);
 
 	oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
 	if (!HeapTupleIsValid(oldtuple))
@@ -1179,22 +1155,10 @@ RenameRole(const char *oldname, const char *newname)
 												   CStringGetDatum(newname));
 	repl_null[Anum_pg_authid_rolname - 1] = false;
 
-	datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
-
-	if (!isnull && isMD5(TextDatumGetCString(datum)))
-	{
-		/* MD5 uses the username as salt, so just clear it on a rename */
-		repl_repl[Anum_pg_authid_rolpassword - 1] = true;
-		repl_null[Anum_pg_authid_rolpassword - 1] = true;
-
-		ereport(NOTICE,
-				(errmsg("MD5 password cleared because of role rename")));
-	}
-
 	newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
-	simple_heap_update(rel, &oldtuple->t_self, newtuple);
+	simple_heap_update(pg_authid_rel, &oldtuple->t_self, newtuple);
 
-	CatalogUpdateIndexes(rel, newtuple);
+	CatalogUpdateIndexes(pg_authid_rel, newtuple);
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
@@ -1202,10 +1166,23 @@ RenameRole(const char *oldname, const char *newname)
 
 	ReleaseSysCache(oldtuple);
 
+	/* look for md5 entry in pg_auth_verifiers and remove it if it exists */
+	authtuple = SearchSysCache2(AUTHVERIFROLEMETH,
+								ObjectIdGetDatum(roleid),
+								CharGetDatum(AUTH_VERIFIER_MD5));
+	if (HeapTupleIsValid(authtuple))
+	{
+		simple_heap_delete(pg_auth_verifiers_rel, &authtuple->t_self);
+		ereport(NOTICE,
+				(errmsg("MD5 password cleared because of role rename")));
+		ReleaseSysCache(authtuple);
+	}
+
 	/*
-	 * Close pg_authid, but keep lock till commit.
+	 * Close pg_authid and pg_auth_verifiers, but keep lock till commit.
 	 */
-	heap_close(rel, NoLock);
+	heap_close(pg_auth_verifiers_rel, NoLock);
+	heap_close(pg_authid_rel, NoLock);
 
 	return address;
 }
@@ -1611,3 +1588,127 @@ DelRoleMems(const char *rolename, Oid roleid,
 	 */
 	heap_close(pg_authmem_rel, NoLock);
 }
+
+/*
+ * FlattenPasswordIdentifiers
+ * Make list of password verifier types and values consistent with input.
+ */
+static void
+FlattenPasswordIdentifiers(List *verifiers, char *rolname)
+{
+	ListCell   *l;
+
+	foreach(l, verifiers)
+	{
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		if (spec->value == NULL)
+			continue;
+
+		/*
+		 * Check if given value for an MD5 verifier is adapted and
+		 * do conversion as needed. If an MD5 password is provided but
+		 * that the verifier has a plain format switch type of verifier
+		 * accordingly.
+		 */
+		if (spec->veriftype == AUTH_VERIFIER_MD5 &&
+			!isMD5(spec->value))
+		{
+			char encrypted_passwd[MD5_PASSWD_LEN + 1];
+			if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
+								encrypted_passwd))
+				elog(ERROR, "password encryption failed");
+			spec->value = pstrdup(encrypted_passwd);
+		}
+		else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
+				 isMD5(spec->value))
+			spec->veriftype = AUTH_VERIFIER_MD5;
+	}
+}
+
+/*
+ * InsertPasswordIdentifiers
+ * Add list of given identifiers into pg_auth_verifiers for given role.
+ */
+static void
+InsertPasswordIdentifiers(Oid roleid, List *verifiers, char *rolname)
+{
+	ListCell   *l;
+	Relation	pg_auth_verifiers_rel;
+	TupleDesc	pg_auth_verifiers_dsc;
+
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+	pg_auth_verifiers_dsc = RelationGetDescr(pg_auth_verifiers_rel);
+
+	foreach(l, verifiers)
+	{
+		Datum		new_record[Natts_pg_auth_verifiers];
+		bool		new_record_nulls[Natts_pg_auth_verifiers];
+		HeapTuple	tuple;
+		AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+		/* Move on if no verifier value define */
+		if (spec->value == NULL)
+			continue;
+
+		/* Build tuple and insert it */
+		MemSet(new_record, 0, sizeof(new_record));
+		MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+		new_record[Anum_pg_auth_verifiers_roleid - 1] = ObjectIdGetDatum(roleid);
+		new_record[Anum_pg_auth_verifiers_method - 1] =
+			CharGetDatum(spec->veriftype);
+
+		new_record[Anum_pg_auth_verifiers_value - 1] =
+			CStringGetTextDatum(spec->value);
+
+		tuple = heap_form_tuple(pg_auth_verifiers_dsc,
+								new_record, new_record_nulls);
+
+		simple_heap_insert(pg_auth_verifiers_rel, tuple);
+		CatalogUpdateIndexes(pg_auth_verifiers_rel, tuple);
+
+		/* CCI after each change */
+		CommandCounterIncrement();
+	}
+
+	/* Keep locks until the end of transaction */
+	heap_close(pg_auth_verifiers_rel, NoLock);
+}
+
+/*
+ * DeletePasswordVerifiers
+ * Remove all password identifiers for given role.
+ */
+static void
+DeletePasswordVerifiers(Oid roleid)
+{
+	Relation	pg_auth_verifiers_rel;
+	ScanKeyData scankey;
+	SysScanDesc	sscan;
+	HeapTuple	tmp_tuple;
+
+	pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+	/*
+	 * Remove role entries from pg_auth_verifiers table. All the tuples that
+	 * are similar to the role dropped need to be removed.
+	 */
+	ScanKeyInit(&scankey,
+				Anum_pg_auth_verifiers_roleid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(roleid));
+
+	sscan = systable_beginscan(pg_auth_verifiers_rel,
+							   AuthVerifRoleMethodIndexId,
+							   true, NULL, 1, &scankey);
+
+	while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(pg_auth_verifiers_rel, &tmp_tuple->t_self);
+	}
+
+	systable_endscan(sscan);
+
+	/* keep lock until the end of transaction */
+	heap_close(pg_auth_verifiers_rel, NoLock);
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 97be944..f04d17a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,14 +20,46 @@
 #include <crypt.h>
 #endif
 
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_type.h"
 #include "libpq/crypt.h"
 #include "libpq/md5.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+/*
+ * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * method.
+ */
+static char *
+get_role_verifier(Oid roleid, const char method)
+{
+	HeapTuple	tuple;
+	Datum		verifier_datum;
+	char	   *verifier;
+	bool		isnull;
+
+	/* Now attempt to grab the verifier value */
+	tuple = SearchSysCache2(AUTHVERIFROLEMETH,
+							ObjectIdGetDatum(roleid),
+							CharGetDatum(method));
+	if (!HeapTupleIsValid(tuple))
+		return NULL; /* no verifier available */
+
+	verifier_datum = SysCacheGetAttr(AUTHVERIFROLEMETH, tuple,
+							   Anum_pg_auth_verifiers_value,
+							   &isnull);
+	verifier = TextDatumGetCString(verifier_datum);
+
+	ReleaseSysCache(tuple);
+
+	return verifier;
+}
 
 /*
  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,29 +71,39 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				 char **logdetail)
 {
 	int			retval = STATUS_ERROR;
-	char	   *shadow_pass,
+	char	   *verifier,
 			   *crypt_pwd;
 	TimestampTz vuntil = 0;
+	bool		verifier_is_md5;
 	char	   *crypt_client_pass = client_pass;
 	HeapTuple	roleTup;
 	Datum		datum;
 	bool		isnull;
+	Oid			roleid;
 
 	/* Get role info from pg_authid */
 	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
 	if (!HeapTupleIsValid(roleTup))
 		return STATUS_ERROR;	/* no such user */
 
-	datum = SysCacheGetAttr(AUTHNAME, roleTup,
-							Anum_pg_authid_rolpassword, &isnull);
-	if (isnull)
+	verifier_is_md5 = true;
+
+	roleid = HeapTupleGetOid(roleTup);
+	verifier = get_role_verifier(roleid, AUTH_VERIFIER_MD5);
+	if (verifier == NULL)
 	{
-		ReleaseSysCache(roleTup);
-		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
-							  role);
-		return STATUS_ERROR;	/* user has no password */
+		/* we can also use a plaintext password, by creating the hash from it */
+		verifier_is_md5 = false;
+		verifier = get_role_verifier(roleid, AUTH_VERIFIER_PLAIN);
+
+		if (verifier == NULL)
+		{
+			*logdetail = psprintf(_("User \"%s\" has no password assigned for authentication method \"%s\"."),
+								  role, "md5");
+			ReleaseSysCache(roleTup);
+			return STATUS_ERROR;
+		}
 	}
-	shadow_pass = TextDatumGetCString(datum);
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
 							Anum_pg_authid_rolvaliduntil, &isnull);
@@ -70,7 +112,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 
 	ReleaseSysCache(roleTup);
 
-	if (*shadow_pass == '\0')
+	if (*verifier == '\0')
 		return STATUS_ERROR;	/* empty password */
 
 	CHECK_FOR_INTERRUPTS();
@@ -83,10 +125,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 	{
 		case uaMD5:
 			crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* stored password already encrypted, only do salt */
-				if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+				if (!pg_md5_encrypt(verifier + strlen("md5"),
 									port->md5Salt,
 									sizeof(port->md5Salt), crypt_pwd))
 				{
@@ -99,7 +141,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 				/* stored password is plain, double-encrypt */
 				char	   *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
 
-				if (!pg_md5_encrypt(shadow_pass,
+				if (!pg_md5_encrypt(verifier,
 									port->user_name,
 									strlen(port->user_name),
 									crypt_pwd2))
@@ -121,7 +163,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 			}
 			break;
 		default:
-			if (isMD5(shadow_pass))
+			if (verifier_is_md5)
 			{
 				/* Encrypt user-supplied password to match stored MD5 */
 				crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
@@ -134,7 +176,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 					return STATUS_ERROR;
 				}
 			}
-			crypt_pwd = shadow_pass;
+			crypt_pwd = verifier;
 			break;
 	}
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c8425d..6034dac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2677,6 +2677,17 @@ _copyRoleSpec(const RoleSpec *from)
 	return newnode;
 }
 
+static AuthVerifierSpec *
+_copyAuthVerifierSpec(const AuthVerifierSpec *from)
+{
+	AuthVerifierSpec *newnode = makeNode(AuthVerifierSpec);
+
+	COPY_SCALAR_FIELD(veriftype);
+	COPY_STRING_FIELD(value);
+
+	return newnode;
+}
+
 static Query *
 _copyQuery(const Query *from)
 {
@@ -4964,6 +4975,9 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_AuthVerifierSpec:
+			retval = _copyAuthVerifierSpec(from);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1d6c43c..935b8dd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2581,6 +2581,15 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalAuthVerifierSpec(const AuthVerifierSpec *a, const AuthVerifierSpec *b)
+{
+	COMPARE_SCALAR_FIELD(veriftype);
+	COMPARE_STRING_FIELD(value);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3321,6 +3330,9 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_AuthVerifierSpec:
+			retval = _equalAuthVerifierSpec(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 426a09d..2e67c96 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,15 +51,18 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
+#include "commands/user.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/gramparse.h"
 #include "parser/parser.h"
 #include "parser/parse_expr.h"
 #include "storage/lmgr.h"
+#include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/datetime.h"
 #include "utils/numeric.h"
@@ -145,6 +148,7 @@ static Node *makeNullAConst(int location);
 static Node *makeAConst(Value *v, int location);
 static Node *makeBoolAConst(bool state, int location);
 static Node *makeRoleSpec(RoleSpecType type, int location);
+static Node *makeAuthVerifierSpec(char type, char *password);
 static void check_qualified_name(List *names, core_yyscan_t yyscanner);
 static List *check_func_name(List *names, core_yyscan_t yyscanner);
 static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -370,6 +374,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				create_generic_options alter_generic_options
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
+				auth_verifier_list
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -497,6 +502,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		createdb_opt_name
 %type <node>	var_value zone_value
 %type <node>	auth_ident RoleSpec opt_granted_by
+%type <node>	AuthVerifierSpec
 
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
@@ -638,7 +644,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERIFIERS VERSION_P VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -918,25 +924,86 @@ AlterOptRoleList:
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
+auth_verifier_list:
+			AuthVerifierSpec
+				{ $$ = list_make1((Node*)$1); }
+			| auth_verifier_list ',' AuthVerifierSpec
+				{ $$ = lappend($1, (Node *)$3); }
+
+AuthVerifierSpec:
+			NonReservedWord '=' Sconst
+				{
+					char	type;
+
+					if (strcmp($1, "md5") == 0)
+						type = AUTH_VERIFIER_MD5;
+					else if (strcmp($1, "plain") == 0)
+						type = AUTH_VERIFIER_PLAIN;
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized authorization verifier option \"%s\"", $1),
+									 parser_errposition(@1)));
+					$$ = (Node *) makeAuthVerifierSpec(type, $3);
+				}
+
 AlterOptRoleElem:
 			PASSWORD Sconst
 				{
-					$$ = makeDefElem("password",
-									 (Node *)makeString($2));
+					char	   *rawstring = pstrdup(Password_encryption);
+					List	   *elemlist;
+					ListCell   *l;
+					List	   *result = NIL;
+
+					if (!SplitIdentifierString(rawstring, ',', &elemlist))
+						Assert(false); /* should not happen */
+
+					foreach(l, elemlist)
+					{
+						char	   *meth_name = (char *) lfirst(l);
+						char		veriftype;
+						AuthVerifierSpec *n;
+
+						if (strcmp(meth_name, "md5") == 0)
+							veriftype = AUTH_VERIFIER_MD5;
+						else if (strcmp(meth_name, "plain") == 0)
+							veriftype = AUTH_VERIFIER_PLAIN;
+						else
+							Assert(false);	/* should not happen */
+						n = (AuthVerifierSpec *)
+							makeAuthVerifierSpec(veriftype, $2);
+						result = lappend(result, (Node *)n);
+					}
+					pfree(rawstring);
+					list_free(elemlist);
+
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *) result);
 				}
 			| PASSWORD NULL_P
 				{
-					$$ = makeDefElem("password", NULL);
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, NULL);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
 				}
 			| ENCRYPTED PASSWORD Sconst
 				{
-					$$ = makeDefElem("encryptedPassword",
-									 (Node *)makeString($3));
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_MD5, $3);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
 				}
 			| UNENCRYPTED PASSWORD Sconst
 				{
-					$$ = makeDefElem("unencryptedPassword",
-									 (Node *)makeString($3));
+					AuthVerifierSpec *n = (AuthVerifierSpec *)
+						makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, $3);
+					$$ = makeDefElem("passwordVerifiers",
+									 (Node *)list_make1((Node *)n));
+				}
+			| PASSWORD VERIFIERS '(' auth_verifier_list ')'
+				{
+					$$ = makeDefElem("passwordVerifiers", (Node *)$4);
 				}
 			| INHERIT
 				{
@@ -13877,6 +13944,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARYING
+			| VERIFIERS
 			| VERSION_P
 			| VIEW
 			| VIEWS
@@ -14271,6 +14339,20 @@ makeRoleSpec(RoleSpecType type, int location)
 	return (Node *) spec;
 }
 
+/* makeAuthVerifierSpec
+ * Create a AuthVerifierSpec for the given type.
+ */
+static Node *
+makeAuthVerifierSpec(char type, char *password)
+{
+	AuthVerifierSpec *spec = makeNode(AuthVerifierSpec);
+
+	spec->veriftype = type;
+	spec->value = password;
+
+	return (Node *) spec;
+}
+
 /* check_qualified_name --- check the result of qualified_name production
  *
  * It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 577c059..9befb84 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1073,6 +1073,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
 		case AUTHNAME:
 		case AUTHOID:
 		case AUTHMEMMEMROLE:
+		case AUTHVERIFROLEMETH:
 
 			/*
 			 * Protect authentication lookups occurring before relcache has
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 44e9509..0990e71 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
@@ -97,6 +98,7 @@ static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type
 static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
 static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
 static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
+static const FormData_pg_attribute Desc_pg_auth_verifiers[Natts_pg_auth_verifiers] = {Schema_pg_auth_verifiers};
 static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
 
 /*
@@ -1539,7 +1541,7 @@ LookupOpclassInfo(Oid operatorClassOid,
  *		catalogs.
  *
  * formrdesc is currently used for: pg_database, pg_authid, pg_auth_members,
- * pg_class, pg_attribute, pg_proc, and pg_type
+ * pg_auth_verifiers, pg_class, pg_attribute, pg_proc, and pg_type
  * (see RelationCacheInitializePhase2/3).
  *
  * Note that these catalogs can't have constraints (except attnotnull),
@@ -2816,6 +2818,7 @@ RelationBuildLocalRelation(const char *relname,
 		case DatabaseRelationId:
 		case AuthIdRelationId:
 		case AuthMemRelationId:
+		case AuthVerifRelationId:
 		case RelationRelationId:
 		case AttributeRelationId:
 		case ProcedureRelationId:
@@ -3202,8 +3205,10 @@ RelationCacheInitializePhase2(void)
 				  true, Natts_pg_authid, Desc_pg_authid);
 		formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
 				  false, Natts_pg_auth_members, Desc_pg_auth_members);
+		formrdesc("pg_auth_verifiers", AuthVerifRelation_Rowtype_Id, true,
+				  false, Natts_pg_auth_verifiers, Desc_pg_auth_verifiers);
 
-#define NUM_CRITICAL_SHARED_RELS	3	/* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS	4	/* fix if you change list above */
 	}
 
 	MemoryContextSwitchTo(oldcxt);
@@ -3323,8 +3328,8 @@ RelationCacheInitializePhase3(void)
 	 * initial lookup of MyDatabaseId, without which we'll never find any
 	 * non-shared catalogs at all.  Autovacuum calls InitPostgres with a
 	 * database OID, so it instead depends on DatabaseOidIndexId.  We also
-	 * need to nail up some indexes on pg_authid and pg_auth_members for use
-	 * during client authentication.
+	 * need to nail up some indexes on pg_authid, pg_auth_verifiers and
+	 * pg_auth_members for use during client authentication.
 	 */
 	if (!criticalSharedRelcachesBuilt)
 	{
@@ -3338,8 +3343,10 @@ RelationCacheInitializePhase3(void)
 							AuthIdRelationId);
 		load_critical_index(AuthMemMemRoleIndexId,
 							AuthMemRelationId);
+		load_critical_index(AuthVerifRoleMethodIndexId,
+							AuthVerifRelationId);
 
-#define NUM_CRITICAL_SHARED_INDEXES 5	/* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 6	/* fix if you change list above */
 
 		criticalSharedRelcachesBuilt = true;
 	}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index efce7b9..5294558 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
@@ -247,6 +248,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{AuthVerifRelationId,		/* AUTHVERIFMETHROLE */
+	 AuthVerifMethodRoleIndexId,
+		2,
+		{
+			Anum_pg_auth_verifiers_method,
+			Anum_pg_auth_verifiers_roleid,
+			0,
+			0
+		},
+		4
+	},
+	{AuthVerifRelationId,		/* AUTHVERIFROLEMETH */
+	 AuthVerifRoleMethodIndexId,
+		2,
+		{
+			Anum_pg_auth_verifiers_roleid,
+			Anum_pg_auth_verifiers_method,
+			0,
+			0
+		},
+		4
+	},
 	{
 		CastRelationId,			/* CASTSOURCETARGET */
 		CastSourceTargetIndexId,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b3dac51..082108e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -179,6 +179,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
 static bool check_application_name(char **newval, void **extra, GucSource source);
 static void assign_application_name(const char *newval, void *extra);
 static bool check_cluster_name(char **newval, void **extra, GucSource source);
+static bool check_password_encryption(char **newval, void **extra,
+									  GucSource source);
 static const char *show_unix_socket_permissions(void);
 static const char *show_log_file_mode(void);
 
@@ -425,8 +427,6 @@ bool		check_function_bodies = true;
 bool		default_with_oids = false;
 bool		SQL_inheritance = true;
 
-bool		Password_encryption = true;
-
 int			log_min_error_statement = ERROR;
 int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
@@ -438,6 +438,8 @@ int			temp_file_limit = -1;
 
 int			num_temp_buffers = 1024;
 
+char	   *Password_encryption;
+
 char	   *cluster_name = "";
 char	   *ConfigFileName;
 char	   *HbaFileName;
@@ -1304,17 +1306,6 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
-			gettext_noop("Encrypt passwords."),
-			gettext_noop("When a password is specified in CREATE USER or "
-			   "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
-						 "this parameter determines whether the password is to be encrypted.")
-		},
-		&Password_encryption,
-		true,
-		NULL, NULL, NULL
-	},
-	{
 		{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
 			gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
 			gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3262,6 +3253,19 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
+		{"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+			gettext_noop("List of password encryption methods."),
+			gettext_noop("When a password is specified in CREATE USER or "
+				"ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+				"this parameter determines how the password is to be encrypted."),
+			GUC_LIST_INPUT
+		},
+		&Password_encryption,
+		"md5",
+		check_password_encryption, NULL, NULL
+	},
+
+	{
 		{"ssl_cert_file", PGC_POSTMASTER, CONN_AUTH_SECURITY,
 			gettext_noop("Location of the SSL server certificate file."),
 			NULL
@@ -10116,6 +10120,41 @@ check_cluster_name(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+static bool
+check_password_encryption(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring = pstrdup(*newval);	/* get copy of list string */
+	List	   *elemlist;
+	ListCell   *l;
+
+	if (!SplitIdentifierString(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		pfree(rawstring);
+		list_free(elemlist);
+		Assert(false);
+		return false;	/* GUC machinery should have already complained */
+	}
+
+	/* Check that only supported formats are listed */
+	foreach(l, elemlist)
+	{
+		char	   *encryption_name = (char *) lfirst(l);
+
+		if (strcmp(encryption_name, "md5") != 0 &&
+			strcmp(encryption_name, "plain") != 0)
+		{
+			pfree(rawstring);
+			list_free(elemlist);
+			return false;
+		}
+	}
+
+	pfree(rawstring);
+	list_free(elemlist);
+	return true;
+}
+
 static const char *
 show_unix_socket_permissions(void)
 {
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e5d275d..bc0efbf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -87,7 +87,7 @@
 #ssl_key_file = 'server.key'		# (change requires restart)
 #ssl_ca_file = ''			# (change requires restart)
 #ssl_crl_file = ''			# (change requires restart)
-#password_encryption = on
+#password_encryption = 'md5'
 #db_user_namespace = off
 #row_security = on
 
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index feeff9e..9ae22bc 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1551,10 +1551,11 @@ setup_auth(void)
 	const char **line;
 	static const char *pg_authid_setup[] = {
 		/*
-		 * The authid table shouldn't be readable except through views, to
-		 * ensure passwords are not publicly visible.
+		 * The authorization tables shouldn't be readable except through
+		 * views, to ensure password data are not publicly visible.
 		 */
 		"REVOKE ALL on pg_authid FROM public;\n",
+		"REVOKE ALL on pg_auth_verifiers FROM public;\n",
 		NULL
 	};
 
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c4b6ae8..b4d09e5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -663,8 +663,22 @@ dumpRoles(PGconn *conn)
 				i_is_current_user;
 	int			i;
 
-	/* note: rolconfig is dumped later */
-	if (server_version >= 90500)
+	/*
+	 * Note: rolconfig is dumped later. In 9.6 and above, password
+	 * information is dumped later on.
+	 */
+	if (server_version >= 90600)
+		printfPQExpBuffer(buf,
+						  "SELECT oid, rolname, rolsuper, rolinherit, "
+						  "rolcreaterole, rolcreatedb, "
+						  "rolcanlogin, rolconnlimit, "
+						  "null::text as rolpassword, "
+						  "rolvaliduntil, rolreplication, rolbypassrls, "
+			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+						  "rolname = current_user AS is_current_user "
+						  "FROM pg_authid "
+						  "ORDER BY 2");
+	else if (server_version >= 90500)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
 						  "rolcreaterole, rolcreatedb, "
@@ -869,6 +883,65 @@ dumpRoles(PGconn *conn)
 
 	PQclear(res);
 
+	/*
+	 * Dump password configuration for all roles.
+	 */
+	if (server_version >= 90600)
+	{
+		char   *current_user = NULL;
+		bool	first_elt = true;
+		res = executeQuery(conn,
+						   "SELECT a.rolname, v.verimet, v.verival "
+						   "FROM pg_auth_verifiers AS v "
+						   "LEFT JOIN pg_authid AS a ON (v.roleid = a.oid) "
+						   "ORDER BY rolname;");
+
+		for (i = 0; i < PQntuples(res); i++)
+		{
+			char   *user_name = PQgetvalue(res, i, 0);
+			char	verifier_meth = *PQgetvalue(res, i, 1);
+			char   *verifier_value = PQgetvalue(res, i, 2);
+
+			/* Switch to new ALTER ROLE query when a different user is found */
+			if (current_user == NULL ||
+				strcmp(user_name, current_user) != 0)
+			{
+				/* Finish last query */
+				if (current_user != NULL)
+				{
+					appendPQExpBufferStr(buf, ");\n");
+					fprintf(OPF, "%s", buf->data);
+				}
+
+				resetPQExpBuffer(buf);
+
+				if (current_user)
+					pg_free(current_user);
+				current_user = pg_strdup(user_name);
+				first_elt = true;
+				appendPQExpBuffer(buf, "ALTER ROLE %s PASSWORD VERIFIERS (",
+					current_user);
+			}
+
+			if (first_elt)
+				first_elt = false;
+			else
+				appendPQExpBufferStr(buf, ", ");
+
+			if (verifier_meth == 'm')
+				appendPQExpBufferStr(buf, "md5 = ");
+			else if (verifier_meth == 'p')
+				appendPQExpBufferStr(buf, "plain = ");
+			appendStringLiteralConn(buf, verifier_value, conn);
+		}
+		if (current_user != NULL)
+		{
+			appendPQExpBufferStr(buf, ");\n");
+			fprintf(OPF, "%s", buf->data);
+		}
+	}
+
+
 	fprintf(OPF, "\n\n");
 
 	destroyPQExpBuffer(buf);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index c38958d..cd262a2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -97,6 +97,11 @@ DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, on pg_auth_members
 DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
 #define AuthMemMemRoleIndexId	2695
 
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_role_method_index, 3315, on pg_auth_verifiers using btree(roleid oid_ops, verimet char_ops));
+#define AuthVerifRoleMethodIndexId	3315
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_method_role_index, 3316, on pg_auth_verifiers using btree(verimet char_ops, roleid oid_ops));
+#define AuthVerifMethodRoleIndexId	3316
+
 DECLARE_UNIQUE_INDEX(pg_cast_oid_index, 2660, on pg_cast using btree(oid oid_ops));
 #define CastOidIndexId	2660
 DECLARE_UNIQUE_INDEX(pg_cast_source_target_index, 2661, on pg_cast using btree(castsource oid_ops, casttarget oid_ops));
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
new file mode 100644
index 0000000..daef049
--- /dev/null
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_verifiers.h
+ *	  definition of the system "authorization password hashes" relation
+ *	  (pg_auth_verifiers) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_auth_verifiers.h
+ *
+ * NOTES
+ *	  the genbki.pl script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_VERIFIERS_H
+#define PG_AUTH_VERIFIERS_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_auth_verifiers definition.  cpp turns this into
+ *		typedef struct FormData_pg_auth_verifiers
+ * ----------------
+ */
+#define AuthVerifRelationId	3300
+#define AuthVerifRelation_Rowtype_Id	3308
+
+CATALOG(pg_auth_verifiers,3300) BKI_SHARED_RELATION BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(3308) BKI_SCHEMA_MACRO
+{
+	Oid			roleid;					/* ID of the role using this hash */
+	char		verimet;				/* Method used to generate the hash *
+										 * See AUTH_VERIFIER_xxx below */
+
+#ifdef CATALOG_VARLEN					/* variable-length fields start here */
+	text verival BKI_FORCE_NOT_NULL;	/* Hash value */
+#endif
+} FormData_pg_auth_verifiers;
+
+/* ----------------
+ *		Form_pg_auth_verifiers corresponds to a pointer to a tuple with
+ *		the format of pg_auth_verifiers relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
+
+/* ----------------
+ *		compiler constants for pg_auth_verifiers
+ * ----------------
+ */
+#define Natts_pg_auth_verifiers				3
+#define Anum_pg_auth_verifiers_roleid		1
+#define Anum_pg_auth_verifiers_method		2
+#define Anum_pg_auth_verifiers_value		3
+
+#define AUTH_VERIFIER_PLAIN	'p'		/* plain verifier */
+#define AUTH_VERIFIER_MD5	'm'		/* md5 verifier */
+
+#endif   /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index d5f19d6..623dc1d 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,6 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
 
 	/* remaining fields may be null; use heap_getattr to read them! */
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
-	text		rolpassword;	/* password, if any */
 	timestamptz rolvaliduntil;	/* password expiration time, if any */
 #endif
 } FormData_pg_authid;
@@ -75,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  *		compiler constants for pg_authid
  * ----------------
  */
-#define Natts_pg_authid					11
+#define Natts_pg_authid					10
 #define Anum_pg_authid_rolname			1
 #define Anum_pg_authid_rolsuper			2
 #define Anum_pg_authid_rolinherit		3
@@ -85,8 +84,7 @@ typedef FormData_pg_authid *Form_pg_authid;
 #define Anum_pg_authid_rolreplication	7
 #define Anum_pg_authid_rolbypassrls		8
 #define Anum_pg_authid_rolconnlimit		9
-#define Anum_pg_authid_rolpassword		10
-#define Anum_pg_authid_rolvaliduntil	11
+#define Anum_pg_authid_rolvaliduntil	10
 
 /* ----------------
  *		initial contents of pg_authid
@@ -95,7 +93,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  * user choices.
  * ----------------
  */
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_));
 
 #define BOOTSTRAP_SUPERUSERID 10
 
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..636e8ac 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,12 +14,13 @@
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
+/* GUC parameter */
+extern char *Password_encryption;
 
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT		0
-#define PASSWORD_TYPE_MD5			1
-
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username,
+								List *passwordVerifiers,
+								Datum validuntil_time,
+								bool validuntil_null);
 
 extern PGDLLIMPORT check_password_hook_type check_password_hook;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 748e434..b060ce8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -427,6 +427,7 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_AuthVerifierSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151c93a..6ac716d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -309,6 +309,17 @@ typedef struct RoleSpec
 } RoleSpec;
 
 /*
+ * AuthVerifierSpec - a password verifier with a some dedicated values.
+ */
+typedef struct AuthVerifierSpec
+{
+	NodeTag		type;
+	char		veriftype;		/* type of this verifier, as listed in *
+								 * pg_auth_verifiers.h */
+	char	   *value;			/* value specified by user */
+} AuthVerifierSpec;
+
+/*
  * FuncCall - a function or aggregate invocation
  *
  * agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)', or if
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 2414069..c8201b4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -415,6 +415,7 @@ PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("verifiers", VERIFIERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 18404e2..0d6c29e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,8 @@ enum SysCacheIdentifier
 	AUTHMEMROLEMEM,
 	AUTHNAME,
 	AUTHOID,
+	AUTHVERIFMETHROLE,
+	AUTHVERIFROLEMETH,
 	CASTSOURCETARGET,
 	CLAAMNAMENSP,
 	CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..73ca2e5
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,105 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR:  invalid value for parameter "password_encryption": "novalue"
+SET password_encryption = true; -- error
+ERROR:  invalid value for parameter "password_encryption": "true"
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+   rolname    | verimet | substr 
+--------------+---------+--------
+ role_passwd1 | p       | rol
+ role_passwd2 | m       | md5
+ role_passwd3 | m       | md5
+ role_passwd3 | p       | rol
+(4 rows)
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+NOTICE:  MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname = 'role_passwd3_new'
+    ORDER BY a.rolname, v.verimet;
+     rolname      | verimet | substr 
+------------------+---------+--------
+ role_passwd3_new | p       | rol
+(1 row)
+
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+ 
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+   rolname    | verimet | substr 
+--------------+---------+--------
+ role_passwd1 | p       | foo
+ role_passwd2 | m       | md5
+ role_passwd3 | m       | md5
+ role_passwd4 | m       | md5
+(4 rows)
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ERROR:  unrecognized authorization verifier option "unexistent_verif"
+LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
+                                                    ^
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+   rolname    | verimet | substr 
+--------------+---------+--------
+ role_passwd1 | m       | md5
+ role_passwd2 | p       | foo
+ role_passwd3 | m       | md5
+ role_passwd4 | m       | md5
+(4 rows)
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
+ rolname | verimet 
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 44c6740..9b9fe5f 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1628,7 +1628,14 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.rolsuper AS usesuper,
     pg_authid.rolreplication AS userepl,
     pg_authid.rolbypassrls AS usebypassrls,
-    pg_authid.rolpassword AS passwd,
+    ARRAY( SELECT
+                CASE pg_auth_verifiers.verimet
+                    WHEN 'p'::"char" THEN ('plain:'::text || pg_auth_verifiers.verival)
+                    WHEN 'm'::"char" THEN ('md5:'::text || pg_auth_verifiers.verival)
+                    ELSE NULL::text
+                END AS verifiers
+           FROM pg_auth_verifiers
+          WHERE (pg_auth_verifiers.roleid = pg_authid.oid)) AS verifiers,
     (pg_authid.rolvaliduntil)::abstime AS valuntil,
     s.setconfig AS useconfig
    FROM (pg_authid
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..d4b3f36 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -91,6 +91,7 @@ pg_amproc|t
 pg_attrdef|t
 pg_attribute|t
 pg_auth_members|t
+pg_auth_verifiers|t
 pg_authid|t
 pg_cast|t
 pg_class|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4df15de..c24420b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets password
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 15d74d4..88d10cf 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -109,6 +109,7 @@ test: matview
 test: lock
 test: replica_identity
 test: rowsecurity
+test: password
 test: object_address
 test: tablesample
 test: alter_generic
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..0376e1b
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,72 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- error
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname = 'role_passwd3_new'
+    ORDER BY a.rolname, v.verimet;
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+ 
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%'
+    ORDER BY a.rolname, v.verimet;
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+    FROM pg_auth_verifiers v
+    LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+    WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
-- 
2.5.0

0002-Move-sha1.c-to-src-common.patchtext/x-diff; charset=US-ASCII; name=0002-Move-sha1.c-to-src-common.patchDownload
From c27c6d472d0f6340ba899af06034826b4c3d1bf1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 7 Aug 2015 13:42:53 +0900
Subject: [PATCH 2/6] Move sha1.c to src/common

---
 contrib/pgcrypto/Makefile                       | 4 ++--
 contrib/pgcrypto/internal.c                     | 2 +-
 src/common/Makefile                             | 2 +-
 {contrib/pgcrypto => src/common}/sha1.c         | 4 ++--
 {contrib/pgcrypto => src/include/common}/sha1.h | 2 +-
 src/tools/msvc/Mkvcbuild.pm                     | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)
 rename {contrib/pgcrypto => src/common}/sha1.c (99%)
 rename {contrib/pgcrypto => src/include/common}/sha1.h (98%)

diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 18bad1a..bb5118e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,6 +1,6 @@
 # contrib/pgcrypto/Makefile
 
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
+INT_SRCS = md5.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
 		fortuna.c random.c pgp-mpi-internal.c imath.c
 INT_TESTS = sha2
 
@@ -30,7 +30,7 @@ DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
 	pgcrypto--unpackaged--1.0.sql
 PGFILEDESC = "pgcrypto - cryptographic functions"
 
-REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
+REGRESS = init md5 hmac-md5 hmac-sha1 blowfish rijndael \
 	$(CF_TESTS) \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
 	pgp-armor pgp-decrypt pgp-encrypt $(CF_PGP_TESTS) \
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..9f42955 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -35,7 +35,7 @@
 
 #include "px.h"
 #include "md5.h"
-#include "sha1.h"
+#include "common/sha1.h"
 #include "blf.h"
 #include "rijndael.h"
 #include "fortuna.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index c47445e..d6c2a57 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = exec.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o string.o username.o wait_error.o
+	rmtree.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
diff --git a/contrib/pgcrypto/sha1.c b/src/common/sha1.c
similarity index 99%
rename from contrib/pgcrypto/sha1.c
rename to src/common/sha1.c
index 0e753ce..4d9a325 100644
--- a/contrib/pgcrypto/sha1.c
+++ b/src/common/sha1.c
@@ -28,7 +28,7 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * contrib/pgcrypto/sha1.c
+ * src/common/sha1.c
  */
 /*
  * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
@@ -40,7 +40,7 @@
 
 #include <sys/param.h>
 
-#include "sha1.h"
+#include "common/sha1.h"
 
 /* constant table */
 static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
diff --git a/contrib/pgcrypto/sha1.h b/src/include/common/sha1.h
similarity index 98%
rename from contrib/pgcrypto/sha1.h
rename to src/include/common/sha1.h
index 5532ca1..d5ff296 100644
--- a/contrib/pgcrypto/sha1.h
+++ b/src/include/common/sha1.h
@@ -1,4 +1,4 @@
-/*	contrib/pgcrypto/sha1.h */
+/*	src/include/common/sha1.h */
 /*	   $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $    */
 
 /*
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 3abbb4c..fec81ea 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -106,7 +106,7 @@ sub mkvcbuild
 
 	our @pgcommonallfiles = qw(
 	  exec.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
-	  string.c username.c wait_error.c);
+	  sha1.c string.c username.c wait_error.c);
 
 	our @pgcommonfrontendfiles = (
 		@pgcommonallfiles, qw(fe_memutils.c
-- 
2.5.0

0003-Refactor-sendAuthRequest.patchtext/x-diff; charset=US-ASCII; name=0003-Refactor-sendAuthRequest.patchDownload
From 2a12f171deaaf3238c9dc0a3586d245f29d00e8f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 3 Aug 2015 15:42:49 +0900
Subject: [PATCH 3/6] Refactor sendAuthRequest

---
 src/backend/libpq/auth.c | 65 ++++++++++++++++++++++++------------------------
 1 file changed, 32 insertions(+), 33 deletions(-)

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 4699efa..1b28722 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -36,7 +36,8 @@
  * Global authentication functions
  *----------------------------------------------------------------
  */
-static void sendAuthRequest(Port *port, AuthRequest areq);
+static void sendAuthRequest(Port *port, AuthRequest areq, char *extradata,
+				int extralen);
 static void auth_failed(Port *port, int status, char *logdetail);
 static char *recv_password_packet(Port *port);
 static int	recv_and_check_password_packet(Port *port, char **logdetail);
@@ -479,7 +480,7 @@ ClientAuthentication(Port *port)
 
 		case uaGSS:
 #ifdef ENABLE_GSS
-			sendAuthRequest(port, AUTH_REQ_GSS);
+			sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
 			status = pg_GSS_recvauth(port);
 #else
 			Assert(false);
@@ -488,7 +489,7 @@ ClientAuthentication(Port *port)
 
 		case uaSSPI:
 #ifdef ENABLE_SSPI
-			sendAuthRequest(port, AUTH_REQ_SSPI);
+			sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
 			status = pg_SSPI_recvauth(port);
 #else
 			Assert(false);
@@ -512,12 +513,13 @@ ClientAuthentication(Port *port)
 				ereport(FATAL,
 						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
 						 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
-			sendAuthRequest(port, AUTH_REQ_MD5);
+			/* Add the salt for encrypted passwords. */
+			sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
 		case uaPassword:
-			sendAuthRequest(port, AUTH_REQ_PASSWORD);
+			sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
@@ -556,7 +558,7 @@ ClientAuthentication(Port *port)
 		(*ClientAuthentication_hook) (port, status);
 
 	if (status == STATUS_OK)
-		sendAuthRequest(port, AUTH_REQ_OK);
+		sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
 	else
 		auth_failed(port, status, logdetail);
 }
@@ -566,7 +568,7 @@ ClientAuthentication(Port *port)
  * Send an authentication request packet to the frontend.
  */
 static void
-sendAuthRequest(Port *port, AuthRequest areq)
+sendAuthRequest(Port *port, AuthRequest areq, char *extradata, int extralen)
 {
 	StringInfoData buf;
 
@@ -575,27 +577,8 @@ sendAuthRequest(Port *port, AuthRequest areq)
 	pq_beginmessage(&buf, 'R');
 	pq_sendint(&buf, (int32) areq, sizeof(int32));
 
-	/* Add the salt for encrypted passwords. */
-	if (areq == AUTH_REQ_MD5)
-		pq_sendbytes(&buf, port->md5Salt, 4);
-
-#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
-
-	/*
-	 * Add the authentication data for the next step of the GSSAPI or SSPI
-	 * negotiation.
-	 */
-	else if (areq == AUTH_REQ_GSS_CONT)
-	{
-		if (port->gss->outbuf.length > 0)
-		{
-			elog(DEBUG4, "sending GSS token of length %u",
-				 (unsigned int) port->gss->outbuf.length);
-
-			pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
-		}
-	}
-#endif
+	if (extralen > 0)
+		pq_sendbytes(&buf, extradata, extralen);
 
 	pq_endmessage(&buf);
 
@@ -908,7 +891,15 @@ pg_GSS_recvauth(Port *port)
 			elog(DEBUG4, "sending GSS response token of length %u",
 				 (unsigned int) port->gss->outbuf.length);
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			gss_release_buffer(&lmin_s, &port->gss->outbuf);
 		}
@@ -1151,7 +1142,15 @@ pg_SSPI_recvauth(Port *port)
 			port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
 			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
 
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+			/*
+			 * Add the authentication data for the next step of the GSSAPI or
+			 * SSPI negotiation.
+			 */
+			elog(DEBUG4, "sending GSS token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+							port->gss->outbuf.value, port->gss->outbuf.length);
 
 			FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
 		}
@@ -1672,7 +1671,7 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
 					 * let's go ask the client to send a password, which we
 					 * then stuff into PAM.
 					 */
-					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
+					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
 					passwd = recv_password_packet(pam_port_cludge);
 					if (passwd == NULL)
 					{
@@ -1947,7 +1946,7 @@ CheckLDAPAuth(Port *port)
 	if (port->hba->ldapport == 0)
 		port->hba->ldapport = LDAP_PORT;
 
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
@@ -2303,7 +2302,7 @@ CheckRADIUSAuth(Port *port)
 		identifier = port->hba->radiusidentifier;
 
 	/* Send regular password request to client, and get the response */
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
 	passwd = recv_password_packet(port);
 	if (passwd == NULL)
-- 
2.5.0

0004-Refactor-RandomSalt-to-handle-salts-of-different-len.patchtext/x-diff; charset=US-ASCII; name=0004-Refactor-RandomSalt-to-handle-salts-of-different-len.patchDownload
From cb6731309469d8e751c4fb9405634e2d8d82313b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 11 Aug 2015 20:43:26 +0900
Subject: [PATCH 4/6] Refactor RandomSalt to handle salts of different lengths

---
 src/backend/postmaster/postmaster.c | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 000524d..c2d5b0e 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -401,7 +401,7 @@ static int	initMasks(fd_set *rmask);
 static void report_fork_failure_to_client(Port *port, int errnum);
 static CAC_state canAcceptConnections(void);
 static long PostmasterRandom(void);
-static void RandomSalt(char *md5Salt);
+static void RandomSalt(char *salt, int len);
 static void signal_child(pid_t pid, int signal);
 static bool SignalSomeChildren(int signal, int targets);
 static bool SignalUnconnectedWorkers(int signal);
@@ -2282,7 +2282,7 @@ ConnCreate(int serverFd)
 	 * after.  Else the postmaster's random sequence won't get advanced, and
 	 * all backends would end up using the same salt...
 	 */
-	RandomSalt(port->md5Salt);
+	RandomSalt(port->md5Salt, sizeof(port->md5Salt));
 
 	/*
 	 * Allocate GSSAPI specific state struct
@@ -5039,23 +5039,21 @@ StartupPacketTimeoutHandler(void)
  * RandomSalt
  */
 static void
-RandomSalt(char *md5Salt)
+RandomSalt(char *md5Salt, int len)
 {
 	long		rand;
+	int			i;
 
 	/*
 	 * We use % 255, sacrificing one possible byte value, so as to ensure that
 	 * all bits of the random() value participate in the result. While at it,
 	 * add one to avoid generating any null bytes.
 	 */
-	rand = PostmasterRandom();
-	md5Salt[0] = (rand % 255) + 1;
-	rand = PostmasterRandom();
-	md5Salt[1] = (rand % 255) + 1;
-	rand = PostmasterRandom();
-	md5Salt[2] = (rand % 255) + 1;
-	rand = PostmasterRandom();
-	md5Salt[3] = (rand % 255) + 1;
+	for (i = 0; i < len; i++)
+	{
+		rand = PostmasterRandom();
+		md5Salt[i] = (rand % 255) + 1;
+	}
 }
 
 /*
-- 
2.5.0

0005-Move-encoding-routines-to-src-common.patchtext/x-diff; charset=US-ASCII; name=0005-Move-encoding-routines-to-src-common.patchDownload
From 4522fb11637eb2d943418950698a5609ec3a83f1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 17 Aug 2015 12:37:08 +0900
Subject: [PATCH 5/6] Move encoding routines to src/common/

The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 is planned to be used by SCRAM-SHA1, moving the others made sense
for consistency.
---
 src/backend/utils/adt/encode.c             | 408 +----------------------------
 src/common/Makefile                        |   4 +-
 src/{backend/utils/adt => common}/encode.c | 356 ++++++++++---------------
 src/include/common/encode.h                |  30 +++
 src/tools/msvc/Mkvcbuild.pm                |   4 +-
 5 files changed, 167 insertions(+), 635 deletions(-)
 copy src/{backend/utils/adt => common}/encode.c (72%)
 create mode 100644 src/include/common/encode.h

diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index 4b32b6c..e32da54 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
 
 #include <ctype.h>
 
+#include "common/encode.h"
 #include "utils/builtins.h"
 
 
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
 
 
 /*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[128] = {
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
-	const char *end = src + len;
-
-	while (src < end)
-	{
-		*dst++ = hextbl[(*src >> 4) & 0xF];
-		*dst++ = hextbl[*src & 0xF];
-		src++;
-	}
-	return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
-	int			res = -1;
-
-	if (c > 0 && c < 127)
-		res = hexlookup[(unsigned char) c];
-
-	if (res < 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
-	return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
-	const char *s,
-			   *srcend;
-	char		v1,
-				v2,
-			   *p;
-
-	srcend = src + len;
-	s = src;
-	p = dst;
-	while (s < srcend)
-	{
-		if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
-		{
-			s++;
-			continue;
-		}
-		v1 = get_hex(*s++) << 4;
-		if (s >= srcend)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				  errmsg("invalid hexadecimal data: odd number of digits")));
-
-		v2 = get_hex(*s++);
-		*p++ = v1 | v2;
-	}
-
-	return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
-	return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
-	return srclen >> 1;
-}
-
-/*
- * BASE64
- */
-
-static const char _base64[] =
-"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-static const int8 b64lookup[128] = {
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
-	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
-	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
-	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
-};
-
-static unsigned
-b64_encode(const char *src, unsigned len, char *dst)
-{
-	char	   *p,
-			   *lend = dst + 76;
-	const char *s,
-			   *end = src + len;
-	int			pos = 2;
-	uint32		buf = 0;
-
-	s = src;
-	p = dst;
-
-	while (s < end)
-	{
-		buf |= (unsigned char) *s << (pos << 3);
-		pos--;
-		s++;
-
-		/* write it out */
-		if (pos < 0)
-		{
-			*p++ = _base64[(buf >> 18) & 0x3f];
-			*p++ = _base64[(buf >> 12) & 0x3f];
-			*p++ = _base64[(buf >> 6) & 0x3f];
-			*p++ = _base64[buf & 0x3f];
-
-			pos = 2;
-			buf = 0;
-		}
-		if (p >= lend)
-		{
-			*p++ = '\n';
-			lend = p + 76;
-		}
-	}
-	if (pos != 2)
-	{
-		*p++ = _base64[(buf >> 18) & 0x3f];
-		*p++ = _base64[(buf >> 12) & 0x3f];
-		*p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
-		*p++ = '=';
-	}
-
-	return p - dst;
-}
-
-static unsigned
-b64_decode(const char *src, unsigned len, char *dst)
-{
-	const char *srcend = src + len,
-			   *s = src;
-	char	   *p = dst;
-	char		c;
-	int			b = 0;
-	uint32		buf = 0;
-	int			pos = 0,
-				end = 0;
-
-	while (s < srcend)
-	{
-		c = *s++;
-
-		if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
-			continue;
-
-		if (c == '=')
-		{
-			/* end sequence */
-			if (!end)
-			{
-				if (pos == 2)
-					end = 1;
-				else if (pos == 3)
-					end = 2;
-				else
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-							 errmsg("unexpected \"=\" while decoding base64 sequence")));
-			}
-			b = 0;
-		}
-		else
-		{
-			b = -1;
-			if (c > 0 && c < 127)
-				b = b64lookup[(unsigned char) c];
-			if (b < 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("invalid symbol '%c' while decoding base64 sequence", (int) c)));
-		}
-		/* add it to buffer */
-		buf = (buf << 6) + b;
-		pos++;
-		if (pos == 4)
-		{
-			*p++ = (buf >> 16) & 255;
-			if (end == 0 || end > 1)
-				*p++ = (buf >> 8) & 255;
-			if (end == 0 || end > 2)
-				*p++ = buf & 255;
-			buf = 0;
-			pos = 0;
-		}
-	}
-
-	if (pos != 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid base64 end sequence"),
-				 errhint("Input data is missing padding, truncated, or otherwise corrupted.")));
-
-	return p - dst;
-}
-
-
-static unsigned
-b64_enc_len(const char *src, unsigned srclen)
-{
-	/* 3 bytes will be converted to 4, linefeed after 76 chars */
-	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
-}
-
-static unsigned
-b64_dec_len(const char *src, unsigned srclen)
-{
-	return (srclen * 3) >> 2;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH)			((CH) - '0')
-#define DIG(VAL)		((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
-	const char *end = src + srclen;
-	char	   *rp = dst;
-	int			len = 0;
-
-	while (src < end)
-	{
-		unsigned char c = (unsigned char) *src;
-
-		if (c == '\0' || IS_HIGHBIT_SET(c))
-		{
-			rp[0] = '\\';
-			rp[1] = DIG(c >> 6);
-			rp[2] = DIG((c >> 3) & 7);
-			rp[3] = DIG(c & 7);
-			rp += 4;
-			len += 4;
-		}
-		else if (c == '\\')
-		{
-			rp[0] = '\\';
-			rp[1] = '\\';
-			rp += 2;
-			len += 2;
-		}
-		else
-		{
-			*rp++ = c;
-			len++;
-		}
-
-		src++;
-	}
-
-	return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
-	const char *end = src + srclen;
-	char	   *rp = dst;
-	int			len = 0;
-
-	while (src < end)
-	{
-		if (src[0] != '\\')
-			*rp++ = *src++;
-		else if (src + 3 < end &&
-				 (src[1] >= '0' && src[1] <= '3') &&
-				 (src[2] >= '0' && src[2] <= '7') &&
-				 (src[3] >= '0' && src[3] <= '7'))
-		{
-			int			val;
-
-			val = VAL(src[1]);
-			val <<= 3;
-			val += VAL(src[2]);
-			val <<= 3;
-			*rp++ = val + VAL(src[3]);
-			src += 4;
-		}
-		else if (src + 1 < end &&
-				 (src[1] == '\\'))
-		{
-			*rp++ = '\\';
-			src += 2;
-		}
-		else
-		{
-			/*
-			 * One backslash, not followed by ### valid octal. Should never
-			 * get here, since esc_dec_len does same check.
-			 */
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-					 errmsg("invalid input syntax for type bytea")));
-		}
-
-		len++;
-	}
-
-	return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
-	const char *end = src + srclen;
-	int			len = 0;
-
-	while (src < end)
-	{
-		if (*src == '\0' || IS_HIGHBIT_SET(*src))
-			len += 4;
-		else if (*src == '\\')
-			len += 2;
-		else
-			len++;
-
-		src++;
-	}
-
-	return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
-	const char *end = src + srclen;
-	int			len = 0;
-
-	while (src < end)
-	{
-		if (src[0] != '\\')
-			src++;
-		else if (src + 3 < end &&
-				 (src[1] >= '0' && src[1] <= '3') &&
-				 (src[2] >= '0' && src[2] <= '7') &&
-				 (src[3] >= '0' && src[3] <= '7'))
-		{
-			/*
-			 * backslash + valid octal
-			 */
-			src += 4;
-		}
-		else if (src + 1 < end &&
-				 (src[1] == '\\'))
-		{
-			/*
-			 * two backslashes = backslash
-			 */
-			src += 2;
-		}
-		else
-		{
-			/*
-			 * one backslash, not followed by ### valid octal
-			 */
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-					 errmsg("invalid input syntax for type bytea")));
-		}
-
-		len++;
-	}
-	return len;
-}
-
-/*
  * Common
  */
 
diff --git a/src/common/Makefile b/src/common/Makefile
index d6c2a57..4d26f6e 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -23,8 +23,8 @@ include $(top_builddir)/src/Makefile.global
 override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
-OBJS_COMMON = exec.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o sha1.o string.o username.o wait_error.o
+OBJS_COMMON = encode.o exec.o pg_lzcompress.o pgfnames.o psprintf.o \
+	relpath.o rmtree.o sha1.o string.o username.o wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
diff --git a/src/backend/utils/adt/encode.c b/src/common/encode.c
similarity index 72%
copy from src/backend/utils/adt/encode.c
copy to src/common/encode.c
index 4b32b6c..a5c9c6a 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/common/encode.c
@@ -1,200 +1,27 @@
 /*-------------------------------------------------------------------------
  *
  * encode.c
- *	  Various data encoding/decoding things.
+ *	  Various data encoding/decoding things for base64, hexadecimal and
+ *	  escape. In case of failure, those routines return elog(ERROR) in
+ *	  the backend, and 0 in the frontend to let the caller handle the \
+ *	  error handling, something needed by libpq.
  *
  * Copyright (c) 2001-2015, PostgreSQL Global Development Group
  *
  *
  * IDENTIFICATION
- *	  src/backend/utils/adt/encode.c
+ *	  src/common/encode.c
  *
  *-------------------------------------------------------------------------
  */
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "utils/builtins.h"
-
-
-struct pg_encoding
-{
-	unsigned	(*encode_len) (const char *data, unsigned dlen);
-	unsigned	(*decode_len) (const char *data, unsigned dlen);
-	unsigned	(*encode) (const char *data, unsigned dlen, char *res);
-	unsigned	(*decode) (const char *data, unsigned dlen, char *res);
-};
-
-static const struct pg_encoding *pg_find_encoding(const char *name);
-
-/*
- * SQL functions.
- */
-
-Datum
-binary_encode(PG_FUNCTION_ARGS)
-{
-	bytea	   *data = PG_GETARG_BYTEA_P(0);
-	Datum		name = PG_GETARG_DATUM(1);
-	text	   *result;
-	char	   *namebuf;
-	int			datalen,
-				resultlen,
-				res;
-	const struct pg_encoding *enc;
-
-	datalen = VARSIZE(data) - VARHDRSZ;
-
-	namebuf = TextDatumGetCString(name);
-
-	enc = pg_find_encoding(namebuf);
-	if (enc == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
-	resultlen = enc->encode_len(VARDATA(data), datalen);
-	result = palloc(VARHDRSZ + resultlen);
-
-	res = enc->encode(VARDATA(data), datalen, VARDATA(result));
-
-	/* Make this FATAL 'cause we've trodden on memory ... */
-	if (res > resultlen)
-		elog(FATAL, "overflow - encode estimate too small");
-
-	SET_VARSIZE(result, VARHDRSZ + res);
-
-	PG_RETURN_TEXT_P(result);
-}
-
-Datum
-binary_decode(PG_FUNCTION_ARGS)
-{
-	text	   *data = PG_GETARG_TEXT_P(0);
-	Datum		name = PG_GETARG_DATUM(1);
-	bytea	   *result;
-	char	   *namebuf;
-	int			datalen,
-				resultlen,
-				res;
-	const struct pg_encoding *enc;
-
-	datalen = VARSIZE(data) - VARHDRSZ;
-
-	namebuf = TextDatumGetCString(name);
-
-	enc = pg_find_encoding(namebuf);
-	if (enc == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
-	resultlen = enc->decode_len(VARDATA(data), datalen);
-	result = palloc(VARHDRSZ + resultlen);
-
-	res = enc->decode(VARDATA(data), datalen, VARDATA(result));
-
-	/* Make this FATAL 'cause we've trodden on memory ... */
-	if (res > resultlen)
-		elog(FATAL, "overflow - decode estimate too small");
-
-	SET_VARSIZE(result, VARHDRSZ + res);
-
-	PG_RETURN_BYTEA_P(result);
-}
-
-
-/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[128] = {
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
-	const char *end = src + len;
-
-	while (src < end)
-	{
-		*dst++ = hextbl[(*src >> 4) & 0xF];
-		*dst++ = hextbl[*src & 0xF];
-		src++;
-	}
-	return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
-	int			res = -1;
-
-	if (c > 0 && c < 127)
-		res = hexlookup[(unsigned char) c];
-
-	if (res < 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
-	return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
-	const char *s,
-			   *srcend;
-	char		v1,
-				v2,
-			   *p;
-
-	srcend = src + len;
-	s = src;
-	p = dst;
-	while (s < srcend)
-	{
-		if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
-		{
-			s++;
-			continue;
-		}
-		v1 = get_hex(*s++) << 4;
-		if (s >= srcend)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				  errmsg("invalid hexadecimal data: odd number of digits")));
 
-		v2 = get_hex(*s++);
-		*p++ = v1 | v2;
-	}
-
-	return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
-	return srclen << 1;
-}
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
 
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
-	return srclen >> 1;
-}
+#include "common/encode.h"
 
 /*
  * BASE64
@@ -214,7 +41,7 @@ static const int8 b64lookup[128] = {
 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
 };
 
-static unsigned
+unsigned
 b64_encode(const char *src, unsigned len, char *dst)
 {
 	char	   *p,
@@ -261,7 +88,7 @@ b64_encode(const char *src, unsigned len, char *dst)
 	return p - dst;
 }
 
-static unsigned
+unsigned
 b64_decode(const char *src, unsigned len, char *dst)
 {
 	const char *srcend = src + len,
@@ -290,9 +117,15 @@ b64_decode(const char *src, unsigned len, char *dst)
 				else if (pos == 3)
 					end = 2;
 				else
+				{
+#ifndef FRONTEND
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 							 errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+					return 0;
+#endif
+				}
 			}
 			b = 0;
 		}
@@ -302,9 +135,16 @@ b64_decode(const char *src, unsigned len, char *dst)
 			if (c > 0 && c < 127)
 				b = b64lookup[(unsigned char) c];
 			if (b < 0)
+			{
+#ifndef FRONTEND
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("invalid symbol '%c' while decoding base64 sequence", (int) c)));
+						 errmsg("invalid symbol '%c' while decoding base64 sequence",
+								(int) c)));
+#else
+				return 0;
+#endif
+			}
 		}
 		/* add it to buffer */
 		buf = (buf << 6) + b;
@@ -322,23 +162,29 @@ b64_decode(const char *src, unsigned len, char *dst)
 	}
 
 	if (pos != 0)
+	{
+#ifndef FRONTEND
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("invalid base64 end sequence"),
 				 errhint("Input data is missing padding, truncated, or otherwise corrupted.")));
+#else
+		return 0;
+#endif
+	}
 
 	return p - dst;
 }
 
 
-static unsigned
+unsigned
 b64_enc_len(const char *src, unsigned srclen)
 {
 	/* 3 bytes will be converted to 4, linefeed after 76 chars */
 	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
 }
 
-static unsigned
+unsigned
 b64_dec_len(const char *src, unsigned srclen)
 {
 	return (srclen * 3) >> 2;
@@ -361,7 +207,7 @@ b64_dec_len(const char *src, unsigned srclen)
 #define VAL(CH)			((CH) - '0')
 #define DIG(VAL)		((VAL) + '0')
 
-static unsigned
+unsigned
 esc_encode(const char *src, unsigned srclen, char *dst)
 {
 	const char *end = src + srclen;
@@ -400,7 +246,7 @@ esc_encode(const char *src, unsigned srclen, char *dst)
 	return len;
 }
 
-static unsigned
+unsigned
 esc_decode(const char *src, unsigned srclen, char *dst)
 {
 	const char *end = src + srclen;
@@ -437,9 +283,13 @@ esc_decode(const char *src, unsigned srclen, char *dst)
 			 * One backslash, not followed by ### valid octal. Should never
 			 * get here, since esc_dec_len does same check.
 			 */
+#ifndef FRONTEND
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 					 errmsg("invalid input syntax for type bytea")));
+#else
+			return 0;
+#endif
 		}
 
 		len++;
@@ -448,7 +298,7 @@ esc_decode(const char *src, unsigned srclen, char *dst)
 	return len;
 }
 
-static unsigned
+unsigned
 esc_enc_len(const char *src, unsigned srclen)
 {
 	const char *end = src + srclen;
@@ -469,7 +319,7 @@ esc_enc_len(const char *src, unsigned srclen)
 	return len;
 }
 
-static unsigned
+unsigned
 esc_dec_len(const char *src, unsigned srclen)
 {
 	const char *end = src + srclen;
@@ -502,9 +352,13 @@ esc_dec_len(const char *src, unsigned srclen)
 			/*
 			 * one backslash, not followed by ### valid octal
 			 */
+#ifndef FRONTEND
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 					 errmsg("invalid input syntax for type bytea")));
+#else
+			return 0;
+#endif
 		}
 
 		len++;
@@ -513,50 +367,104 @@ esc_dec_len(const char *src, unsigned srclen)
 }
 
 /*
- * Common
+ * HEX
  */
 
-static const struct
-{
-	const char *name;
-	struct pg_encoding enc;
-}	enclist[] =
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[128] = {
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
 
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
 {
+	const char *end = src + len;
+
+	while (src < end)
 	{
-		"hex",
-		{
-			hex_enc_len, hex_dec_len, hex_encode, hex_decode
-		}
-	},
+		*dst++ = hextbl[(*src >> 4) & 0xF];
+		*dst++ = hextbl[*src & 0xF];
+		src++;
+	}
+	return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+	int			res = -1;
+
+	if (c > 0 && c < 127)
+		res = hexlookup[(unsigned char) c];
+
+	if (res < 0)
 	{
-		"base64",
-		{
-			b64_enc_len, b64_dec_len, b64_encode, b64_decode
-		}
-	},
+#ifndef FRONTEND
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+		return 0;
+#endif
+	}
+
+	return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+	const char *s,
+			   *srcend;
+	char		v1,
+				v2,
+			   *p;
+
+	srcend = src + len;
+	s = src;
+	p = dst;
+	while (s < srcend)
 	{
-		"escape",
+		if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
 		{
-			esc_enc_len, esc_dec_len, esc_encode, esc_decode
+			s++;
+			continue;
 		}
-	},
-	{
-		NULL,
+		v1 = get_hex(*s++) << 4;
+		if (s >= srcend)
 		{
-			NULL, NULL, NULL, NULL
+#ifndef FRONTEND
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				  errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+			return 0;
+#endif
 		}
+
+		v2 = get_hex(*s++);
+		*p++ = v1 | v2;
 	}
-};
 
-static const struct pg_encoding *
-pg_find_encoding(const char *name)
-{
-	int			i;
+	return p - dst;
+}
 
-	for (i = 0; enclist[i].name; i++)
-		if (pg_strcasecmp(enclist[i].name, name) == 0)
-			return &enclist[i].enc;
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+	return srclen << 1;
+}
 
-	return NULL;
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+	return srclen >> 1;
 }
diff --git a/src/include/common/encode.h b/src/include/common/encode.h
new file mode 100644
index 0000000..4e4c859
--- /dev/null
+++ b/src/include/common/encode.h
@@ -0,0 +1,30 @@
+/*
+ *	encode.h
+ *		Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ *	Portions Copyright (c) 2001-2015, PostgreSQL Global Development Group
+ *
+ *	src/include/common/encode.h
+ */
+#ifndef COMMON_ENCODE_H
+#define COMMON_ENCODE_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif   /* COMMON_ENCODE_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index fec81ea..0f95830 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -105,8 +105,8 @@ sub mkvcbuild
 	}
 
 	our @pgcommonallfiles = qw(
-	  exec.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
-	  sha1.c string.c username.c wait_error.c);
+	  encode.c exec.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c
+	  rmtree.c sha1.c string.c username.c wait_error.c);
 
 	our @pgcommonfrontendfiles = (
 		@pgcommonallfiles, qw(fe_memutils.c
-- 
2.5.0

0006-SCRAM-authentication.patchtext/x-diff; charset=US-ASCII; name=0006-SCRAM-authentication.patchDownload
From b834bcb637871d6737bd410ef99753796aeb3a6f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 18 Aug 2015 13:29:50 +0900
Subject: [PATCH 6/6] SCRAM authentication

---
 contrib/passwordcheck/passwordcheck.c   |   4 +
 doc/src/sgml/catalogs.sgml              |   3 +-
 doc/src/sgml/config.sgml                |   3 +-
 doc/src/sgml/protocol.sgml              | 148 ++++++-
 src/backend/catalog/system_views.sql    |   1 +
 src/backend/commands/user.c             |  47 ++-
 src/backend/libpq/Makefile              |   2 +-
 src/backend/libpq/auth-scram.c          | 682 ++++++++++++++++++++++++++++++++
 src/backend/libpq/auth.c                | 117 ++++++
 src/backend/libpq/crypt.c               |   4 +-
 src/backend/libpq/hba.c                 |  13 +
 src/backend/libpq/pg_hba.conf.sample    |   2 +-
 src/backend/parser/gram.y               |   4 +
 src/backend/postmaster/postmaster.c     |   1 +
 src/backend/utils/adt/varlena.c         |   1 +
 src/backend/utils/misc/guc.c            |   3 +-
 src/bin/pg_dump/pg_dumpall.c            |   2 +
 src/common/Makefile                     |   3 +-
 src/common/scram-common.c               | 170 ++++++++
 src/include/catalog/pg_auth_verifiers.h |   1 +
 src/include/common/scram-common.h       |  45 +++
 src/include/libpq/auth.h                |   5 +
 src/include/libpq/crypt.h               |   1 +
 src/include/libpq/hba.h                 |   1 +
 src/include/libpq/libpq-be.h            |   3 +-
 src/include/libpq/pqcomm.h              |   2 +
 src/include/libpq/scram.h               |  27 ++
 src/include/utils/builtins.h            |   2 -
 src/interfaces/libpq/.gitignore         |   3 +
 src/interfaces/libpq/Makefile           |   7 +-
 src/interfaces/libpq/fe-auth-scram.c    | 386 ++++++++++++++++++
 src/interfaces/libpq/fe-auth.c          |  96 +++++
 src/interfaces/libpq/fe-auth.h          |   8 +
 src/interfaces/libpq/fe-connect.c       |  51 +++
 src/interfaces/libpq/libpq-int.h        |   5 +
 src/test/regress/expected/password.out  |   7 +-
 src/test/regress/expected/rules.out     |   1 +
 src/test/regress/sql/password.sql       |   3 +
 38 files changed, 1829 insertions(+), 35 deletions(-)
 create mode 100644 src/backend/libpq/auth-scram.c
 create mode 100644 src/common/scram-common.c
 create mode 100644 src/include/common/scram-common.h
 create mode 100644 src/include/libpq/scram.h
 create mode 100644 src/interfaces/libpq/fe-auth-scram.c

diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 5ee38ed..b02934a8 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -135,6 +135,10 @@ check_password(const char *username,
 #endif
 				break;
 
+			case AUTH_VERIFIER_SCRAM:
+				/* unfortunately not much can be done here */
+				break;
+
 			default:
 				elog(ERROR, "unrecognized password type: %d", spec->veriftype);
 				break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 13cc7cb..1227ada 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1522,7 +1522,8 @@
       <entry><type>char</type></entry>
       <entry>
        <literal>p</> = plain format,
-       <literal>m</> = MD5-encrypted
+       <literal>m</> = MD5-encrypted,
+       <literal>s</> = SCRAM-SHA1-encrypted
       </entry>
      </row>
 
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5ae27f8..dbcb985 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1171,7 +1171,8 @@ include_dir 'conf.d'
       <listitem>
        <para>
         Specifies a comma-separated list of password encryption formats.
-        Supported formats are <literal>plain</> and <literal>md5</>.
+        Supported formats are <literal>plain</>,<literal>md5</> and
+        <literal>scram</>.
        </para>
 
        <para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 42e9497..2a9b0e4 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -228,11 +228,11 @@
     The server then sends an appropriate authentication request message,
     to which the frontend must reply with an appropriate authentication
     response message (such as a password).
-    For all authentication methods except GSSAPI and SSPI, there is at most
-    one request and one response. In some methods, no response
+    For all authentication methods except GSSAPI, SSPI and SASL, there is at
+    most one request and one response. In some methods, no response
     at all is needed from the frontend, and so no authentication request
-    occurs. For GSSAPI and SSPI, multiple exchanges of packets may be needed
-    to complete the authentication.
+    occurs. For GSSAPI, SSPI and SASL, multiple exchanges of packets may be
+    needed to complete the authentication.
    </para>
 
    <para>
@@ -366,6 +366,35 @@
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <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.
+       </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.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
 
@@ -2572,6 +2601,115 @@ AuthenticationGSSContinue (B)
 </listitem>
 </varlistentry>
 
+<varlistentry>
+<term>
+AuthenticationSASL (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(10)
+</term>
+<listitem>
+<para>
+                Specifies that SASL authentication is started.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of a SASL authentication mechanism.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+AuthenticationSASLContinue (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32(11)
+</term>
+<listitem>
+<para>
+                Specifies that this message contains SASL-mechanism specific
+                data.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+                SASL data, specific to the SASL mechanism being used.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
 
 <varlistentry>
 <term>
@@ -4334,7 +4472,7 @@ PasswordMessage (F)
 <listitem>
 <para>
                 Identifies the message as a password response. Note that
-                this is also used for GSSAPI and SSPI response messages
+                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).
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 15001df..d6e130e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -38,6 +38,7 @@ CREATE VIEW pg_shadow AS
                 CASE verimet
                     WHEN 'p' THEN 'plain:' || verival
                     WHEN 'm' THEN 'md5:' || verival
+                    WHEN 's' THEN 'scram:' || verival
                 END AS verifiers
             FROM pg_auth_verifiers
 	    WHERE roleid = pg_authid.oid
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index c6bf9db..5d468ec 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -31,6 +31,7 @@
 #include "commands/seclabel.h"
 #include "commands/user.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
@@ -1591,7 +1592,9 @@ DelRoleMems(const char *rolename, Oid roleid,
 
 /*
  * FlattenPasswordIdentifiers
- * Make list of password verifier types and values consistent with input.
+ * Make list of password verifier types and values consistent with the output
+ * wanted, and adapt the specifier value if possible, informing user in case of
+ * incorrect verifier used.
  */
 static void
 FlattenPasswordIdentifiers(List *verifiers, char *rolname)
@@ -1605,24 +1608,34 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
 		if (spec->value == NULL)
 			continue;
 
-		/*
-		 * Check if given value for an MD5 verifier is adapted and
-		 * do conversion as needed. If an MD5 password is provided but
-		 * that the verifier has a plain format switch type of verifier
-		 * accordingly.
-		 */
-		if (spec->veriftype == AUTH_VERIFIER_MD5 &&
-			!isMD5(spec->value))
+		switch (spec->veriftype)
 		{
-			char encrypted_passwd[MD5_PASSWD_LEN + 1];
-			if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
-								encrypted_passwd))
-				elog(ERROR, "password encryption failed");
-			spec->value = pstrdup(encrypted_passwd);
+			case AUTH_VERIFIER_MD5:
+				if (is_scram_verifier(spec->value))
+					elog(ERROR, "Cannot use SCRAM verifier as MD5 verifier");
+				if (!isMD5(spec->value))
+				{
+					char encrypted_passwd[MD5_PASSWD_LEN + 1];
+					if (!pg_md5_encrypt(spec->value, rolname,
+										strlen(rolname),
+										encrypted_passwd))
+						elog(ERROR, "password encryption failed");
+					spec->value = pstrdup(encrypted_passwd);
+				}
+				break;
+			case AUTH_VERIFIER_PLAIN:
+				if (is_scram_verifier(spec->value))
+					spec->veriftype = AUTH_VERIFIER_SCRAM;
+				else if (isMD5(spec->value))
+					spec->veriftype = AUTH_VERIFIER_MD5;
+				break;
+			case AUTH_VERIFIER_SCRAM:
+				if (isMD5(spec->value))
+					elog(ERROR, "Cannot use MD5 verifier as SCRAM verifier");
+				if (!is_scram_verifier(spec->value))
+					spec->value = scram_build_verifier(rolname, spec->value, 0);
+				break;
 		}
-		else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
-				 isMD5(spec->value))
-			spec->veriftype = AUTH_VERIFIER_MD5;
 	}
 }
 
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..3dd60e1 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
 # be-fsstubs is here for historical reasons, probably belongs elsewhere
 
 OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \
-       pqformat.o pqmq.o pqsignal.o
+       pqformat.o pqmq.o pqsignal.o auth-scram.o
 
 ifeq ($(with_openssl),yes)
 OBJS += be-secure-openssl.o
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
new file mode 100644
index 0000000..0d53348
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,682 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ *	  Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See RFC 5802. Some differences:
+ *
+ * - Username from the authentication exchange is not used. The client
+ *   should send an empty string as the username.
+ *
+ * - Password is not processed with the SASLprep algorithm.
+ *
+ * - Channel binding is not supported.
+ *
+ * The verifier stored in pg_auth_verifiers consists of the salt, iteration
+ * count, StoredKey, and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_authid.h"
+#include "common/encode.h"
+#include "common/scram-common.h"
+#include "common/sha1.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		SALT_SENT,
+		FINISHED
+	} state;
+
+	const char *username;	/* username from startup packet */
+	char	   *salt;		/* base64-encoded */
+	int			iterations;
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+
+	/* These come from the client-first message */
+	char	   *client_first_message_bare;
+	char	   *client_username;
+	char	   *client_authzid;
+	char	   *client_nonce;
+
+	/* These come from the client-final message */
+	char	   *client_final_message_without_proof;
+	char	   *client_final_nonce;
+	char		ClientProof[SCRAM_KEY_LEN];
+
+	char	   *server_first_message;
+	char	   *server_nonce;		/* base64-encoded */
+	char	   *server_signature;
+
+} scram_state;
+
+static void read_client_first_message(scram_state *state, char *input);
+static void read_client_final_message(scram_state *state, char *input);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+static bool parse_scram_verifier(const char *verifier, char **salt,
+				 int *iterations, char **stored_key, char **server_key);
+
+static void generate_nonce(char *out, int len);
+
+/*
+ * Initialize a new SCRAM authentication exchange, with given username and
+ * its stored verifier.
+ */
+void *
+scram_init(const char *username, const char *verifier)
+{
+	scram_state *state;
+	char   *server_key;
+	char   *stored_key;
+	char   *salt;
+	int		iterations;
+
+
+	state = (scram_state *) palloc0(sizeof(scram_state));
+	state->state = INIT;
+	state->username = username;
+
+	if (!parse_scram_verifier(verifier, &salt, &iterations,
+							  &stored_key, &server_key))
+	{
+		elog(ERROR, "invalid SCRAM verifier");
+		return NULL;
+	}
+
+	state->salt = salt;
+	state->iterations = iterations;
+	memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+	memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+	pfree(stored_key);
+	pfree(server_key);
+	return state;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ */
+int
+scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen)
+{
+	scram_state *state = (scram_state *) opaq;
+	int			result;
+
+	*output = NULL;
+	*outputlen = 0;
+
+	if (inputlen > 0)
+		elog(DEBUG4, "got SCRAM message: %s", input);
+
+	switch (state->state)
+	{
+		case INIT:
+			/* receive username and client nonce, send challenge */
+			read_client_first_message(state, input);
+			*output = build_server_first_message(state);
+			*outputlen = strlen(*output);
+			result = SASL_EXCHANGE_CONTINUE;
+			state->state = SALT_SENT;
+			break;
+
+		case SALT_SENT:
+			/* receive response to challenge and verify it */
+			read_client_final_message(state, input);
+			if (verify_final_nonce(state) && verify_client_proof(state))
+			{
+				*output = build_server_final_message(state);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_SUCCESS;
+			}
+			else
+			{
+				result = SASL_EXCHANGE_FAILURE;
+			}
+			state->state = FINISHED;
+			break;
+
+		default:
+			elog(ERROR, "invalid SCRAM exchange state");
+			result = 0;
+	}
+
+	return result;
+}
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolverifiers.
+ *
+ * If iterations is 0, default number of iterations is used;
+ */
+char *
+scram_build_verifier(char *username, char *password, int iterations)
+{
+	uint8		keybuf[SCRAM_KEY_LEN + 1];
+	char		storedkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		serverkey_hex[SCRAM_KEY_LEN * 2 + 1];
+	char		salt[SCRAM_SALT_LEN];
+	char	   *encoded_salt;
+	int			encoded_len;
+
+	if (iterations <= 0)
+		iterations = SCRAM_ITERATIONS_DEFAULT;
+
+	generate_nonce(salt, SCRAM_SALT_LEN);
+
+	encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+	encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+	encoded_salt[encoded_len] = '\0';
+
+	/* Calculate StoredKey, and encode it in hex */
+	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+							iterations, SCRAM_CLIENT_KEY_NAME, keybuf);
+	scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, storedkey_hex);
+	storedkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	/* And same for ServerKey */
+	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN, iterations,
+							SCRAM_SERVER_KEY_NAME, keybuf);
+	(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
+	serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+	return psprintf("%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+	return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+					 char **stored_key, char **server_key)
+{
+	char	   *salt_res = NULL;
+	char	   *stored_key_res = NULL;
+	char	   *server_key_res = NULL;
+	char	   *v;
+	char	   *p;
+	int			iterations_res;
+
+	/*
+	 * The verifier is of form:
+	 *
+	 * salt:iterations:storedkey:serverkey
+	 */
+	v = pstrdup(verifier);
+
+	/* salt */
+	if ((p = strtok(v, ":")) == NULL)
+		goto invalid_verifier;
+	salt_res = pstrdup(p);
+
+	/* iterations */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	errno = 0;
+	iterations_res = strtol(p, &p, 10);
+	if (*p || errno != 0)
+		goto invalid_verifier;
+
+	/* storedkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+
+	stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+	hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+	/* serverkey */
+	if ((p = strtok(NULL, ":")) == NULL)
+		goto invalid_verifier;
+	if (strlen(p) != SCRAM_KEY_LEN * 2)
+		goto invalid_verifier;
+	server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+	hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+	if (iterations)
+		*iterations = iterations_res;
+	if (salt)
+		*salt = salt_res;
+	else
+		pfree(salt_res);
+	if (stored_key)
+		*stored_key = stored_key_res;
+	else
+		pfree(stored_key_res);
+	if (server_key)
+		*server_key = server_key_res;
+	else
+		pfree(server_key_res);
+	pfree(v);
+	return true;
+
+invalid_verifier:
+	if (salt_res)
+		pfree(salt_res);
+	if (stored_key_res)
+		pfree(stored_key_res);
+	if (server_key_res)
+		pfree(server_key_res);
+	pfree(v);
+	return false;
+}
+
+static char *
+read_attr_value(char **input, char attr)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		elog(ERROR, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+read_any_attr(char **input, char *attr_p)
+{
+	char *begin = *input;
+	char *end;
+	char attr = *begin;
+
+	if (!((attr >= 'A' && attr <= 'Z') ||
+		  (attr >= 'a' && attr <= 'z')))
+		elog(ERROR, "malformed SCRAM message (invalid attribute char)");
+	if (attr_p)
+		*attr_p = attr;
+	begin++;
+
+	if (*begin != '=')
+		elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static void
+read_client_first_message(scram_state *state, char *input)
+{
+	input = pstrdup(input);
+
+	/*
+	 * saslname        = 1*(value-safe-char / "=2C" / "=3D")
+	 *              ;; Conforms to <value>.
+	 *
+	 * authzid         = "a=" saslname
+	 *              ;; Protocol specific.
+	 *
+	 * username        = "n=" saslname
+	 *               ;; Usernames are prepared using SASLprep.
+	 *
+	 * gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
+	 *               ;; "n" -> client doesn't support channel binding.
+	 *               ;; "y" -> client does support channel binding
+	 *               ;;        but thinks the server does not.
+	 *               ;; "p" -> client requires channel binding.
+	 *               ;; The selected channel binding follows "p=".
+	 *
+	 * gs2-header      = gs2-cbind-flag "," [ authzid ] ","
+	 *               ;; GS2 header for SCRAM
+	 *               ;; (the actual GS2 header includes an optional
+	 *               ;; flag to indicate that the GSS mechanism is not
+	 *               ;; "standard", but since SCRAM is "standard", we
+	 *               ;; don't include that flag).
+	 *
+	 *   client-first-message-bare =
+	 *               [reserved-mext ","]
+	 *               username "," nonce ["," extensions]
+	 *
+	 *   client-first-message =
+	 *                gs2-header client-first-message-bare
+	 *
+	 *
+	 * For example:
+	 * n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+	 */
+
+	/* read gs2-cbind-flag */
+	switch (*input)
+	{
+		case 'n':
+			/* client does not support channel binding */
+			input++;
+			break;
+		case 'y':
+			/* client supports channel binding, but we're not doing it today */
+			input++;
+			break;
+		case 'p':
+			/* client requires channel binding. We don't support it */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("channel binding not supported")));
+	}
+
+	/* any mandatory extensions would go here. */
+	if (*input != ',')
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("mandatory extension %c not supported", *input)));
+	input++;
+
+	/* read optional authzid (authorization identity) */
+	if (*input != ',')
+		state->client_authzid = read_attr_value(&input, 'a');
+	else
+		input++;
+
+	state->client_first_message_bare = pstrdup(input);
+
+	/* read username */
+	state->client_username = read_attr_value(&input, 'n');
+
+	/* read nonce */
+	state->client_nonce = read_attr_value(&input, 'r');
+
+	/*
+	 * There can be any number of optional extensions after this. We don't
+	 * support any extensions, so ignore them.
+	 */
+	while (*input != '\0')
+		read_any_attr(&input, NULL);
+
+	/* success! */
+}
+
+static bool
+verify_final_nonce(scram_state *state)
+{
+	int			client_nonce_len = strlen(state->client_nonce);
+	int			server_nonce_len = strlen(state->server_nonce);
+	int			final_nonce_len = strlen(state->client_final_nonce);
+
+	if (final_nonce_len != client_nonce_len + server_nonce_len)
+		return false;
+	if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0)
+		return false;
+	if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0)
+		return false;
+
+	return true;
+}
+
+static bool
+verify_client_proof(scram_state *state)
+{
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		client_StoredKey[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+	int			i;
+
+	/* calculate ClientSignature */
+	scram_HMAC_init(&ctx, state->StoredKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+	elog(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+	elog(DEBUG4, "AuthMessage: %s,%s,%s", state->client_first_message_bare,
+		 state->server_first_message, state->client_final_message_without_proof);
+
+	/* Extract the ClientKey that the client calculated from the proof */
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+
+	/* Hash it one more time, and compare with StoredKey */
+	scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey);
+	elog(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+	elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+	elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+	if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+		return false;
+
+	return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+	char		nonce[SCRAM_NONCE_LEN];
+	int			encoded_len;
+
+	/*
+	 * server-first-message =
+	 *                   [reserved-mext ","] nonce "," salt ","
+	 *                   iteration-count ["," extensions]
+	 *
+	 *   nonce           = "r=" c-nonce [s-nonce]
+	 *               ;; Second part provided by server.
+	 *
+	 * c-nonce         = printable
+	 *
+	 * s-nonce         = printable
+	 *
+	 * salt            = "s=" base64
+	 *
+	 * iteration-count = "i=" posit-number
+	 *              ;; A positive number.
+	 *
+	 * Example:
+	 *
+	 * r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+	 */
+	generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+	state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+	encoded_len = b64_encode(nonce, SCRAM_NONCE_LEN, state->server_nonce);
+
+	state->server_nonce[encoded_len] = '\0';
+	state->server_first_message =
+		psprintf("r=%s%s,s=%s,i=%u",
+				 state->client_nonce, state->server_nonce,
+				 state->salt, state->iterations);
+
+	return state->server_first_message;
+}
+
+static void
+read_client_final_message(scram_state *state, char *input)
+{
+	char		attr;
+	char	   *channel_binding;
+	char	   *value;
+	char	   *begin, *proof;
+	char	   *p;
+	char	   *client_proof;
+
+	begin = p = pstrdup(input);
+
+	/*
+	 *
+	 * cbind-input   = gs2-header [ cbind-data ]
+	 *               ;; cbind-data MUST be present for
+	 *               ;; gs2-cbind-flag of "p" and MUST be absent
+	 *               ;; for "y" or "n".
+	 *
+	 * channel-binding = "c=" base64
+	 *               ;; base64 encoding of cbind-input.
+	 *
+	 * proof           = "p=" base64
+	 *
+	 * client-final-message-without-proof =
+	 *               channel-binding "," nonce ["," extensions]
+	 *
+	 * client-final-message =
+	 *              client-final-message-without-proof "," proof
+	 */
+	channel_binding = read_attr_value(&p, 'c');
+	if (strcmp(channel_binding, "biws") != 0)
+		elog(ERROR, "invalid channel binding input");
+	state->client_final_nonce = read_attr_value(&p, 'r');
+
+	/* ignore optional extensions */
+	do
+	{
+		proof = p - 1;
+		value = read_any_attr(&p, &attr);
+	} while (attr != 'p');
+
+	client_proof = palloc(b64_dec_len(value, strlen(value)));
+	if (b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+		elog(ERROR, "invalid ClientProof");
+	memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+	pfree(client_proof);
+
+	if (*p != '\0')
+		elog(ERROR, "malformed SCRAM message (garbage at end of message %c)", attr);
+
+	state->client_final_message_without_proof = palloc(proof - begin + 1);
+	memcpy(state->client_final_message_without_proof, input, proof - begin);
+	state->client_final_message_without_proof[proof - begin] = '\0';
+
+	/* XXX: check channel_binding field if support is added */
+}
+
+
+static char *
+build_server_final_message(scram_state *state)
+{
+	uint8		ServerSignature[SCRAM_KEY_LEN];
+	char	   *server_signature_base64;
+	int			siglen;
+	scram_HMAC_ctx ctx;
+
+	/* calculate ServerSignature */
+	scram_HMAC_init(&ctx, state->ServerKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ServerSignature, &ctx);
+
+	server_signature_base64 = palloc(b64_enc_len((const char *) ServerSignature,
+												 SCRAM_KEY_LEN) + 1);
+	siglen = b64_encode((const char *) ServerSignature,
+						SCRAM_KEY_LEN, server_signature_base64);
+	server_signature_base64[siglen] = '\0';
+
+	/*
+	 *
+	 * server-error = "e=" server-error-value
+	 *
+	 * server-error-value = "invalid-encoding" /
+	 *           "extensions-not-supported" /  ; unrecognized 'm' value
+	 *            "invalid-proof" /
+	 *            "channel-bindings-dont-match" /
+	 *            "server-does-support-channel-binding" /
+	 *              ; server does not support channel binding
+	 *            "channel-binding-not-supported" /
+	 *            "unsupported-channel-binding-type" /
+	 *            "unknown-user" /
+	 *            "invalid-username-encoding" /
+	 *              ; invalid username encoding (invalid UTF-8 or
+	 *              ; SASLprep failed)
+	 *            "no-resources" /
+	 *            "other-error" /
+	 *            server-error-value-ext
+	 *     ; Unrecognized errors should be treated as "other-error".
+	 *     ; In order to prevent information disclosure, the server
+	 *     ; may substitute the real reason with "other-error".
+	 *
+	 * server-error-value-ext = value
+	 *     ; Additional error reasons added by extensions
+	 *     ; to this document.
+	 *
+	 * verifier        = "v=" base64
+	 *               ;; base-64 encoded ServerSignature.
+	 *
+	 * server-final-message = (server-error / verifier)
+	 *                ["," extensions]
+	 */
+	return psprintf("v=%s", server_signature_base64);
+}
+
+static void
+generate_nonce(char *result, int len)
+{
+	/* Use the salt generated for SASL authentication */
+	memset(result, 0, len);
+	memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 1b28722..5472e08 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,15 +21,19 @@
 #include <arpa/inet.h>
 #include <unistd.h>
 
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
 #include "libpq/auth.h"
 #include "libpq/crypt.h"
 #include "libpq/ip.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
+#include "utils/syscache.h"
 
 
 /*----------------------------------------------------------------
@@ -185,6 +189,12 @@ static int	CheckRADIUSAuth(Port *port);
 
 
 /*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
  * Global authentication functions
  *----------------------------------------------------------------
  */
@@ -246,6 +256,7 @@ auth_failed(Port *port, int status, char *logdetail)
 			break;
 		case uaPassword:
 		case uaMD5:
+		case uaSASL:
 			errstr = gettext_noop("password authentication failed for user \"%s\"");
 			/* We use it to indicate if a .pgpass password failed. */
 			errcode_return = ERRCODE_INVALID_PASSWORD;
@@ -523,6 +534,10 @@ ClientAuthentication(Port *port)
 			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
+		case uaSASL:
+			status = CheckSASLAuth(port, &logdetail);
+			break;
+
 		case uaPAM:
 #ifdef USE_PAM
 			status = CheckPAMAuth(port, port->user_name, "");
@@ -691,6 +706,108 @@ recv_and_check_password_packet(Port *port, char **logdetail)
 	return result;
 }
 
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+	int			mtype;
+	StringInfoData buf;
+	void	   *scram_opaq;
+	char	   *verifier;
+	char	   *output = NULL;
+	int			outputlen = 0;
+	int			result;
+	HeapTuple	roleTup;
+
+	/*
+	 * SASL auth is not supported for protocol versions before 3, because it
+	 * relies on the overall message length word to determine the SASL payload
+	 * size in AuthenticationSASLContinue and PasswordMessage messages. (We
+	 * used to have a hard rule that protocol messages must be parsable
+	 * without relying on the length word, but we hardly care about protocol
+	 * version or older anymore.)
+	 *
+	 * FIXME: the FE/BE docs need to updated.
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SASL authentication is not supported in protocol version 2")));
+
+	/* Get role info from pg_authid */
+	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(port->user_name));
+	if (!HeapTupleIsValid(roleTup))
+		return STATUS_ERROR;
+
+	/* lookup verifier */
+	verifier = get_role_verifier(HeapTupleGetOid(roleTup), AUTH_VERIFIER_SCRAM);
+	if (verifier == NULL)
+	{
+		ReleaseSysCache(roleTup);
+		return STATUS_ERROR;
+	}
+
+	ReleaseSysCache(roleTup);
+
+	sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA1_NAME,
+					strlen(SCRAM_SHA1_NAME) + 1);
+
+	scram_opaq = scram_init(port->user_name, verifier);
+
+	/*
+	 * Loop through SASL message exchange. This exchange can consist of
+	 * multiple messags sent in both directions. First message is always from
+	 * the client. All messages from client to server are password packets
+	 * (type 'p').
+	 */
+	do
+	{
+		pq_startmsgread();
+		mtype = pq_getbyte();
+		if (mtype != 'p')
+		{
+			/* Only log error if client didn't disconnect. */
+			if (mtype != EOF)
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("expected SASL response, got message type %d",
+								mtype)));
+			return STATUS_ERROR;
+		}
+
+		/* Get the actual SASL token */
+		initStringInfo(&buf);
+		if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
+		{
+			/* EOF - pq_getmessage already logged error */
+			pfree(buf.data);
+			return STATUS_ERROR;
+		}
+
+		elog(DEBUG4, "Processing received SASL token of length %d", buf.len);
+
+		result = scram_exchange(scram_opaq, buf.data, buf.len,
+								&output, &outputlen);
+
+		/* input buffer no longer used */
+		pfree(buf.data);
+
+		if (outputlen > 0)
+		{
+			/*
+			 * Negotiation generated data to be sent to the client.
+			 */
+			elog(DEBUG4, "sending SASL response token of length %u", outputlen);
+
+			sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+		}
+	} while (result == SASL_EXCHANGE_CONTINUE);
+
+	return (result == SASL_EXCHANGE_SUCCESS) ? STATUS_OK : STATUS_ERROR;
+}
 
 
 /*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index f04d17a..4140dd8 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -33,10 +33,10 @@
 #include "utils/timestamp.h"
 
 /*
- * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * Get verifier stored in pg_auth_verifiers, for given authentication
  * method.
  */
-static char *
+char *
 get_role_verifier(Oid roleid, const char method)
 {
 	HeapTuple	tuple;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 23c8b5d..3e5c50b 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
 		}
 		parsedline->auth_method = uaMD5;
 	}
+	else if (strcmp(token->string, "scram") == 0)
+	{
+		if (Db_user_namespace)
+		{
+			ereport(LOG,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("SCRAM authentication is not supported when \"db_user_namespace\" is enabled"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			return NULL;
+		}
+		parsedline->auth_method = uaSASL;
+	}
 	else if (strcmp(token->string, "pam") == 0)
 #ifdef USE_PAM
 		parsedline->auth_method = uaPAM;
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index 86a89ed..dc3ce2f 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,7 +42,7 @@
 # or "samenet" to match any address in any subnet that the server is
 # directly connected to.
 #
-# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
+# METHOD can be "trust", "reject", "md5", "password", "scram", "gss", "sspi",
 # "ident", "peer", "pam", "ldap", "radius" or "cert".  Note that
 # "password" sends passwords in clear text; "md5" is preferred since
 # it sends encrypted passwords.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2e67c96..008166d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -939,6 +939,8 @@ AuthVerifierSpec:
 						type = AUTH_VERIFIER_MD5;
 					else if (strcmp($1, "plain") == 0)
 						type = AUTH_VERIFIER_PLAIN;
+					else if (strcmp($1, "scram") == 0)
+						type = AUTH_VERIFIER_SCRAM;
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
@@ -968,6 +970,8 @@ AlterOptRoleElem:
 							veriftype = AUTH_VERIFIER_MD5;
 						else if (strcmp(meth_name, "plain") == 0)
 							veriftype = AUTH_VERIFIER_PLAIN;
+						else if (strcmp(meth_name, "scram") == 0)
+							veriftype = AUTH_VERIFIER_SCRAM;
 						else
 							Assert(false);	/* should not happen */
 						n = (AuthVerifierSpec *)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index c2d5b0e..0d5ee09 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2283,6 +2283,7 @@ ConnCreate(int serverFd)
 	 * all backends would end up using the same salt...
 	 */
 	RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+	RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
 
 	/*
 	 * Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 2fbbf54..4af2357 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
 #include "access/tuptoaster.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
+#include "common/encode.h"
 #include "lib/hyperloglog.h"
 #include "libpq/md5.h"
 #include "libpq/pqformat.h"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 082108e..c45ca09 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10142,7 +10142,8 @@ check_password_encryption(char **newval, void **extra, GucSource source)
 		char	   *encryption_name = (char *) lfirst(l);
 
 		if (strcmp(encryption_name, "md5") != 0 &&
-			strcmp(encryption_name, "plain") != 0)
+			strcmp(encryption_name, "plain") != 0 &&
+			strcmp(encryption_name, "scram") != 0)
 		{
 			pfree(rawstring);
 			list_free(elemlist);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index b4d09e5..f5c0b1a 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -932,6 +932,8 @@ dumpRoles(PGconn *conn)
 				appendPQExpBufferStr(buf, "md5 = ");
 			else if (verifier_meth == 'p')
 				appendPQExpBufferStr(buf, "plain = ");
+			else if (verifier_meth == 's')
+				appendPQExpBufferStr(buf, "scram = ");
 			appendStringLiteralConn(buf, verifier_value, conn);
 		}
 		if (current_user != NULL)
diff --git a/src/common/Makefile b/src/common/Makefile
index 4d26f6e..b93c7bc 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,8 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = encode.o exec.o pg_lzcompress.o pgfnames.o psprintf.o \
-	relpath.o rmtree.o sha1.o string.o username.o wait_error.o
+	relpath.o rmtree.o scram-common.o sha1.o string.o username.o \
+	wait_error.o
 
 OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..a17387e
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,170 @@
+/*-------------------------------------------------------------------------
+ * scram-common.c
+ *		Shared frontend/backend code for SCRAM authentication
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the Salted Challenge Response Authentication
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-1.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+	uint8		k_ipad[SHA1_HMAC_B];
+	int			i;
+	uint8		keybuf[SHA1_RESULTLEN];
+
+	/*
+	 * If the key is longer than the block size (64 bytes for SHA-1),
+	 * pass it through SHA-1 once to shrink it down
+	 */
+	if (keylen > SHA1_HMAC_B)
+	{
+		SHA1_CTX	sha1_ctx;
+
+		SHA1Init(&sha1_ctx);
+		SHA1Update(&sha1_ctx, key, keylen);
+		SHA1Final(keybuf, &sha1_ctx);
+		key = keybuf;
+		keylen = SHA1_RESULTLEN;
+	}
+
+	memset(k_ipad, 0x36, SHA1_HMAC_B);
+	memset(ctx->k_opad, 0x5C, SHA1_HMAC_B);
+	for (i = 0; i < keylen; i++)
+	{
+		k_ipad[i] ^= key[i];
+		ctx->k_opad[i] ^= key[i];
+	}
+
+	/* tmp = H(K XOR ipad, text) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, k_ipad, SHA1_HMAC_B);
+}
+
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+	SHA1Update(&ctx->sha1ctx, (const uint8 *) str, slen);
+}
+
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+	uint8		h[SHA1_RESULTLEN];
+
+	SHA1Final(h, &ctx->sha1ctx);
+
+	/* H(K XOR opad, tmp) */
+	SHA1Init(&ctx->sha1ctx);
+	SHA1Update(&ctx->sha1ctx, ctx->k_opad, SHA1_HMAC_B);
+	SHA1Update(&ctx->sha1ctx, h, SHA1_RESULTLEN);
+	SHA1Final(result, &ctx->sha1ctx);
+}
+
+static void
+scram_Hi(const char *str, const char *salt, int saltlen, int iterations, uint8 *result)
+{
+	int			str_len = strlen(str);
+	uint32		one = htonl(1);
+	int			i, j;
+	uint8		Ui[SCRAM_KEY_LEN];
+	uint8		Ui_prev[SCRAM_KEY_LEN];
+	scram_HMAC_ctx hmac_ctx;
+
+	/* First iteration */
+	scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+	scram_HMAC_update(&hmac_ctx, salt, saltlen);
+	scram_HMAC_update(&hmac_ctx, (char *) &one, sizeof(uint32));
+	scram_HMAC_final(Ui_prev, &hmac_ctx);
+	memcpy(result, Ui_prev, SCRAM_KEY_LEN);
+
+	/* Subsequent iterations */
+	for (i = 2; i <= iterations; i++)
+	{
+		scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+		scram_HMAC_update(&hmac_ctx, (const char *) Ui_prev, SCRAM_KEY_LEN);
+		scram_HMAC_final(Ui, &hmac_ctx);
+		for (j = 0; j < SCRAM_KEY_LEN; j++)
+			result[j] ^= Ui[j];
+		memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
+	}
+}
+
+
+/*
+ * Calculate SHA-1 hash for a NULL-terminated string. (The NULL terminator is
+ * not included in the hash).
+ */
+void
+scram_H(const uint8 *input, int len, uint8 *result)
+{
+	SHA1_CTX	ctx;
+
+	SHA1Init(&ctx);
+	SHA1Update(&ctx, input, len);
+	SHA1Final(result, &ctx);
+}
+
+static void
+scram_Normalize(const char *password, char *result)
+{
+	/*
+	 * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+	 * it is required that the password is encoded in UTF-8, something that is
+	 * not guaranteed in this protocol. We may want to revisit this
+	 * normalization function once encoding functions are available as well
+	 * in the frontend in order to be able to encode properly this string,
+	 * and then apply SASLprep on it.
+	 */
+	memcpy(result, password, strlen(password) + 1);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+					 uint8 *result)
+{
+	char		*pwbuf;
+
+	pwbuf = (char *) malloc(strlen(password) + 1);
+	scram_Normalize(password, pwbuf);
+	scram_Hi(pwbuf, salt, saltlen, iterations, result);
+	free(pwbuf);
+}
+
+/*
+ * Calculate ClientKey or ServerKey.
+ */
+void
+scram_ClientOrServerKey(const char *password,
+						const char *salt, int saltlen, int iterations,
+						const char *keystr, uint8 *result)
+{
+	uint8		keybuf[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+
+	scram_SaltedPassword(password, salt, saltlen, iterations, keybuf);
+	scram_HMAC_init(&ctx, keybuf, 20);
+	scram_HMAC_update(&ctx, keystr, strlen(keystr));
+	scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
index daef049..5b72e40 100644
--- a/src/include/catalog/pg_auth_verifiers.h
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -58,5 +58,6 @@ typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
 
 #define AUTH_VERIFIER_PLAIN	'p'		/* plain verifier */
 #define AUTH_VERIFIER_MD5	'm'		/* md5 verifier */
+#define AUTH_VERIFIER_SCRAM	's'		/* SCRAM verifier */
 
 #endif   /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..3d99bc8
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ *		Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/relpath.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCRAM_COMMON_H
+#define SCRAM_COMMON_H
+
+#include "common/sha1.h"
+
+#define SCRAM_KEY_LEN	SHA1_RESULTLEN
+#define SHA1_HMAC_B		64
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN	10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN	10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT	4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+typedef struct
+{
+	SHA1_CTX	sha1ctx;
+	uint8		k_opad[SHA1_HMAC_B];
+} scram_HMAC_ctx;
+
+extern void scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen);
+extern void scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen);
+extern void scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx);
+
+extern void scram_H(const uint8 *str, int len, uint8 *result);
+extern void scram_ClientOrServerKey(const char *password, const char *salt, int saltlen, int iterations, const char *keystr, uint8 *result);
+
+#endif
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 80f26a8..4469565 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -22,6 +22,11 @@ extern char *pg_krb_realm;
 
 extern void ClientAuthentication(Port *port);
 
+/* Return codes for SASL authentication functions */
+#define	SASL_EXCHANGE_CONTINUE		0
+#define	SASL_EXCHANGE_SUCCESS		1
+#define	SASL_EXCHANGE_FAILURE		2
+
 /* Hook for plugins to get control in ClientAuthentication() */
 typedef void (*ClientAuthentication_hook_type) (Port *, int);
 extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index dfab8f3..1dcf955 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,7 @@
 
 #include "libpq/libpq-be.h"
 
+extern char *get_role_verifier(Oid roleid, char method);
 extern int md5_crypt_verify(const Port *port, const char *role,
 				 char *client_pass, char **logdetail);
 
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 68a953a..a73d2f9 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -24,6 +24,7 @@ typedef enum UserAuth
 	uaIdent,
 	uaPassword,
 	uaMD5,
+	uaSASL,
 	uaGSS,
 	uaSSPI,
 	uaPAM,
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index caaa8b5..80fd226 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,8 @@ typedef struct Port
 	 * Information that needs to be held during the authentication cycle.
 	 */
 	HbaLine    *hba;
-	char		md5Salt[4];		/* Password salt */
+	char		md5Salt[4];		/* MD5 password salt */
+	char		SASLSalt[10];	/* SASL password salt */
 
 	/*
 	 * Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 639bf72..462a5dd 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -172,6 +172,8 @@ 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 */
 
 typedef uint32 AuthRequest;
 
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
new file mode 100644
index 0000000..b9af4c4
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ *	  Interface to libpq/scram.c
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/scram.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SCRAM_H
+#define PG_SCRAM_H
+
+/* Name of SCRAM-SHA1 per IANA */
+#define SCRAM_SHA1_NAME "SCRAM-SHA-1"
+
+extern void *scram_init(const char *username, const char *verifier);
+extern int scram_exchange(void *opaq, char *input, int inputlen,
+			   char **output, int *outputlen);
+extern char *scram_build_verifier(char *username, char *password,
+					 int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fc1679e..f825f87 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -155,8 +155,6 @@ extern int	errdomainconstraint(Oid datatypeOid, const char *conname);
 /* encode.c */
 extern Datum binary_encode(PG_FUNCTION_ARGS);
 extern Datum binary_decode(PG_FUNCTION_ARGS);
-extern unsigned hex_encode(const char *src, unsigned len, char *dst);
-extern unsigned hex_decode(const char *src, unsigned len, char *dst);
 
 /* enum.c */
 extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..225cfe4 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
 /exports.list
 /chklocale.c
 /crypt.c
+/encode.c
 /getaddrinfo.c
 /getpeereid.c
 /inet_aton.c
@@ -9,6 +10,8 @@
 /open.c
 /pgstrcasecmp.c
 /pqsignal.c
+/scram-common.c
+/sha1.c
 /snprintf.c
 /strerror.c
 /strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c2105f1..65e8906 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,7 @@ LIBS := $(LIBS:-lpgport=)
 
 # We can't use Makefile variables here because the MSVC build system scrapes
 # OBJS from this file.
-OBJS=	fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+OBJS=	fe-auth.o fe-auth-scram.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
 	fe-protocol2.o fe-protocol3.o pqexpbuffer.o fe-secure.o \
 	libpq-events.o
 # libpgport C files we always use
@@ -43,6 +43,8 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
 OBJS += ip.o md5.o
 # utils/mb
 OBJS += encnames.o wchar.o
+# common/
+OBJS += encode.o scram-common.o sha1.o
 
 ifeq ($(with_openssl),yes)
 OBJS += fe-secure-openssl.o
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
 encnames.c wchar.c: % : $(backend_src)/utils/mb/%
 	rm -f $@ && $(LN_S) $< .
 
+encode.c scram-common.c sha1.c: % : $(top_srcdir)/src/common/%
+	rm -f $@ && $(LN_S) $< .
+
 
 distprep: libpq-dist.rc
 
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
new file mode 100644
index 0000000..ebbd1db
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,386 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ *	   The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "common/encode.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+typedef struct
+{
+	enum
+	{
+		INIT,
+		NONCE_SENT,
+		PROOF_SENT,
+		FINISHED
+	} state;
+
+	const char *username;
+	const char *password;
+
+	char	   *client_first_message_bare;
+	char	   *client_final_message_without_proof;
+
+	/* These come from the server-first message */
+	char	   *server_first_message;
+	char	   *salt;
+	int			saltlen;
+	int			iterations;
+	char	   *server_nonce;
+
+	/* These come from the server-final message */
+	char	   *server_final_message;
+	char		ServerProof[SCRAM_KEY_LEN];
+} fe_scram_state;
+
+static bool read_server_first_message(fe_scram_state *state,
+									  char *input,
+									  PQExpBuffer errormessage);
+static bool read_server_final_message(fe_scram_state *state,
+									  char *input,
+									  PQExpBuffer errormessage);
+static char *build_client_first_message(fe_scram_state *state);
+static char *build_client_final_message(fe_scram_state *state);
+static bool verify_server_proof(fe_scram_state *state);
+static void generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state,
+					const char *client_final_message_without_proof,
+					uint8 *result);
+
+void *
+pg_fe_scram_init(const char *username, const char *password)
+{
+	fe_scram_state *state;
+
+	state = (fe_scram_state *) malloc(sizeof(fe_scram_state));
+	if (!state)
+		return NULL;
+	memset(state, 0, sizeof(fe_scram_state));
+	state->state = INIT;
+	state->username = username;
+	state->password = password;
+
+	return state;
+}
+
+void
+pg_fe_scram_free(void *opaq)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	/* client messages */
+	if (state->client_first_message_bare)
+		free(state->client_first_message_bare);
+	if (state->client_final_message_without_proof)
+		free(state->client_final_message_without_proof);
+
+	/* first message from server */
+	if (state->server_first_message)
+		free(state->server_first_message);
+	if (state->salt)
+		free(state->salt);
+	if (state->server_nonce)
+		free(state->server_nonce);
+
+	/* final message from server */
+	if (state->server_final_message)
+		free(state->server_final_message);
+
+	free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+void
+pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage)
+{
+	fe_scram_state *state = (fe_scram_state *) opaq;
+
+	*done = false;
+	*success = false;
+	*output = NULL;
+	*outputlen = 0;
+
+	switch (state->state)
+	{
+		case INIT:
+			/* send client nonce */
+			*output = build_client_first_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = NONCE_SENT;
+			break;
+
+		case NONCE_SENT:
+			/* receive salt and server nonce, send response */
+			read_server_first_message(state, input, errorMessage);
+			*output = build_client_final_message(state);
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = PROOF_SENT;
+			break;
+
+		case PROOF_SENT:
+			/* receive server proof, and verify it */
+			read_server_final_message(state, input, errorMessage);
+			*success = verify_server_proof(state);
+			*done = true;
+			state->state = FINISHED;
+			break;
+
+		default:
+			/* shouldn't happen */
+			*done = true;
+			*success = false;
+			printfPQExpBuffer(errorMessage, "invalid SCRAM exchange state");
+	}
+}
+
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin != attr)
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (%c expected)", attr);
+	begin++;
+
+	if (*begin != '=')
+		printfPQExpBuffer(errorMessage, "malformed SCRAM message (expected = in attr %c)", attr);
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+	char		nonce[SCRAM_NONCE_LEN + 1];
+	char	   *buf;
+	char		msglen;
+
+	generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+	/* Generate message */
+	msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+	buf = malloc(msglen + 1);
+	snprintf(buf, msglen + 1, "n,,n=%s,r=%s", state->username, nonce);
+
+	state->client_first_message_bare = strdup(buf + 3);
+	if (!state->client_first_message_bare)
+		return NULL;
+
+	return buf;
+}
+
+static bool
+read_server_first_message(fe_scram_state *state,
+						  char *input,
+						  PQExpBuffer errormessage)
+{
+	char	   *iterations_str;
+	char	   *endptr;
+	char	   *encoded_salt;
+
+	state->server_first_message = strdup(input);
+	if (!state->server_first_message)
+		return false;
+
+	/* parse the message */
+	state->server_nonce = strdup(read_attr_value(&input, 'r', errormessage));
+	if (state->server_nonce == NULL)
+		return false;
+
+	encoded_salt = read_attr_value(&input, 's', errormessage);
+	if (encoded_salt == NULL)
+		return false;
+	state->salt = malloc(b64_dec_len(encoded_salt, strlen(encoded_salt)));
+	if (state->salt == NULL)
+		return false;
+	state->saltlen = b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+	if (state->saltlen != SCRAM_SALT_LEN)
+		return false;
+
+	iterations_str = read_attr_value(&input, 'i', errormessage);
+	if (iterations_str == NULL)
+		return false;
+	state->iterations = strtol(iterations_str, &endptr, 10);
+	if (*endptr != '\0')
+		return false;
+
+	if (*input != '\0')
+		return false;
+
+	return true;
+}
+
+static bool
+read_server_final_message(fe_scram_state *state,
+						  char *input,
+						  PQExpBuffer errormessage)
+{
+	char	   *encoded_server_proof;
+	int			server_proof_len;
+
+	state->server_final_message = strdup(input);
+	if (!state->server_final_message)
+		return false;
+
+	/* parse the message */
+	encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+	if (encoded_server_proof == NULL)
+		return false;
+
+	server_proof_len = b64_decode(encoded_server_proof,
+								  strlen(encoded_server_proof),
+								  state->ServerProof);
+	if (server_proof_len != SCRAM_KEY_LEN)
+	{
+		printfPQExpBuffer(errormessage, "invalid ServerProof");
+		return false;
+	}
+
+	if (*input != '\0')
+		return false;
+
+	return true;
+}
+
+static char *
+build_client_final_message(fe_scram_state *state)
+{
+	char		client_final_message_without_proof[200];
+	uint8		client_proof[SCRAM_KEY_LEN];
+	char		client_proof_base64[SCRAM_KEY_LEN * 2 + 1];
+	int			client_proof_len;
+	char		buf[300];
+
+	snprintf(client_final_message_without_proof, sizeof(client_final_message_without_proof),
+			 "c=biws,r=%s", state->server_nonce);
+
+	calculate_client_proof(state,
+						   client_final_message_without_proof,
+						   client_proof);
+	if (b64_enc_len((char *) client_proof, SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+		return NULL;
+
+	client_proof_len = b64_encode((char *) client_proof, SCRAM_KEY_LEN, client_proof_base64);
+	client_proof_base64[client_proof_len] = '\0';
+
+	state->client_final_message_without_proof =
+		strdup(client_final_message_without_proof);
+	snprintf(buf, sizeof(buf), "%s,p=%s",
+			 client_final_message_without_proof,
+			 client_proof_base64);
+
+	return strdup(buf);
+}
+
+static void
+calculate_client_proof(fe_scram_state *state,
+					   const char *client_final_message_without_proof,
+					   uint8 *result)
+{
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ClientKey[SCRAM_KEY_LEN];
+	uint8		ClientSignature[SCRAM_KEY_LEN];
+	int			i;
+	scram_HMAC_ctx ctx;
+
+	scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+							state->iterations, SCRAM_CLIENT_KEY_NAME, ClientKey);
+	scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+	scram_HMAC_init(&ctx, StoredKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  client_final_message_without_proof,
+					  strlen(client_final_message_without_proof));
+	scram_HMAC_final(ClientSignature, &ctx);
+
+	for (i = 0; i < SCRAM_KEY_LEN; i++)
+		result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+	uint8		ServerSignature[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+	scram_HMAC_ctx ctx;
+
+	scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+							state->iterations, SCRAM_SERVER_KEY_NAME,
+							ServerKey);
+
+	/* calculate ServerSignature */
+	scram_HMAC_init(&ctx, ServerKey, 20);
+	scram_HMAC_update(&ctx,
+					  state->client_first_message_bare,
+					  strlen(state->client_first_message_bare));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->server_first_message,
+					  strlen(state->server_first_message));
+	scram_HMAC_update(&ctx, ",", 1);
+	scram_HMAC_update(&ctx,
+					  state->client_final_message_without_proof,
+					  strlen(state->client_final_message_without_proof));
+	scram_HMAC_final(ServerSignature, &ctx);
+
+	if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+		return false;
+
+	return true;
+}
+
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+	int			i;
+
+	for (i = 0; i < len; i++)
+		buf[i] = random() % 255 + 1;
+
+	buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 5891c75..7f70ffb 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
 #include "libpq-fe.h"
 #include "fe-auth.h"
 #include "libpq/md5.h"
+#include "libpq/scram.h"
 
 
 #ifdef ENABLE_GSS
@@ -428,6 +429,74 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
 }
 #endif   /* ENABLE_SSPI */
 
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+	/*
+	 * Check the authentication mechanism (only SCRAM-SHA-1 is supported at
+	 * the moment.)
+	 */
+	if (strcmp(auth_mechanism, SCRAM_SHA1_NAME) == 0)
+	{
+		conn->password_needed = true;
+		if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  PQnoPasswordSupplied);
+			return STATUS_ERROR;
+		}
+		conn->sasl_state = pg_fe_scram_init(conn->pguser, conn->pgpass);
+		if (!conn->sasl_state)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("out of memory\n"));
+			return STATUS_ERROR;
+		}
+		else
+			return STATUS_OK;
+	}
+	else
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("SASL authentication mechanism %s not supported\n"),
+						  (char *) conn->auth_req_inbuf);
+		return STATUS_ERROR;
+	}
+}
+
+static int
+pg_SASL_exchange(PGconn *conn)
+{
+	char	   *output;
+	int			outputlen;
+	bool		done;
+	bool		success;
+	int			res;
+
+	pg_fe_scram_exchange(conn->sasl_state,
+						 conn->auth_req_inbuf, conn->auth_req_inlen,
+						 &output, &outputlen,
+						 &done, &success, &conn->errorMessage);
+	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);
+
+		if (res != STATUS_OK)
+			return STATUS_ERROR;
+	}
+
+	if (done && !success)
+		return STATUS_ERROR;
+
+	return STATUS_OK;
+}
+
 /*
  * Respond to AUTH_REQ_SCM_CREDS challenge.
  *
@@ -696,6 +765,33 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
 			}
 			break;
 
+		case AUTH_REQ_SASL:
+			/*
+			 * The request contains the name (as assigned by IANA) of the
+			 * authentication mechanism.
+			 */
+			if (pg_SASL_init(conn, conn->auth_req_inbuf) != STATUS_OK)
+			{
+				/* pg_SASL_init already set the error message */
+				return STATUS_ERROR;
+			}
+			/* fall through */
+
+		case AUTH_REQ_SASL_CONT:
+			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) != STATUS_OK)
+			{
+				printfPQExpBuffer(&conn->errorMessage,
+					 "fe_sendauth: error sending password authentication\n");
+				return STATUS_ERROR;
+			}
+			break;
+
 		case AUTH_REQ_SCM_CREDS:
 			if (pg_local_sendauth(conn) != STATUS_OK)
 				return STATUS_ERROR;
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 8d35767..b1b0294 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -18,7 +18,15 @@
 #include "libpq-int.h"
 
 
+/* Prototypes for functions in fe-auth.c */
 extern int	pg_fe_sendauth(AuthRequest areq, PGconn *conn);
 extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 
+/* Prototypes for functions in fe-auth-scram.c */
+extern void *pg_fe_scram_init(const char *username, const char *password);
+extern void pg_fe_scram_free(void *opaq);
+extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen,
+					 bool *done, bool *success, PQExpBuffer errorMessage);
+
 #endif   /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a45f4cb..d69ec96 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2479,6 +2479,48 @@ keep_going:						/* We will come back to here until there is
 					}
 				}
 #endif
+				/* Get additional payload for SASL, if any */
+				if (msgLength > 4 &&
+					(areq == AUTH_REQ_SASL ||
+					 areq == AUTH_REQ_SASL_CONT))
+				{
+					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. */
+						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
@@ -3035,6 +3077,15 @@ closePGconn(PGconn *conn)
 		conn->sspictx = NULL;
 	}
 #endif
+	if (conn->sasl_state)
+	{
+		/*
+		 * XXX: if we add support for more authentication mechanisms, this
+		 * needs to call the right 'free' function.
+		 */
+		pg_fe_scram_free(conn->sasl_state);
+		conn->sasl_state = NULL;
+	}
 }
 
 /*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 2175957..391192b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -421,7 +421,12 @@ 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 SSL, GSS, etc */
+	void	   *sasl_state;
 
 #ifdef USE_SSL
 	bool		allow_ssl_try;	/* Allowed to try SSL negotiation */
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 73ca2e5..a7131e8 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -76,6 +76,11 @@ LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
 ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
 ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
 ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5='XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ERROR:  Cannot use SCRAM verifier as MD5 verifier
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram='md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ERROR:  Cannot use MD5 verifier as SCRAM verifier
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain='XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
 SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
     FROM pg_auth_verifiers v
     LEFT JOIN pg_authid a ON (v.roleid = a.oid)
@@ -86,7 +91,7 @@ SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
  role_passwd1 | m       | md5
  role_passwd2 | p       | foo
  role_passwd3 | m       | md5
- role_passwd4 | m       | md5
+ role_passwd4 | s       | XxC
 (4 rows)
 
 DROP ROLE role_passwd1;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9b9fe5f..af107c9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1632,6 +1632,7 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
                 CASE pg_auth_verifiers.verimet
                     WHEN 'p'::"char" THEN ('plain:'::text || pg_auth_verifiers.verival)
                     WHEN 'm'::"char" THEN ('md5:'::text || pg_auth_verifiers.verival)
+                    WHEN 's'::"char" THEN ('scram:'::text || pg_auth_verifiers.verival)
                     ELSE NULL::text
                 END AS verifiers
            FROM pg_auth_verifiers
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 0376e1b..0131953 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -53,6 +53,9 @@ ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
 ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
 ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
 ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5='XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram='md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain='XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
 SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
     FROM pg_auth_verifiers v
     LEFT JOIN pg_authid a ON (v.roleid = a.oid)
-- 
2.5.0

#52Stephen Frost
sfrost@snowman.net
In reply to: Josh Berkus (#50)
Re: WIP: SCRAM authentication

Josh,

* Josh Berkus (josh@agliodbs.com) wrote:

I don't feel like you've correctly assessed the risk inherent in the
md5 auth method, which is that, having captured an md5auth string by
whatever means, and attacker can reuse that md5 string on other
databases in the network *without* cracking it. That's the biggest risk
as long as md5 is present.

Robert and I, I believe, are both aware of that risk and had moved on to
discussing the risk to the cleartext password by keeping an md5 present,
even if it wasn't able to be used for login. We will want to make it
simple for admins to enable/disable md5 (eg: pg_hba), but we also have
to consider how to make it simple for admins to remove the old md5
password verifier or there will be risk of the cleartext password being
compromised from backups, etc.

I fully agree that it's a serious risk that, today, what's stored in the
database is directly usable for logging into the running system (or any
system where the user has the same username and password).

Aside from code complexity, the user security concern with a "multiple
verifier per role" approach is that the DBAs would never remember to
completely disable md5auth and would capture md5 hashes either in flight
or from backups. This approach can be used to capture an md5hash from a
non-critical database which is poorly secured, and then re-use it
against an important database.

The 'in flight' case is at least a bit less of an issue, as we don't
ship the password verifier directly over the network. The backup
concern is certainly corret though. I fully agree that we need a way to
make sure users don't end up having the old password verifiers around
longer than necessary.

That was the imputus for my earlier suggestion that in a release or two,
we actively make pg_upgrade error (or perhaps warn first, then error,
across two releases) if any of the old verifiers exist.

Now, the counter-argument to this is that a DBA is just as likely to
rememeber to remove md5 verifiers as she is to remember to remove roles
with md5auth.

Indeed, that's a serious concern also.

The other concern with a single password verifier is that we're locking
ourselves into a one-verifier-per-role solution when most of the serious
solutions in use today (Active Directory, Kerberos, certificate based
systems) allow for more than one.

Regardless of the approach we take, encouraging users to migrate is
going to be more of a matter of documentation, publicity, and
administrative tools than one of multiple verifiers vs. multiple roles.
That is, giving DBAs the ability to see and log who's using what kind
of verifier, and what account has what verifier(s) available, will make
more of a difference.

Fully agreed.

Thanks!

Stephen

#53Robert Haas
robertmhaas@gmail.com
In reply to: Stephen Frost (#52)
Re: WIP: SCRAM authentication

On Tue, Aug 18, 2015 at 10:06 AM, Stephen Frost <sfrost@snowman.net> wrote:

That was the imputus for my earlier suggestion that in a release or two,
we actively make pg_upgrade error (or perhaps warn first, then error,
across two releases) if any of the old verifiers exist.

I think there's basically no chance of that being acceptable. The
time at which it's possible to get rid of MD5 is going to vary widely
between installations. People who are using only libpq or
libpq-derived connectors will be able to get rid of it almost
immediately, if they want, though some won't. People who are using
obscure connectors that are poorly maintained may not even have a
version that supports SCRAM for 5 years. Think about how long it took
us to roll out the standard_conforming_strings changes, and there were
still people who got bitten.

The other concern with a single password verifier is that we're locking
ourselves into a one-verifier-per-role solution when most of the serious
solutions in use today (Active Directory, Kerberos, certificate based
systems) allow for more than one.

So what? If you want to delegate authentication to AD or Kerberos, we
already support that. That's not a reason to invent the same
functionality inside the server. If you've got a tangible plan, other
than SCRAM, that would require us to support multiple verifiers, then
please say what it is. If not, the mere fact that some other people
support it is not a reason why we should. In fact, we generally have
taken the approach that needs which are already handled adequately by
other tools to do not need to also be handled inside the database.
That's not a perfect approach and we always argue about it around the
edges, but generally, I think it's served us pretty well.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#53)
Re: WIP: SCRAM authentication

Robert,

* Robert Haas (robertmhaas@gmail.com) wrote:

On Tue, Aug 18, 2015 at 10:06 AM, Stephen Frost <sfrost@snowman.net> wrote:

That was the imputus for my earlier suggestion that in a release or two,
we actively make pg_upgrade error (or perhaps warn first, then error,
across two releases) if any of the old verifiers exist.

I think there's basically no chance of that being acceptable. The
time at which it's possible to get rid of MD5 is going to vary widely
between installations. People who are using only libpq or
libpq-derived connectors will be able to get rid of it almost
immediately, if they want, though some won't. People who are using
obscure connectors that are poorly maintained may not even have a
version that supports SCRAM for 5 years. Think about how long it took
us to roll out the standard_conforming_strings changes, and there were
still people who got bitten.

I would expect there to be people who would run into pg_upgrade
complaining, that's why there would be the check. That's actually a
much better situation than what happened around
standard_conforming_strings. Further, users would be able to continue
with their existing version until they're ready to move or it goes out
of support, by which time, if their connector isn't updated, they should
be moving off of it also. We hear about people running 8.4 and older
because of some application which was never maintained or updated, and
that sucks, but it doesn't prevent us from making the changes we need to
make to move the project forward for the users who properly manage their
systems and use supported connectors.

The other concern with a single password verifier is that we're locking
ourselves into a one-verifier-per-role solution when most of the serious
solutions in use today (Active Directory, Kerberos, certificate based
systems) allow for more than one.

So what? If you want to delegate authentication to AD or Kerberos, we
already support that. That's not a reason to invent the same
functionality inside the server. If you've got a tangible plan, other
than SCRAM, that would require us to support multiple verifiers, then
please say what it is. If not, the mere fact that some other people
support it is not a reason why we should.

SCRAM itself, as has been discussed, supports multiple password
verifiers- that's a specific case all by itself, and it's done
specifically to address the issue that one or another of the algorithms
used is compromised, or that a new algorithm becomes available which is
better. AD and Kerberos support multiple password verifiers because of
this and that it allows you to migrate from one to the next without
having to do wholesale replacment across all systems involved. I bring
them up as examples of the advances in password-based authentication
which we've missed and because they are what users expect from current
password-based authentication systems, not because we support them and
therefore should just push everyone to them.

That said, if we aren't going to move password-based authentication in
PG to beyond the stone age it's currently in, then perhaps we should
just drop it completely and force users to pick a solution which is well
written, uses standard protocols, supports multiple ciphers and hashes
(in case one or more become a problem) and is updated to keep up with
the field. Obviously, that's a strawman and I appreciate that you are
not arguing to keep the status-quo, but I'm trying to make a point that
multiple password verifiers is part of modern authentication technology,
used by multiple standard and broadly installed solutions.

In fact, we generally have
taken the approach that needs which are already handled adequately by
other tools to do not need to also be handled inside the database.
That's not a perfect approach and we always argue about it around the
edges, but generally, I think it's served us pretty well.

Unless we take my strawman literally, which I don't think we can, we
can't simply punt on pushing all password-based authentication out of
the server. If we agree that we need to support password-based
authentication then we should be working to maintain it as well as we
do the WAL and other subsystems.

I fully agree that we should leverage existing systems, technologies,
and protocols where possible, to avoid having to add a lot of extra
code which we have to maintain. I believe that this proposal already
meets that goal and we should move forward with full support for SCRAM.
That, in my view at least, means that we also need to provide for
multiple password verifiers.

Thanks!

Stephen

#55Robert Haas
robertmhaas@gmail.com
In reply to: Stephen Frost (#54)
Re: WIP: SCRAM authentication

On Tue, Aug 18, 2015 at 2:07 PM, Stephen Frost <sfrost@snowman.net> wrote:

I would expect there to be people who would run into pg_upgrade
complaining, that's why there would be the check. That's actually a
much better situation than what happened around
standard_conforming_strings. Further, users would be able to continue
with their existing version until they're ready to move or it goes out
of support, by which time, if their connector isn't updated, they should
be moving off of it also. We hear about people running 8.4 and older
because of some application which was never maintained or updated, and
that sucks, but it doesn't prevent us from making the changes we need to
make to move the project forward for the users who properly manage their
systems and use supported connectors.

Sorry, that's a completely bogus argument. We do not "need" to
prevent people from upgrading if they haven't moved off of MD5
authentication; that's just an arbitrary - and IMHO extremely
user-hostile - policy decision. We have a legitimate need, to move
the project forward, to introduce a better system for password
authentication. Ripping out the old one is not a real need. I'm sure
at some point it will seem like antiquated cruft that nobody uses any
more, but that will not be in "a year or two" or anything close to
that.

SCRAM itself, as has been discussed, supports multiple password
verifiers- that's a specific case all by itself, and it's done
specifically to address the issue that one or another of the algorithms
used is compromised, or that a new algorithm becomes available which is
better. AD and Kerberos support multiple password verifiers because of
this and that it allows you to migrate from one to the next without
having to do wholesale replacment across all systems involved. I bring
them up as examples of the advances in password-based authentication
which we've missed and because they are what users expect from current
password-based authentication systems, not because we support them and
therefore should just push everyone to them.

OK, that's an interesting argument. If SCRAM supports multiple
password verifiers, and we support SCRAM, then I guess we should
probably do that, too. I still don't like it all that much, though.
I think it's absolutely inevitable that people are going to end up
with an account with 3 or more different passwords that can all be
used to log into it, and that won't be good. How do other systems
avoid this pitfall?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#55)
Re: WIP: SCRAM authentication

* Robert Haas (robertmhaas@gmail.com) wrote:

On Tue, Aug 18, 2015 at 2:07 PM, Stephen Frost <sfrost@snowman.net> wrote:

SCRAM itself, as has been discussed, supports multiple password
verifiers- that's a specific case all by itself, and it's done
specifically to address the issue that one or another of the algorithms
used is compromised, or that a new algorithm becomes available which is
better. AD and Kerberos support multiple password verifiers because of
this and that it allows you to migrate from one to the next without
having to do wholesale replacment across all systems involved. I bring
them up as examples of the advances in password-based authentication
which we've missed and because they are what users expect from current
password-based authentication systems, not because we support them and
therefore should just push everyone to them.

OK, that's an interesting argument. If SCRAM supports multiple
password verifiers, and we support SCRAM, then I guess we should
probably do that, too. I still don't like it all that much, though.
I think it's absolutely inevitable that people are going to end up
with an account with 3 or more different passwords that can all be
used to log into it, and that won't be good. How do other systems
avoid this pitfall?

They provide:

a) ability to configure which algorithms are used at change-password

MIT Kerberos, kdc.conf: supported_enctypes and
kadmin: enctype-salttype

b) ability to configure which algorithms are allowed to be used at all

MIT Kerberos, libdefaults: default_tgs_enctypes, default_tkt_enctypes,
and permitted_enctypes, along with allow_weak_crypto which removes all
those considered 'weak' from the other sets if set to false (the
default).

c) password aging (to get users to change their password regularly, so
you know after, say, 90 days of the change to the set configured in
'a' that all users will have either expired passwords or have been
updated to the latest set).

Note that we don't need quite so many options, these were done over time
for Kerberos and also address the different types of tickets involved in
that system.

Today, they discourage explicitly setting default_tgs_enctypes and
default_tkt_enctypes. We really just need to allow configuration of
which should be stored at change-password time and the set of allowed
types. I wouldn't want those to be the same because you'd want to
remove a compromised type as soon as possible from the "change-password"
set while still allowing those users to connect, to get them to change
their password using self-service (doing it all through the
adminstrators of the system would be very painful in large
organizations).

We also don't strictly need password aging, but it certainly helps. We
definitely do need to provide administrators with easy interfaces
through functions and tools to manage the password verifiers used.

Thanks!

Stephen

#57Greg Stark
stark@mit.edu
In reply to: Robert Haas (#55)
Re: WIP: SCRAM authentication

On Tue, Aug 18, 2015 at 7:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Sorry, that's a completely bogus argument. We do not "need" to
prevent people from upgrading if they haven't moved off of MD5
authentication; that's just an arbitrary - and IMHO extremely
user-hostile - policy decision. We have a legitimate need, to move
the project forward, to introduce a better system for password
authentication. Ripping out the old one is not a real need. I'm sure
at some point it will seem like antiquated cruft that nobody uses any
more, but that will not be in "a year or two" or anything close to
that.

I would imagine a GUC like enable_legacy_authentication=true which we
would just start defaulting to false after a few years and perhaps
consider removing five years after that. pg_upgrade could check if
there are any users which require it to be set to true and warn users
that they must enable it but should check the documentation so they
understand the impact.

OK, that's an interesting argument. If SCRAM supports multiple
password verifiers, and we support SCRAM, then I guess we should
probably do that, too. I still don't like it all that much, though.
I think it's absolutely inevitable that people are going to end up
with an account with 3 or more different passwords that can all be
used to log into it, and that won't be good. How do other systems
avoid this pitfall?

Fwiw having multiple passwords would make automated credential
rotations *so* much easier. Heroku has a really baroque solution to
this problem in Postgres involving creating new child roles and
swapping them around. My team in Google wasted many man hours dealing
with fallout from the quarterly password rotations.

(I wish we could just drop the account management and authentication
system entirely and dump the whole work on a system designed for this
particular problem. It's a hard problem that's far outside our core
features and Postgres is never going to be a good system for anyone
let alone everyone when there are many different types of users.)

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Michael Paquier
michael.paquier@gmail.com
In reply to: Greg Stark (#57)
Re: WIP: SCRAM authentication

On Wed, Aug 19, 2015 at 5:30 AM, Greg Stark <stark@mit.edu> wrote:

On Tue, Aug 18, 2015 at 7:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Sorry, that's a completely bogus argument. We do not "need" to
prevent people from upgrading if they haven't moved off of MD5
authentication; that's just an arbitrary - and IMHO extremely
user-hostile - policy decision. We have a legitimate need, to move
the project forward, to introduce a better system for password
authentication. Ripping out the old one is not a real need. I'm sure
at some point it will seem like antiquated cruft that nobody uses any
more, but that will not be in "a year or two" or anything close to
that.

I would imagine a GUC like enable_legacy_authentication=true which we
would just start defaulting to false after a few years and perhaps
consider removing five years after that. pg_upgrade could check if
there are any users which require it to be set to true and warn users
that they must enable it but should check the documentation so they
understand the impact.

Yep, that's one of the ideas mentioned upstread. This parameter should
actually be filled with a list of verifiers that we consider
out-of-support, deprecated, well things that users should be warned
about. One solution may be to log in warnings when parsing pg_hba.conf
should a deprecated method be used.

OK, that's an interesting argument. If SCRAM supports multiple
password verifiers, and we support SCRAM, then I guess we should
probably do that, too. I still don't like it all that much, though.
I think it's absolutely inevitable that people are going to end up
with an account with 3 or more different passwords that can all be
used to log into it, and that won't be good. How do other systems
avoid this pitfall?

Fwiw having multiple passwords would make automated credential
rotations *so* much easier. Heroku has a really baroque solution to
this problem in Postgres involving creating new child roles and
swapping them around. My team in Google wasted many man hours dealing
with fallout from the quarterly password rotations.

(I wish we could just drop the account management and authentication
system entirely and dump the whole work on a system designed for this
particular problem. It's a hard problem that's far outside our core
features and Postgres is never going to be a good system for anyone
let alone everyone when there are many different types of users.)

This makes me think that we may need actually a finer grammar than
what the patch is proposing:
ADD PASSWORD VERIFIERS (method = 'value' [, ...] ) [ OPTIONS
(validUntil = 'timestamp') ]
DROP PASSWORD VERIFIERS (method [, ...])
PASSWORD VERIFIERS (method = 'value' [, ...]) [OPTIONS validUntil =
'timestamp'] -- replace all the existing ones

Reaching to the following catalog for pg_auth_verifiers:
Oid roleoid;
char method;
text value;
timestamp valueUntil;

I am not sure if we would want to be able to have multiple verifier
values for the same method, but feel free to comment.
Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Bruce Momjian
bruce@momjian.us
In reply to: Greg Stark (#57)
Re: WIP: SCRAM authentication

On Tue, Aug 18, 2015 at 09:30:39PM +0100, Greg Stark wrote:

OK, that's an interesting argument. If SCRAM supports multiple
password verifiers, and we support SCRAM, then I guess we should
probably do that, too. I still don't like it all that much, though.
I think it's absolutely inevitable that people are going to end up
with an account with 3 or more different passwords that can all be
used to log into it, and that won't be good. How do other systems
avoid this pitfall?

Fwiw having multiple passwords would make automated credential
rotations *so* much easier. Heroku has a really baroque solution to
this problem in Postgres involving creating new child roles and
swapping them around. My team in Google wasted many man hours dealing
with fallout from the quarterly password rotations.

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Stephen Frost
sfrost@snowman.net
In reply to: Bruce Momjian (#59)
Re: WIP: SCRAM authentication

Bruce,

* Bruce Momjian (bruce@momjian.us) wrote:

On Tue, Aug 18, 2015 at 09:30:39PM +0100, Greg Stark wrote:

OK, that's an interesting argument. If SCRAM supports multiple
password verifiers, and we support SCRAM, then I guess we should
probably do that, too. I still don't like it all that much, though.
I think it's absolutely inevitable that people are going to end up
with an account with 3 or more different passwords that can all be
used to log into it, and that won't be good. How do other systems
avoid this pitfall?

Fwiw having multiple passwords would make automated credential
rotations *so* much easier. Heroku has a really baroque solution to
this problem in Postgres involving creating new child roles and
swapping them around. My team in Google wasted many man hours dealing
with fallout from the quarterly password rotations.

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

That's exactly the process if multiple passwords can be used. If
there's only one account and one password supported then you have to
change all the systems all at once and that certainly can be a hard
problem.

One way to deal with this is to have a bunch of different accounts, but
that's certainly not simple either and can get quite painful.

Thanks!

Stephen

#61Bruce Momjian
bruce@momjian.us
In reply to: Stephen Frost (#60)
Re: WIP: SCRAM authentication

On Fri, Sep 4, 2015 at 04:51:33PM -0400, Stephen Frost wrote:

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

That's exactly the process if multiple passwords can be used. If
there's only one account and one password supported then you have to
change all the systems all at once and that certainly can be a hard
problem.

One way to deal with this is to have a bunch of different accounts, but
that's certainly not simple either and can get quite painful.

OK, for me, if we can explain the benefit for users, it seems worth
doing just to allow that.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Michael Paquier
michael.paquier@gmail.com
In reply to: Bruce Momjian (#61)
Re: WIP: SCRAM authentication

On Sat, Sep 5, 2015 at 9:31 AM, Bruce Momjian wrote:

On Fri, Sep 4, 2015 at 04:51:33PM -0400, Stephen Frost wrote:

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

That's exactly the process if multiple passwords can be used. If
there's only one account and one password supported then you have to
change all the systems all at once and that certainly can be a hard
problem.

One way to deal with this is to have a bunch of different accounts, but
that's certainly not simple either and can get quite painful.

OK, for me, if we can explain the benefit for users, it seems worth
doing just to allow that.

Reviving an old thread for a patch still registered in this commit
fest to make the arguing move on.

Supporting multiple verifiers for a single role has IMO clear advantages:
- help transition to new protocols and decommission of old protocols
without the need to create alternative roles in the backend when
switching from one or the other.
- move on to a more complex password aging system. The patch currently
submitted allows only one verifier per type and per role so this is
not a complete system. Still, the new catalog table pg_auth_verifiers
could be later easily extended based on other aging properties that we
consider adapted to reach this goal.

There are clear concerns about protocol aging and how to facilitate
the life of users to switch to a new system. Hence, I think that the
patch could include the following:
- A compatibility GUC allowed_password_verifiers defaulting to a list
of verifier protocols that we think are safe. This would be added in
the patch adding infrastructure for multiple verifiers, with default
to md5. In the patch adding SCRAM, the value of this parameter is
changed to md5,scram. If a user create a password verifier with a
protocol not listed in this parameter we return to him a WARNING.
ERROR may be too much but opinions are welcome.
- A superuser cleanup function for pg_auth_verifiers aimed at being
used by pg_upgrade to do needed cleanup of this catalog based on the
previous GUC to remove outdated verifiers. Optionally, we could have
an argument for a list of protocols to clean up.
Using both things we could leverage the upgrade experience and
transition between systems. Say even if at some point we decide to
decommission SCRAM we could reuse the same infrastructure to move on
to a new major version.

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

#63Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#62)
Re: WIP: SCRAM authentication

On Mon, Nov 16, 2015 at 10:53 PM, Michael Paquier wrote:

Reviving an old thread for a patch still registered in this commit
fest to make the arguing move on.

Supporting multiple verifiers for a single role has IMO clear advantages:
- help transition to new protocols and decommission of old protocols
without the need to create alternative roles in the backend when
switching from one or the other.
- move on to a more complex password aging system. The patch currently
submitted allows only one verifier per type and per role so this is
not a complete system. Still, the new catalog table pg_auth_verifiers
could be later easily extended based on other aging properties that we
consider adapted to reach this goal.

There are clear concerns about protocol aging and how to facilitate
the life of users to switch to a new system. Hence, I think that the
patch could include the following:
- A compatibility GUC allowed_password_verifiers defaulting to a list
of verifier protocols that we think are safe. This would be added in
the patch adding infrastructure for multiple verifiers, with default
to md5. In the patch adding SCRAM, the value of this parameter is
changed to md5,scram. If a user create a password verifier with a
protocol not listed in this parameter we return to him a WARNING.
ERROR may be too much but opinions are welcome.
- A superuser cleanup function for pg_auth_verifiers aimed at being
used by pg_upgrade to do needed cleanup of this catalog based on the
previous GUC to remove outdated verifiers. Optionally, we could have
an argument for a list of protocols to clean up.
Using both things we could leverage the upgrade experience and
transition between systems. Say even if at some point we decide to
decommission SCRAM we could reuse the same infrastructure to move on
to a new major version.

Thoughts?

I am going to mark this patch as returned with feedback because of a
visible lack of interest. It would be nice if I could get some
feedback about the suggestions above to help move on for (why not) a
patch aiming for January's CF.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64David Steele
david@pgmasters.net
In reply to: Michael Paquier (#62)
Re: WIP: SCRAM authentication

Hi Michael and Heikki,

On 11/16/15 8:53 AM, Michael Paquier wrote:

On Sat, Sep 5, 2015 at 9:31 AM, Bruce Momjian wrote:

On Fri, Sep 4, 2015 at 04:51:33PM -0400, Stephen Frost wrote:

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

That's exactly the process if multiple passwords can be used. If
there's only one account and one password supported then you have to
change all the systems all at once and that certainly can be a hard
problem.

One way to deal with this is to have a bunch of different accounts, but
that's certainly not simple either and can get quite painful.

OK, for me, if we can explain the benefit for users, it seems worth
doing just to allow that.

Reviving an old thread for a patch still registered in this commit
fest to make the arguing move on.

I was wondering if this patch was going to be submitted for the 2016-03 CF?

If so I am interesting in testing/reviewing or doing any other work that
would be helpful.

Supporting multiple verifiers for a single role has IMO clear advantages:
- help transition to new protocols and decommission of old protocols
without the need to create alternative roles in the backend when
switching from one or the other.

Agreed.

- move on to a more complex password aging system. The patch currently
submitted allows only one verifier per type and per role so this is
not a complete system. Still, the new catalog table pg_auth_verifiers
could be later easily extended based on other aging properties that we
consider adapted to reach this goal.

Yes, with some careful design the pg_auth_verifiers table could support
multiple passwords using a single verifier in the future. I think the
major changes would be to ALTER ROLE WITH PASSWORD so coming up with an
extensible syntax is important.

In addition, I would prefer to maintain the current structure of the
pg_authid table and use the rolpassword and rolvaliduntil columns to
store only the md5 verifier which will also be stored in
pg_auth_verifiers. This would provide a smoother migration path with
the idea that rolpassword and rolvaliduntil will be removed from
pg_authid in a future version of Postgres.

There are clear concerns about protocol aging and how to facilitate
the life of users to switch to a new system. Hence, I think that the
patch could include the following:
- A compatibility GUC allowed_password_verifiers defaulting to a list
of verifier protocols that we think are safe. This would be added in
the patch adding infrastructure for multiple verifiers, with default
to md5. In the patch adding SCRAM, the value of this parameter is
changed to md5,scram. If a user create a password verifier with a
protocol not listed in this parameter we return to him a WARNING.
ERROR may be too much but opinions are welcome.

It seems like an error would be better here.

- A superuser cleanup function for pg_auth_verifiers aimed at being
used by pg_upgrade to do needed cleanup of this catalog based on the
previous GUC to remove outdated verifiers. Optionally, we could have
an argument for a list of protocols to clean up.
Using both things we could leverage the upgrade experience and
transition between systems. Say even if at some point we decide to
decommission SCRAM we could reuse the same infrastructure to move on
to a new major version.

Yes - and although the eventual migration process may not need to be
worked out it its entirety we should have a very good idea of what it's
going to look like as that will inform some of the decisions that need
to be made now.

Please let me know if there's anything I can do to expedite this patch.

--
-David
david@pgmasters.net

#65Michael Paquier
michael.paquier@gmail.com
In reply to: David Steele (#64)
Re: WIP: SCRAM authentication

On Sat, Feb 13, 2016 at 3:05 AM, David Steele <david@pgmasters.net> wrote:

On 11/16/15 8:53 AM, Michael Paquier wrote:

On Sat, Sep 5, 2015 at 9:31 AM, Bruce Momjian wrote:

On Fri, Sep 4, 2015 at 04:51:33PM -0400, Stephen Frost wrote:

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

That's exactly the process if multiple passwords can be used. If
there's only one account and one password supported then you have to
change all the systems all at once and that certainly can be a hard
problem.

One way to deal with this is to have a bunch of different accounts, but
that's certainly not simple either and can get quite painful.

OK, for me, if we can explain the benefit for users, it seems worth
doing just to allow that.

Reviving an old thread for a patch still registered in this commit
fest to make the arguing move on.

I was wondering if this patch was going to be submitted for the 2016-03 CF?

For 9.6 I am afraid this is too late, per the rule that there cannot
be huge patches for the last CF of a development cycle. But I have
plans for this set of features afterwards with the first CF of 9.7 and
I was planning to talk about it at PgCon's unconference if I can get
there to gather some feedback. There is still cruel need for it on my
side..

If so I am interesting in testing/reviewing or doing any other work that
would be helpful.

Thanks.

In addition, I would prefer to maintain the current structure of the
pg_authid table and use the rolpassword and rolvaliduntil columns to
store only the md5 verifier which will also be stored in
pg_auth_verifiers. This would provide a smoother migration path with
the idea that rolpassword and rolvaliduntil will be removed from
pg_authid in a future version of Postgres.

Actually, I am of the opinion that both rolpassword and rolvaliduntil
should be directly part of pg_auth_verifiers. Being able to handle
multiple verifiers for the same protocol is a feature that is being
asked for with a given password handling policy (was pinged again
about that in Moscow last week). Rolling in new verifiers needs now
extra roles to be created.

There are clear concerns about protocol aging and how to facilitate
the life of users to switch to a new system. Hence, I think that the
patch could include the following:
- A compatibility GUC allowed_password_verifiers defaulting to a list
of verifier protocols that we think are safe. This would be added in
the patch adding infrastructure for multiple verifiers, with default
to md5. In the patch adding SCRAM, the value of this parameter is
changed to md5,scram. If a user create a password verifier with a
protocol not listed in this parameter we return to him a WARNING.
ERROR may be too much but opinions are welcome.

It seems like an error would be better here.

Noted.

- A superuser cleanup function for pg_auth_verifiers aimed at being
used by pg_upgrade to do needed cleanup of this catalog based on the
previous GUC to remove outdated verifiers. Optionally, we could have
an argument for a list of protocols to clean up.
Using both things we could leverage the upgrade experience and
transition between systems. Say even if at some point we decide to
decommission SCRAM we could reuse the same infrastructure to move on
to a new major version.

Yes - and although the eventual migration process may not need to be
worked out it its entirety we should have a very good idea of what it's
going to look like as that will inform some of the decisions that need
to be made now.

Thinking about that again a combination of a GUC and an interface
dedicated to pg_upgrade sounds the saner way of going here.

Please let me know if there's anything I can do to expedite this patch.

I am planning to work on a new patch following the ideas I have sent
upthread, after the last CF of 9.6 is wrapped up. This thread is high
on my priority list.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Stephen Frost
sfrost@snowman.net
In reply to: Michael Paquier (#65)
Re: WIP: SCRAM authentication

Michael,

* Michael Paquier (michael.paquier@gmail.com) wrote:

On Sat, Feb 13, 2016 at 3:05 AM, David Steele <david@pgmasters.net> wrote:

On 11/16/15 8:53 AM, Michael Paquier wrote:

On Sat, Sep 5, 2015 at 9:31 AM, Bruce Momjian wrote:

On Fri, Sep 4, 2015 at 04:51:33PM -0400, Stephen Frost wrote:

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

That's exactly the process if multiple passwords can be used. If
there's only one account and one password supported then you have to
change all the systems all at once and that certainly can be a hard
problem.

One way to deal with this is to have a bunch of different accounts, but
that's certainly not simple either and can get quite painful.

OK, for me, if we can explain the benefit for users, it seems worth
doing just to allow that.

Reviving an old thread for a patch still registered in this commit
fest to make the arguing move on.

I was wondering if this patch was going to be submitted for the 2016-03 CF?

For 9.6 I am afraid this is too late, per the rule that there cannot
be huge patches for the last CF of a development cycle. But I have
plans for this set of features afterwards with the first CF of 9.7 and
I was planning to talk about it at PgCon's unconference if I can get
there to gather some feedback. There is still cruel need for it on my
side..

There's a lot of good reason to get SCRAM added as a protocol,
considering our current password-based implementation is rather..
lacking. Regarding the specific comment about the timing, that rule is
specifically to prevent large patches from landing in the last CF
without any prior discussion or review, as I recall, so I'm not sure it
really applies here as there's been quite a bit of discussion and review
already.

That said, per various discussions, we'd really want a more-or-less
fully formed patch to land prior to the last CF, for this to have any
chance. Perhaps that means it's not going to happen, which would be
unfortunate, but it's not beyond the possible, in my view, at least.

In addition, I would prefer to maintain the current structure of the
pg_authid table and use the rolpassword and rolvaliduntil columns to
store only the md5 verifier which will also be stored in
pg_auth_verifiers. This would provide a smoother migration path with
the idea that rolpassword and rolvaliduntil will be removed from
pg_authid in a future version of Postgres.

Actually, I am of the opinion that both rolpassword and rolvaliduntil
should be directly part of pg_auth_verifiers. Being able to handle
multiple verifiers for the same protocol is a feature that is being
asked for with a given password handling policy (was pinged again
about that in Moscow last week). Rolling in new verifiers needs now
extra roles to be created.

I'm on Michael's side here. I don't believe it makes sense to try and
maintain the exact structure of pg_authid. We are certainly happy to
whack around the other catalogs, and I'm unimpressed with my prior
efforts to provide backwards-compatible catalog (see pg_user, et al) for
just a few releases- we seem unable to get rid of them now, even though
we should have years ago, really. Indeed, I'd be just as happy if we
got rid of them during this work..

Thanks!

Stephen

#67Michael Paquier
michael.paquier@gmail.com
In reply to: Stephen Frost (#66)
Re: WIP: SCRAM authentication

On Mon, Feb 15, 2016 at 9:17 AM, Stephen Frost <sfrost@snowman.net> wrote:

Michael,

* Michael Paquier (michael.paquier@gmail.com) wrote:

On Sat, Feb 13, 2016 at 3:05 AM, David Steele <david@pgmasters.net> wrote:

On 11/16/15 8:53 AM, Michael Paquier wrote:

On Sat, Sep 5, 2015 at 9:31 AM, Bruce Momjian wrote:

On Fri, Sep 4, 2015 at 04:51:33PM -0400, Stephen Frost wrote:

Coming in late, but can you explain how multiple passwords allow for
easier automated credential rotation? If you have five applications
with stored passwords, I imagine you can't change them all at once, so
with multiples you could change it on one, then go to the others and
change it there, and finally, remove the old password. Is that the
process? I am not realizing that without multiple plasswords, this is a
hard problem.

That's exactly the process if multiple passwords can be used. If
there's only one account and one password supported then you have to
change all the systems all at once and that certainly can be a hard
problem.

One way to deal with this is to have a bunch of different accounts, but
that's certainly not simple either and can get quite painful.

OK, for me, if we can explain the benefit for users, it seems worth
doing just to allow that.

Reviving an old thread for a patch still registered in this commit
fest to make the arguing move on.

I was wondering if this patch was going to be submitted for the 2016-03 CF?

For 9.6 I am afraid this is too late, per the rule that there cannot
be huge patches for the last CF of a development cycle. But I have
plans for this set of features afterwards with the first CF of 9.7 and
I was planning to talk about it at PgCon's unconference if I can get
there to gather some feedback. There is still cruel need for it on my
side..

There's a lot of good reason to get SCRAM added as a protocol,
considering our current password-based implementation is rather..
lacking. Regarding the specific comment about the timing, that rule is
specifically to prevent large patches from landing in the last CF
without any prior discussion or review, as I recall, so I'm not sure it
really applies here as there's been quite a bit of discussion and review
already.

Honestly I don't know what to answer to that.

That said, per various discussions, we'd really want a more-or-less
fully formed patch to land prior to the last CF, for this to have any
chance. Perhaps that means it's not going to happen, which would be
unfortunate, but it's not beyond the possible, in my view, at least.

Well, I could send a rebased patch with the new things proposed
upthread, and with things split in as many patches as I can get out,
roughly:
1) One patch for the catalog split
2) One for the GUC param controlling recommended protocols
3) One for the pg_upgrade function cleaning up automatically the
entries of unsupported protocols
4) SCRAM on top of the rest, which is at more or less 75% something
that Heikki produced.

In addition, I would prefer to maintain the current structure of the
pg_authid table and use the rolpassword and rolvaliduntil columns to
store only the md5 verifier which will also be stored in
pg_auth_verifiers. This would provide a smoother migration path with
the idea that rolpassword and rolvaliduntil will be removed from
pg_authid in a future version of Postgres.

Actually, I am of the opinion that both rolpassword and rolvaliduntil
should be directly part of pg_auth_verifiers. Being able to handle
multiple verifiers for the same protocol is a feature that is being
asked for with a given password handling policy (was pinged again
about that in Moscow last week). Rolling in new verifiers needs now
extra roles to be created.

I'm on Michael's side here. I don't believe it makes sense to try and
maintain the exact structure of pg_authid. We are certainly happy to
whack around the other catalogs, and I'm unimpressed with my prior
efforts to provide backwards-compatible catalog (see pg_user, et al) for
just a few releases- we seem unable to get rid of them now, even though
we should have years ago, really. Indeed, I'd be just as happy if we
got rid of them during this work..

We'd need as well to switch pg_shadow to have an array of elements
made of protocol:identifier instead of a single password field. There
can be only one valid identifier per protocol, user and valid_until
for a single point in time, and I can't believe that we should
recommend only one authentication protocol per single major version of
Postgres.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Stephen Frost
sfrost@snowman.net
In reply to: Michael Paquier (#67)
Re: WIP: SCRAM authentication

Michael,

* Michael Paquier (michael.paquier@gmail.com) wrote:

On Mon, Feb 15, 2016 at 9:17 AM, Stephen Frost <sfrost@snowman.net> wrote:

That said, per various discussions, we'd really want a more-or-less
fully formed patch to land prior to the last CF, for this to have any
chance. Perhaps that means it's not going to happen, which would be
unfortunate, but it's not beyond the possible, in my view, at least.

Well, I could send a rebased patch with the new things proposed
upthread, and with things split in as many patches as I can get out,
roughly:
1) One patch for the catalog split
2) One for the GUC param controlling recommended protocols
3) One for the pg_upgrade function cleaning up automatically the
entries of unsupported protocols
4) SCRAM on top of the rest, which is at more or less 75% something
that Heikki produced.

That sounds like a pretty reasonable split, to me at least.

In addition, I would prefer to maintain the current structure of the
pg_authid table and use the rolpassword and rolvaliduntil columns to
store only the md5 verifier which will also be stored in
pg_auth_verifiers. This would provide a smoother migration path with
the idea that rolpassword and rolvaliduntil will be removed from
pg_authid in a future version of Postgres.

Actually, I am of the opinion that both rolpassword and rolvaliduntil
should be directly part of pg_auth_verifiers. Being able to handle
multiple verifiers for the same protocol is a feature that is being
asked for with a given password handling policy (was pinged again
about that in Moscow last week). Rolling in new verifiers needs now
extra roles to be created.

I'm on Michael's side here. I don't believe it makes sense to try and
maintain the exact structure of pg_authid. We are certainly happy to
whack around the other catalogs, and I'm unimpressed with my prior
efforts to provide backwards-compatible catalog (see pg_user, et al) for
just a few releases- we seem unable to get rid of them now, even though
we should have years ago, really. Indeed, I'd be just as happy if we
got rid of them during this work..

We'd need as well to switch pg_shadow to have an array of elements
made of protocol:identifier instead of a single password field. There
can be only one valid identifier per protocol, user and valid_until
for a single point in time, and I can't believe that we should
recommend only one authentication protocol per single major version of
Postgres.

Ugh, that sounds pretty grotty to me.

Applications which consider these fields will need to be updated, one
way or the other, and I'd much rather they be updated to work with
reasonable structures rather than something we've hacked together in
some faint hope that it'd be useful. An array in pg_shadow for a field
which used to be a text field does *not* sound like a simpler solution
to me, and I'd rather simply do away with those views entirely, or at
least nuke the fields which are at issue, than try to come up with
something between wholesale change and no change that ends up being
worse than both.

Thanks!

Stephen

#69Michael Paquier
michael.paquier@gmail.com
In reply to: Stephen Frost (#68)
Re: WIP: SCRAM authentication

On Mon, Feb 15, 2016 at 9:56 AM, Stephen Frost <sfrost@snowman.net> wrote:

* Michael Paquier (michael.paquier@gmail.com) wrote:

We'd need as well to switch pg_shadow to have an array of elements
made of protocol:identifier instead of a single password field. There
can be only one valid identifier per protocol, user and valid_until
for a single point in time, and I can't believe that we should
recommend only one authentication protocol per single major version of
Postgres.

Ugh, that sounds pretty grotty to me.

Applications which consider these fields will need to be updated, one
way or the other, and I'd much rather they be updated to work with
reasonable structures rather than something we've hacked together in
some faint hope that it'd be useful. An array in pg_shadow for a field
which used to be a text field does *not* sound like a simpler solution
to me, and I'd rather simply do away with those views entirely, or at
least nuke the fields which are at issue, than try to come up with
something between wholesale change and no change that ends up being
worse than both.

It seems to me that applications are going to need a refresh anyway...
Among the other possibilities I can foresee:
- Add a column "protocol" to pg_shadow and produce one row per
protocol, so one user will be listed for all the protocol it has. Any
application could then filter out things with an additional WHERE
clause.
- Nuke passwd from pg_shadow and have a new view pg_shadow_verifiers
made of the user OID, the protocol and the verifier. This maps quite
well with pg_auth_verifiers.
- Give up and nuke pg_shadow, which is here for compatibility down to
8.1, and add a protocol column to pg_user, or even better create a new
view pg_user_verifiers that has all the data of all the protocols. If
we care a lot about backward-compatibility, pg_user could as well map
with pg_auth_verifiers with the md5 protocol.
I would go with the last one.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Stephen Frost
sfrost@snowman.net
In reply to: Michael Paquier (#69)
Re: WIP: SCRAM authentication

Michael,

* Michael Paquier (michael.paquier@gmail.com) wrote:

It seems to me that applications are going to need a refresh anyway...

Indeed.

Among the other possibilities I can foresee:
- Add a column "protocol" to pg_shadow and produce one row per
protocol, so one user will be listed for all the protocol it has. Any
application could then filter out things with an additional WHERE
clause.
- Nuke passwd from pg_shadow and have a new view pg_shadow_verifiers
made of the user OID, the protocol and the verifier. This maps quite
well with pg_auth_verifiers.
- Give up and nuke pg_shadow, which is here for compatibility down to
8.1, and add a protocol column to pg_user, or even better create a new
view pg_user_verifiers that has all the data of all the protocols. If
we care a lot about backward-compatibility, pg_user could as well map
with pg_auth_verifiers with the md5 protocol.
I would go with the last one.

I would start by pointing out that pg_user currently uses pg_shadow..

Why do we need pg_shadow or pg_user or related views at all..?
Applications will need to be updated, we might as well simply nuke them
and expect applications to use the new catalogs. Perhaps there is a
useful view or two which we can provide over the new catalogs, but I'd
rather consider how to create brand new, useful, views over the new
catalogs than consider any kind of way to provides backwards
compatible-ish views.

Thanks!

Stephen

#71Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#70)
Re: WIP: SCRAM authentication

Stephen Frost <sfrost@snowman.net> writes:

Why do we need pg_shadow or pg_user or related views at all..?

A lot of code looks at those just to get usernames. I am not in favor of
breaking such stuff without need.

How about we just say that the password in these old views always reads
out as '********' even when there is a password, and we invent new views
that carry real auth information? (Hopefully in an extensible way.)

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

#72Michael Paquier
michael.paquier@gmail.com
In reply to: Stephen Frost (#70)
Re: WIP: SCRAM authentication

On Mon, Feb 15, 2016 at 10:23 AM, Stephen Frost <sfrost@snowman.net> wrote:

I would start by pointing out that pg_user currently uses pg_shadow..
Why do we need pg_shadow or pg_user or related views at all..?

pg_user/pg_shadow have the advantage to be world-readable and mask
password values.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#71)
Re: WIP: SCRAM authentication

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

Why do we need pg_shadow or pg_user or related views at all..?

A lot of code looks at those just to get usernames. I am not in favor of
breaking such stuff without need.

Alright.

How about we just say that the password in these old views always reads
out as '********' even when there is a password, and we invent new views
that carry real auth information? (Hopefully in an extensible way.)

I'd be alright with that approach, I'd just rather that any clients
which actually want to read the password field be updated to look at the
extensible and sensible base catalogs, and not some hacked up array that
we shoved into that field.

Thanks!

Stephen

#74Stephen Frost
sfrost@snowman.net
In reply to: Michael Paquier (#72)
Re: WIP: SCRAM authentication

* Michael Paquier (michael.paquier@gmail.com) wrote:

On Mon, Feb 15, 2016 at 10:23 AM, Stephen Frost <sfrost@snowman.net> wrote:

I would start by pointing out that pg_user currently uses pg_shadow..
Why do we need pg_shadow or pg_user or related views at all..?

pg_user/pg_shadow have the advantage to be world-readable and mask
password values.

New views would have that same advantage, should we implement them that
way. Tom's approach is also workable though, where we make the existing
views have a reducaed charter, which is mainly around providing user
lists and simply not include any info about password verifiers or the
like.

Thanks!

Stephen

#75Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#73)
Re: WIP: SCRAM authentication

Stephen Frost <sfrost@snowman.net> writes:

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

How about we just say that the password in these old views always reads
out as '********' even when there is a password, and we invent new views
that carry real auth information? (Hopefully in an extensible way.)

I'd be alright with that approach, I'd just rather that any clients
which actually want to read the password field be updated to look at the
extensible and sensible base catalogs, and not some hacked up array that
we shoved into that field.

Yeah, I'm good with that. I just don't think the breakage needs to extend
to clients that aren't trying to read auth-related information.

BTW, if we haven't learned this lesson by now: I'm pretty sure that every
single one of these views is an attempt to emulate what *used* to be the
real base catalog, in some previous release. Maybe we should stop
expecting clients to read the real catalog, ever, in favor of a sanitized
view? Although I don't know exactly what that would lead to in terms of
what we'd expose that's different from what the base catalog is. But it's
worth thinking about whether there is a way to avoid having this same
discussion again in five or ten years.

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

#76Michael Paquier
michael.paquier@gmail.com
In reply to: Stephen Frost (#73)
Re: WIP: SCRAM authentication

On Mon, Feb 15, 2016 at 10:51 AM, Stephen Frost <sfrost@snowman.net> wrote:

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

Why do we need pg_shadow or pg_user or related views at all..?

A lot of code looks at those just to get usernames. I am not in favor of
breaking such stuff without need.

Alright.

How about we just say that the password in these old views always reads
out as '********' even when there is a password, and we invent new views
that carry real auth information? (Hopefully in an extensible way.)

I'd be alright with that approach, I'd just rather that any clients
which actually want to read the password field be updated to look at the
extensible and sensible base catalogs, and not some hacked up array that
we shoved into that field.

Well, then let's mask it, and just have pg_auth_verifiers. Another
possible problem that I can see with this patch is what do we do with
valid_until? The last set of patches sent did not switch this field to
be per-verifier settable. I would consider a saner approach to keep
things simple and still do that. Allowing multiple verifiers per
protocol is a problem, and having a solution for it would be nice.
Should this be prioritized before having more protocols like SCRAM?

FWIW, browsing through pgbouncer, it has a look at pg_shadow for
user's password to build a basic configuration file.

(My mistake, while pg_user is world-readable, that's not the case of pg_shadow).
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#76)
Re: WIP: SCRAM authentication

On Mon, Feb 15, 2016 at 11:05 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Mon, Feb 15, 2016 at 10:51 AM, Stephen Frost <sfrost@snowman.net> wrote:

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

Why do we need pg_shadow or pg_user or related views at all..?

A lot of code looks at those just to get usernames. I am not in favor of
breaking such stuff without need.

Alright.

How about we just say that the password in these old views always reads
out as '********' even when there is a password, and we invent new views
that carry real auth information? (Hopefully in an extensible way.)

I'd be alright with that approach, I'd just rather that any clients
which actually want to read the password field be updated to look at the
extensible and sensible base catalogs, and not some hacked up array that
we shoved into that field.

Well, then let's mask it, and just have pg_auth_verifiers. Another
possible problem that I can see with this patch is what do we do with
valid_until? The last set of patches sent did not switch this field to
be per-verifier settable. I would consider a saner approach to keep
things simple and still do that. Allowing multiple verifiers per
protocol is a problem, and having a solution for it would be nice.
Should this be prioritized before having more protocols like SCRAM?

FWIW, browsing through pgbouncer, it has a look at pg_shadow for
user's password to build a basic configuration file.

(My mistake, while pg_user is world-readable, that's not the case of pg_shadow).

FWIW, I am going to create a new thread once I am done with the set of
patches I have in mind for the upcoming CF (yes there will be
refreshed patches), because this thread has moved on a bit larger
discussion than SCRAM itself, summarizing what is more or less the
conclusion of this thread, explaining what the patches are doing, what
they are not doing, what could be done afterwards, etc, etc. I'll keep
a clear scope regarding what I am aiming at.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers